Files
pezkuwi-subxt/polkadot/node/core/runtime-api/src/tests.rs
T
Alexandru Gheorghe a84dd0dba5 Approve multiple candidates with a single signature (#1191)
Initial implementation for the plan discussed here: https://github.com/paritytech/polkadot-sdk/issues/701
Built on top of https://github.com/paritytech/polkadot-sdk/pull/1178
v0: https://github.com/paritytech/polkadot/pull/7554,

## Overall idea

When approval-voting checks a candidate and is ready to advertise the
approval, defer it in a per-relay chain block until we either have
MAX_APPROVAL_COALESCE_COUNT candidates to sign or a candidate has stayed
MAX_APPROVALS_COALESCE_TICKS in the queue, in both cases we sign what
candidates we have available.

This should allow us to reduce the number of approvals messages we have
to create/send/verify. The parameters are configurable, so we should
find some values that balance:

- Security of the network: Delaying broadcasting of an approval
shouldn't but the finality at risk and to make sure that never happens
we won't delay sending a vote if we are past 2/3 from the no-show time.
- Scalability of the network: MAX_APPROVAL_COALESCE_COUNT = 1 &
MAX_APPROVALS_COALESCE_TICKS =0, is what we have now and we know from
the measurements we did on versi, it bottlenecks
approval-distribution/approval-voting when increase significantly the
number of validators and parachains
- Block storage: In case of disputes we have to import this votes on
chain and that increase the necessary storage with
MAX_APPROVAL_COALESCE_COUNT * CandidateHash per vote. Given that
disputes are not the normal way of the network functioning and we will
limit MAX_APPROVAL_COALESCE_COUNT in the single digits numbers, this
should be good enough. Alternatively, we could try to create a better
way to store this on-chain through indirection, if that's needed.

## Other fixes:
- Fixed the fact that we were sending random assignments to
non-validators, that was wrong because those won't do anything with it
and they won't gossip it either because they do not have a grid topology
set, so we would waste the random assignments.
- Added metrics to be able to debug potential no-shows and
mis-processing of approvals/assignments.

## TODO:
- [x] Get feedback, that this is moving in the right direction. @ordian
@sandreim @eskimor @burdges, let me know what you think.
- [x] More and more testing.
- [x]  Test in versi.
- [x] Make MAX_APPROVAL_COALESCE_COUNT &
MAX_APPROVAL_COALESCE_WAIT_MILLIS a parachain host configuration.
- [x] Make sure the backwards compatibility works correctly
- [x] Make sure this direction is compatible with other streams of work:
https://github.com/paritytech/polkadot-sdk/issues/635 &
https://github.com/paritytech/polkadot-sdk/issues/742
- [x] Final versi burn-in before merging

---------

Signed-off-by: Alexandru Gheorghe <alexandru.gheorghe@parity.io>
2023-12-13 08:43:15 +02:00

1138 lines
34 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 super::*;
use polkadot_node_primitives::{BabeAllowedSlots, BabeEpoch, BabeEpochConfiguration};
use polkadot_node_subsystem::SpawnGlue;
use polkadot_node_subsystem_test_helpers::make_subsystem_context;
use polkadot_primitives::{
async_backing, slashing,
vstaging::{ApprovalVotingParams, NodeFeatures},
AuthorityDiscoveryId, BlockNumber, CandidateCommitments, CandidateEvent, CandidateHash,
CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupRotationInfo,
Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, OccupiedCoreAssumption,
PersistedValidationData, PvfCheckStatement, ScrapedOnChainVotes, SessionIndex, SessionInfo,
Slot, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature,
};
use sp_api::ApiError;
use sp_core::testing::TaskExecutor;
use std::{
collections::{BTreeMap, HashMap},
sync::{Arc, Mutex},
};
use test_helpers::{dummy_committed_candidate_receipt, dummy_validation_code};
#[derive(Default)]
struct MockSubsystemClient {
submitted_pvf_check_statement: Arc<Mutex<Vec<(PvfCheckStatement, ValidatorSignature)>>>,
authorities: Vec<AuthorityDiscoveryId>,
validators: Vec<ValidatorId>,
validator_groups: Vec<Vec<ValidatorIndex>>,
availability_cores: Vec<CoreState>,
validation_data: HashMap<ParaId, PersistedValidationData>,
validation_code: HashMap<ParaId, ValidationCode>,
validation_outputs_results: HashMap<ParaId, bool>,
session_index_for_child: SessionIndex,
candidate_pending_availability: HashMap<ParaId, CommittedCandidateReceipt>,
dmq_contents: HashMap<ParaId, Vec<InboundDownwardMessage>>,
hrmp_channels: HashMap<ParaId, BTreeMap<ParaId, Vec<InboundHrmpMessage>>>,
validation_code_by_hash: HashMap<ValidationCodeHash, ValidationCode>,
availability_cores_wait: Arc<Mutex<()>>,
babe_epoch: Option<BabeEpoch>,
pvfs_require_precheck: Vec<ValidationCodeHash>,
validation_code_hash: HashMap<ParaId, ValidationCodeHash>,
session_info: HashMap<SessionIndex, SessionInfo>,
candidate_events: Vec<CandidateEvent>,
}
#[async_trait::async_trait]
impl RuntimeApiSubsystemClient for MockSubsystemClient {
async fn api_version_parachain_host(&self, _: Hash) -> Result<Option<u32>, ApiError> {
Ok(Some(5))
}
async fn validators(&self, _: Hash) -> Result<Vec<ValidatorId>, ApiError> {
Ok(self.validators.clone())
}
async fn validator_groups(
&self,
_: Hash,
) -> Result<(Vec<Vec<ValidatorIndex>>, GroupRotationInfo<BlockNumber>), ApiError> {
Ok((
self.validator_groups.clone(),
GroupRotationInfo { session_start_block: 1, group_rotation_frequency: 100, now: 10 },
))
}
async fn availability_cores(
&self,
_: Hash,
) -> Result<Vec<CoreState<Hash, BlockNumber>>, ApiError> {
let _lock = self.availability_cores_wait.lock().unwrap();
Ok(self.availability_cores.clone())
}
async fn persisted_validation_data(
&self,
_: Hash,
para_id: ParaId,
_: OccupiedCoreAssumption,
) -> Result<Option<PersistedValidationData<Hash, BlockNumber>>, ApiError> {
Ok(self.validation_data.get(&para_id).cloned())
}
async fn assumed_validation_data(
&self,
_: Hash,
para_id: ParaId,
expected_persisted_validation_data_hash: Hash,
) -> Result<Option<(PersistedValidationData<Hash, BlockNumber>, ValidationCodeHash)>, ApiError>
{
Ok(self
.validation_data
.get(&para_id)
.cloned()
.filter(|data| data.hash() == expected_persisted_validation_data_hash)
.zip(self.validation_code.get(&para_id).map(|code| code.hash())))
}
async fn check_validation_outputs(
&self,
_: Hash,
para_id: ParaId,
_: CandidateCommitments,
) -> Result<bool, ApiError> {
Ok(self.validation_outputs_results.get(&para_id).copied().unwrap())
}
async fn session_index_for_child(&self, _: Hash) -> Result<SessionIndex, ApiError> {
Ok(self.session_index_for_child)
}
async fn validation_code(
&self,
_: Hash,
para_id: ParaId,
_: OccupiedCoreAssumption,
) -> Result<Option<ValidationCode>, ApiError> {
Ok(self.validation_code.get(&para_id).cloned())
}
async fn candidate_pending_availability(
&self,
_: Hash,
para_id: ParaId,
) -> Result<Option<CommittedCandidateReceipt<Hash>>, ApiError> {
Ok(self.candidate_pending_availability.get(&para_id).cloned())
}
async fn candidate_events(&self, _: Hash) -> Result<Vec<CandidateEvent<Hash>>, ApiError> {
Ok(self.candidate_events.clone())
}
async fn dmq_contents(
&self,
_: Hash,
para_id: ParaId,
) -> Result<Vec<InboundDownwardMessage<BlockNumber>>, ApiError> {
Ok(self.dmq_contents.get(&para_id).cloned().unwrap())
}
async fn inbound_hrmp_channels_contents(
&self,
_: Hash,
para_id: ParaId,
) -> Result<BTreeMap<ParaId, Vec<InboundHrmpMessage<BlockNumber>>>, ApiError> {
Ok(self.hrmp_channels.get(&para_id).cloned().unwrap())
}
async fn validation_code_by_hash(
&self,
_: Hash,
hash: ValidationCodeHash,
) -> Result<Option<ValidationCode>, ApiError> {
Ok(self.validation_code_by_hash.get(&hash).cloned())
}
async fn on_chain_votes(&self, _: Hash) -> Result<Option<ScrapedOnChainVotes<Hash>>, ApiError> {
todo!("Not required for tests")
}
async fn session_info(
&self,
_: Hash,
index: SessionIndex,
) -> Result<Option<SessionInfo>, ApiError> {
Ok(self.session_info.get(&index).cloned())
}
async fn submit_pvf_check_statement(
&self,
_: Hash,
stmt: PvfCheckStatement,
sig: ValidatorSignature,
) -> Result<(), ApiError> {
self.submitted_pvf_check_statement.lock().unwrap().push((stmt, sig));
Ok(())
}
async fn pvfs_require_precheck(&self, _: Hash) -> Result<Vec<ValidationCodeHash>, ApiError> {
Ok(self.pvfs_require_precheck.clone())
}
async fn validation_code_hash(
&self,
_: Hash,
para_id: ParaId,
_: OccupiedCoreAssumption,
) -> Result<Option<ValidationCodeHash>, ApiError> {
Ok(self.validation_code_hash.get(&para_id).cloned())
}
async fn disputes(
&self,
_: Hash,
) -> Result<Vec<(SessionIndex, CandidateHash, DisputeState<BlockNumber>)>, ApiError> {
todo!("Not required for tests")
}
async fn unapplied_slashes(
&self,
_: Hash,
) -> Result<Vec<(SessionIndex, CandidateHash, slashing::PendingSlashes)>, ApiError> {
todo!("Not required for tests")
}
async fn key_ownership_proof(
&self,
_: Hash,
_: ValidatorId,
) -> Result<Option<slashing::OpaqueKeyOwnershipProof>, ApiError> {
todo!("Not required for tests")
}
async fn submit_report_dispute_lost(
&self,
_: Hash,
_: slashing::DisputeProof,
_: slashing::OpaqueKeyOwnershipProof,
) -> Result<Option<()>, ApiError> {
todo!("Not required for tests")
}
async fn session_executor_params(
&self,
_: Hash,
_: SessionIndex,
) -> Result<Option<ExecutorParams>, ApiError> {
todo!("Not required for tests")
}
/// Approval voting configuration parameters
async fn approval_voting_params(
&self,
_: Hash,
_: SessionIndex,
) -> Result<ApprovalVotingParams, ApiError> {
todo!("Not required for tests")
}
async fn current_epoch(&self, _: Hash) -> Result<sp_consensus_babe::Epoch, ApiError> {
Ok(self.babe_epoch.as_ref().unwrap().clone())
}
async fn authorities(&self, _: Hash) -> Result<Vec<AuthorityDiscoveryId>, ApiError> {
Ok(self.authorities.clone())
}
async fn async_backing_params(
&self,
_: Hash,
) -> Result<async_backing::AsyncBackingParams, ApiError> {
todo!("Not required for tests")
}
async fn para_backing_state(
&self,
_: Hash,
_: ParaId,
) -> Result<Option<async_backing::BackingState>, ApiError> {
todo!("Not required for tests")
}
async fn minimum_backing_votes(&self, _: Hash, _: SessionIndex) -> Result<u32, ApiError> {
todo!("Not required for tests")
}
async fn node_features(&self, _: Hash) -> Result<NodeFeatures, ApiError> {
todo!("Not required for tests")
}
async fn disabled_validators(&self, _: Hash) -> Result<Vec<ValidatorIndex>, ApiError> {
todo!("Not required for tests")
}
}
#[test]
fn requests_authorities() {
let (ctx, mut ctx_handle) = make_subsystem_context(TaskExecutor::new());
let substem_client = Arc::new(MockSubsystemClient::default());
let relay_parent = [1; 32].into();
let spawner = sp_core::testing::TaskExecutor::new();
let subsystem =
RuntimeApiSubsystem::new(substem_client.clone(), Metrics(None), SpawnGlue(spawner));
let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap());
let test_task = async move {
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(relay_parent, Request::Authorities(tx)),
})
.await;
assert_eq!(rx.await.unwrap().unwrap(), substem_client.authorities);
ctx_handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
};
futures::executor::block_on(future::join(subsystem_task, test_task));
}
#[test]
fn requests_validators() {
let (ctx, mut ctx_handle) = make_subsystem_context(TaskExecutor::new());
let subsystem_client = Arc::new(MockSubsystemClient::default());
let relay_parent = [1; 32].into();
let spawner = sp_core::testing::TaskExecutor::new();
let subsystem =
RuntimeApiSubsystem::new(subsystem_client.clone(), Metrics(None), SpawnGlue(spawner));
let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap());
let test_task = async move {
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(relay_parent, Request::Validators(tx)),
})
.await;
assert_eq!(rx.await.unwrap().unwrap(), subsystem_client.validators);
ctx_handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
};
futures::executor::block_on(future::join(subsystem_task, test_task));
}
#[test]
fn requests_validator_groups() {
let (ctx, mut ctx_handle) = make_subsystem_context(TaskExecutor::new());
let subsystem_client = Arc::new(MockSubsystemClient::default());
let relay_parent = [1; 32].into();
let spawner = sp_core::testing::TaskExecutor::new();
let subsystem =
RuntimeApiSubsystem::new(subsystem_client.clone(), Metrics(None), SpawnGlue(spawner));
let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap());
let test_task = async move {
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(relay_parent, Request::ValidatorGroups(tx)),
})
.await;
assert_eq!(rx.await.unwrap().unwrap().0, subsystem_client.validator_groups);
ctx_handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
};
futures::executor::block_on(future::join(subsystem_task, test_task));
}
#[test]
fn requests_availability_cores() {
let (ctx, mut ctx_handle) = make_subsystem_context(TaskExecutor::new());
let subsystem_client = Arc::new(MockSubsystemClient::default());
let relay_parent = [1; 32].into();
let spawner = sp_core::testing::TaskExecutor::new();
let subsystem =
RuntimeApiSubsystem::new(subsystem_client.clone(), Metrics(None), SpawnGlue(spawner));
let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap());
let test_task = async move {
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(relay_parent, Request::AvailabilityCores(tx)),
})
.await;
assert_eq!(rx.await.unwrap().unwrap(), subsystem_client.availability_cores);
ctx_handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
};
futures::executor::block_on(future::join(subsystem_task, test_task));
}
#[test]
fn requests_persisted_validation_data() {
let (ctx, mut ctx_handle) = make_subsystem_context(TaskExecutor::new());
let relay_parent = [1; 32].into();
let para_a = ParaId::from(5_u32);
let para_b = ParaId::from(6_u32);
let spawner = sp_core::testing::TaskExecutor::new();
let mut subsystem_client = MockSubsystemClient::default();
subsystem_client.validation_data.insert(para_a, Default::default());
let subsystem_client = Arc::new(subsystem_client);
let subsystem =
RuntimeApiSubsystem::new(subsystem_client.clone(), Metrics(None), SpawnGlue(spawner));
let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap());
let test_task = async move {
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(
relay_parent,
Request::PersistedValidationData(para_a, OccupiedCoreAssumption::Included, tx),
),
})
.await;
assert_eq!(rx.await.unwrap().unwrap(), Some(Default::default()));
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(
relay_parent,
Request::PersistedValidationData(para_b, OccupiedCoreAssumption::Included, tx),
),
})
.await;
assert_eq!(rx.await.unwrap().unwrap(), None);
ctx_handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
};
futures::executor::block_on(future::join(subsystem_task, test_task));
}
#[test]
fn requests_assumed_validation_data() {
let (ctx, mut ctx_handle) = make_subsystem_context(TaskExecutor::new());
let relay_parent = [1; 32].into();
let para_a = ParaId::from(5_u32);
let para_b = ParaId::from(6_u32);
let spawner = sp_core::testing::TaskExecutor::new();
let validation_code = ValidationCode(vec![1, 2, 3]);
let expected_data_hash = <PersistedValidationData as Default>::default().hash();
let expected_code_hash = validation_code.hash();
let mut subsystem_client = MockSubsystemClient::default();
subsystem_client.validation_data.insert(para_a, Default::default());
subsystem_client.validation_code.insert(para_a, validation_code);
subsystem_client.validation_data.insert(para_b, Default::default());
let subsystem_client = Arc::new(subsystem_client);
let subsystem =
RuntimeApiSubsystem::new(subsystem_client.clone(), Metrics(None), SpawnGlue(spawner));
let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap());
let test_task = async move {
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(
relay_parent,
Request::AssumedValidationData(para_a, expected_data_hash, tx),
),
})
.await;
assert_eq!(rx.await.unwrap().unwrap(), Some((Default::default(), expected_code_hash)));
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(
relay_parent,
Request::AssumedValidationData(para_a, Hash::zero(), tx),
),
})
.await;
assert_eq!(rx.await.unwrap().unwrap(), None);
ctx_handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
};
futures::executor::block_on(future::join(subsystem_task, test_task));
}
#[test]
fn requests_check_validation_outputs() {
let (ctx, mut ctx_handle) = make_subsystem_context(TaskExecutor::new());
let mut subsystem_client = MockSubsystemClient::default();
let relay_parent = [1; 32].into();
let para_a = ParaId::from(5_u32);
let para_b = ParaId::from(6_u32);
let commitments = polkadot_primitives::CandidateCommitments::default();
let spawner = sp_core::testing::TaskExecutor::new();
subsystem_client.validation_outputs_results.insert(para_a, false);
subsystem_client.validation_outputs_results.insert(para_b, true);
let subsystem_client = Arc::new(subsystem_client);
let subsystem =
RuntimeApiSubsystem::new(subsystem_client.clone(), Metrics(None), SpawnGlue(spawner));
let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap());
let test_task = async move {
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(
relay_parent,
Request::CheckValidationOutputs(para_a, commitments.clone(), tx),
),
})
.await;
assert_eq!(
rx.await.unwrap().unwrap(),
subsystem_client.validation_outputs_results[&para_a]
);
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(
relay_parent,
Request::CheckValidationOutputs(para_b, commitments, tx),
),
})
.await;
assert_eq!(
rx.await.unwrap().unwrap(),
subsystem_client.validation_outputs_results[&para_b]
);
ctx_handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
};
futures::executor::block_on(future::join(subsystem_task, test_task));
}
#[test]
fn requests_session_index_for_child() {
let (ctx, mut ctx_handle) = make_subsystem_context(TaskExecutor::new());
let subsystem_client = Arc::new(MockSubsystemClient::default());
let relay_parent = [1; 32].into();
let spawner = sp_core::testing::TaskExecutor::new();
let subsystem =
RuntimeApiSubsystem::new(subsystem_client.clone(), Metrics(None), SpawnGlue(spawner));
let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap());
let test_task = async move {
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(relay_parent, Request::SessionIndexForChild(tx)),
})
.await;
assert_eq!(rx.await.unwrap().unwrap(), subsystem_client.session_index_for_child);
ctx_handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
};
futures::executor::block_on(future::join(subsystem_task, test_task));
}
fn dummy_session_info() -> SessionInfo {
SessionInfo {
validators: Default::default(),
discovery_keys: vec![],
assignment_keys: vec![],
validator_groups: Default::default(),
n_cores: 4u32,
zeroth_delay_tranche_width: 0u32,
relay_vrf_modulo_samples: 0u32,
n_delay_tranches: 2u32,
no_show_slots: 0u32,
needed_approvals: 1u32,
active_validator_indices: vec![],
dispute_period: 6,
random_seed: [0u8; 32],
}
}
#[test]
fn requests_session_info() {
let (ctx, mut ctx_handle) = make_subsystem_context(TaskExecutor::new());
let mut subsystem_client = MockSubsystemClient::default();
let session_index = 1;
subsystem_client.session_info.insert(session_index, dummy_session_info());
let subsystem_client = Arc::new(subsystem_client);
let spawner = sp_core::testing::TaskExecutor::new();
let relay_parent = [1; 32].into();
let subsystem =
RuntimeApiSubsystem::new(subsystem_client.clone(), Metrics(None), SpawnGlue(spawner));
let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap());
let test_task = async move {
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(
relay_parent,
Request::SessionInfo(session_index, tx),
),
})
.await;
assert_eq!(rx.await.unwrap().unwrap(), Some(dummy_session_info()));
ctx_handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
};
futures::executor::block_on(future::join(subsystem_task, test_task));
}
#[test]
fn requests_validation_code() {
let (ctx, mut ctx_handle) = make_subsystem_context(TaskExecutor::new());
let relay_parent = [1; 32].into();
let para_a = ParaId::from(5_u32);
let para_b = ParaId::from(6_u32);
let spawner = sp_core::testing::TaskExecutor::new();
let validation_code = dummy_validation_code();
let mut subsystem_client = MockSubsystemClient::default();
subsystem_client.validation_code.insert(para_a, validation_code.clone());
let subsystem_client = Arc::new(subsystem_client);
let subsystem =
RuntimeApiSubsystem::new(subsystem_client.clone(), Metrics(None), SpawnGlue(spawner));
let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap());
let test_task = async move {
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(
relay_parent,
Request::ValidationCode(para_a, OccupiedCoreAssumption::Included, tx),
),
})
.await;
assert_eq!(rx.await.unwrap().unwrap(), Some(validation_code));
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(
relay_parent,
Request::ValidationCode(para_b, OccupiedCoreAssumption::Included, tx),
),
})
.await;
assert_eq!(rx.await.unwrap().unwrap(), None);
ctx_handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
};
futures::executor::block_on(future::join(subsystem_task, test_task));
}
#[test]
fn requests_candidate_pending_availability() {
let (ctx, mut ctx_handle) = make_subsystem_context(TaskExecutor::new());
let relay_parent = [1; 32].into();
let para_a = ParaId::from(5_u32);
let para_b = ParaId::from(6_u32);
let spawner = sp_core::testing::TaskExecutor::new();
let candidate_receipt = dummy_committed_candidate_receipt(relay_parent);
let mut subsystem_client = MockSubsystemClient::default();
subsystem_client
.candidate_pending_availability
.insert(para_a, candidate_receipt.clone());
let subsystem_client = Arc::new(subsystem_client);
let subsystem =
RuntimeApiSubsystem::new(subsystem_client.clone(), Metrics(None), SpawnGlue(spawner));
let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap());
let test_task = async move {
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(
relay_parent,
Request::CandidatePendingAvailability(para_a, tx),
),
})
.await;
assert_eq!(rx.await.unwrap().unwrap(), Some(candidate_receipt));
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(
relay_parent,
Request::CandidatePendingAvailability(para_b, tx),
),
})
.await;
assert_eq!(rx.await.unwrap().unwrap(), None);
ctx_handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
};
futures::executor::block_on(future::join(subsystem_task, test_task));
}
#[test]
fn requests_candidate_events() {
let (ctx, mut ctx_handle) = make_subsystem_context(TaskExecutor::new());
let subsystem_client = Arc::new(MockSubsystemClient::default());
let relay_parent = [1; 32].into();
let spawner = sp_core::testing::TaskExecutor::new();
let subsystem =
RuntimeApiSubsystem::new(subsystem_client.clone(), Metrics(None), SpawnGlue(spawner));
let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap());
let test_task = async move {
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(relay_parent, Request::CandidateEvents(tx)),
})
.await;
assert_eq!(rx.await.unwrap().unwrap(), subsystem_client.candidate_events);
ctx_handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
};
futures::executor::block_on(future::join(subsystem_task, test_task));
}
#[test]
fn requests_dmq_contents() {
let (ctx, mut ctx_handle) = make_subsystem_context(TaskExecutor::new());
let relay_parent = [1; 32].into();
let para_a = ParaId::from(5_u32);
let para_b = ParaId::from(6_u32);
let spawner = sp_core::testing::TaskExecutor::new();
let subsystem_client = Arc::new({
let mut subsystem_client = MockSubsystemClient::default();
subsystem_client.dmq_contents.insert(para_a, vec![]);
subsystem_client.dmq_contents.insert(
para_b,
vec![InboundDownwardMessage { sent_at: 228, msg: b"Novus Ordo Seclorum".to_vec() }],
);
subsystem_client
});
let subsystem =
RuntimeApiSubsystem::new(subsystem_client.clone(), Metrics(None), SpawnGlue(spawner));
let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap());
let test_task = async move {
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(relay_parent, Request::DmqContents(para_a, tx)),
})
.await;
assert_eq!(rx.await.unwrap().unwrap(), vec![]);
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(relay_parent, Request::DmqContents(para_b, tx)),
})
.await;
assert_eq!(
rx.await.unwrap().unwrap(),
vec![InboundDownwardMessage { sent_at: 228, msg: b"Novus Ordo Seclorum".to_vec() }]
);
ctx_handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
};
futures::executor::block_on(future::join(subsystem_task, test_task));
}
#[test]
fn requests_inbound_hrmp_channels_contents() {
let (ctx, mut ctx_handle) = make_subsystem_context(TaskExecutor::new());
let relay_parent = [1; 32].into();
let para_a = ParaId::from(99_u32);
let para_b = ParaId::from(66_u32);
let para_c = ParaId::from(33_u32);
let spawner = sp_core::testing::TaskExecutor::new();
let para_b_inbound_channels = [
(para_a, vec![]),
(para_c, vec![InboundHrmpMessage { sent_at: 1, data: "𝙀=𝙈𝘾²".as_bytes().to_owned() }]),
]
.into_iter()
.collect::<BTreeMap<_, _>>();
let subsystem_client = Arc::new({
let mut subsystem_client = MockSubsystemClient::default();
subsystem_client.hrmp_channels.insert(para_a, BTreeMap::new());
subsystem_client.hrmp_channels.insert(para_b, para_b_inbound_channels.clone());
subsystem_client
});
let subsystem =
RuntimeApiSubsystem::new(subsystem_client.clone(), Metrics(None), SpawnGlue(spawner));
let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap());
let test_task = async move {
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(
relay_parent,
Request::InboundHrmpChannelsContents(para_a, tx),
),
})
.await;
assert_eq!(rx.await.unwrap().unwrap(), BTreeMap::new());
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(
relay_parent,
Request::InboundHrmpChannelsContents(para_b, tx),
),
})
.await;
assert_eq!(rx.await.unwrap().unwrap(), para_b_inbound_channels);
ctx_handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
};
futures::executor::block_on(future::join(subsystem_task, test_task));
}
#[test]
fn requests_validation_code_by_hash() {
let (ctx, mut ctx_handle) = make_subsystem_context(TaskExecutor::new());
let spawner = sp_core::testing::TaskExecutor::new();
let (subsystem_client, validation_code) = {
let mut subsystem_client = MockSubsystemClient::default();
let mut validation_code = Vec::new();
for n in 0..5 {
let code = ValidationCode::from(vec![n; 32]);
subsystem_client.validation_code_by_hash.insert(code.hash(), code.clone());
validation_code.push(code);
}
(Arc::new(subsystem_client), validation_code)
};
let subsystem =
RuntimeApiSubsystem::new(subsystem_client.clone(), Metrics(None), SpawnGlue(spawner));
let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap());
let relay_parent = [1; 32].into();
let test_task = async move {
for code in validation_code {
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(
relay_parent,
Request::ValidationCodeByHash(code.hash(), tx),
),
})
.await;
assert_eq!(rx.await.unwrap().unwrap(), Some(code));
}
ctx_handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
};
futures::executor::block_on(future::join(subsystem_task, test_task));
}
#[test]
fn multiple_requests_in_parallel_are_working() {
let (ctx, mut ctx_handle) = make_subsystem_context(TaskExecutor::new());
let subsystem_client = Arc::new(MockSubsystemClient::default());
let relay_parent = [1; 32].into();
let spawner = sp_core::testing::TaskExecutor::new();
let mutex = subsystem_client.availability_cores_wait.clone();
let subsystem =
RuntimeApiSubsystem::new(subsystem_client.clone(), Metrics(None), SpawnGlue(spawner));
let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap());
let test_task = async move {
// Make all requests block until we release this mutex.
let lock = mutex.lock().unwrap();
let mut receivers = Vec::new();
for _ in 0..MAX_PARALLEL_REQUESTS {
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(relay_parent, Request::AvailabilityCores(tx)),
})
.await;
receivers.push(rx);
}
// The backpressure from reaching `MAX_PARALLEL_REQUESTS` will make the test block, we need
// to drop the lock.
drop(lock);
for _ in 0..MAX_PARALLEL_REQUESTS * 100 {
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(relay_parent, Request::AvailabilityCores(tx)),
})
.await;
receivers.push(rx);
}
let join = future::join_all(receivers);
join.await
.into_iter()
.for_each(|r| assert_eq!(r.unwrap().unwrap(), subsystem_client.availability_cores));
ctx_handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
};
futures::executor::block_on(future::join(subsystem_task, test_task));
}
#[test]
fn requests_babe_epoch() {
let (ctx, mut ctx_handle) = make_subsystem_context(TaskExecutor::new());
let mut subsystem_client = MockSubsystemClient::default();
let epoch = BabeEpoch {
epoch_index: 100,
start_slot: Slot::from(1000),
duration: 10,
authorities: Vec::new(),
randomness: [1u8; 32],
config: BabeEpochConfiguration { c: (1, 4), allowed_slots: BabeAllowedSlots::PrimarySlots },
};
subsystem_client.babe_epoch = Some(epoch.clone());
let subsystem_client = Arc::new(subsystem_client);
let relay_parent = [1; 32].into();
let spawner = sp_core::testing::TaskExecutor::new();
let subsystem =
RuntimeApiSubsystem::new(subsystem_client.clone(), Metrics(None), SpawnGlue(spawner));
let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap());
let test_task = async move {
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(relay_parent, Request::CurrentBabeEpoch(tx)),
})
.await;
assert_eq!(rx.await.unwrap().unwrap(), epoch);
ctx_handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
};
futures::executor::block_on(future::join(subsystem_task, test_task));
}
#[test]
fn requests_submit_pvf_check_statement() {
let (ctx, mut ctx_handle) = make_subsystem_context(TaskExecutor::new());
let spawner = sp_core::testing::TaskExecutor::new();
let subsystem_client = Arc::new(MockSubsystemClient::default());
let subsystem =
RuntimeApiSubsystem::new(subsystem_client.clone(), Metrics(None), SpawnGlue(spawner));
let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap());
let relay_parent = [1; 32].into();
let test_task = async move {
let (stmt, sig) = fake_statement();
// Send the same statement twice.
//
// Here we just want to ensure that those requests do not go through the cache.
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(
relay_parent,
Request::SubmitPvfCheckStatement(stmt.clone(), sig.clone(), tx),
),
})
.await;
let _ = rx.await.unwrap().unwrap();
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(
relay_parent,
Request::SubmitPvfCheckStatement(stmt.clone(), sig.clone(), tx),
),
})
.await;
let _ = rx.await.unwrap().unwrap();
assert_eq!(
&*subsystem_client.submitted_pvf_check_statement.lock().expect("poisened mutex"),
&[(stmt.clone(), sig.clone()), (stmt.clone(), sig.clone())]
);
ctx_handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
};
futures::executor::block_on(future::join(subsystem_task, test_task));
fn fake_statement() -> (PvfCheckStatement, ValidatorSignature) {
let stmt = PvfCheckStatement {
accept: true,
subject: [1; 32].into(),
session_index: 1,
validator_index: 1.into(),
};
let sig = sp_keyring::Sr25519Keyring::Alice.sign(&stmt.signing_payload()).into();
(stmt, sig)
}
}
#[test]
fn requests_pvfs_require_precheck() {
let (ctx, mut ctx_handle) = make_subsystem_context(TaskExecutor::new());
let spawner = sp_core::testing::TaskExecutor::new();
let subsystem_client = Arc::new({
let mut subsystem_client = MockSubsystemClient::default();
subsystem_client.pvfs_require_precheck = vec![[1; 32].into(), [2; 32].into()];
subsystem_client
});
let subsystem =
RuntimeApiSubsystem::new(subsystem_client.clone(), Metrics(None), SpawnGlue(spawner));
let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap());
let relay_parent = [1; 32].into();
let test_task = async move {
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(relay_parent, Request::PvfsRequirePrecheck(tx)),
})
.await;
assert_eq!(rx.await.unwrap().unwrap(), vec![[1; 32].into(), [2; 32].into()]);
ctx_handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
};
futures::executor::block_on(future::join(subsystem_task, test_task));
}
#[test]
fn requests_validation_code_hash() {
let (ctx, mut ctx_handle) = make_subsystem_context(TaskExecutor::new());
let relay_parent = [1; 32].into();
let para_a = ParaId::from(5_u32);
let para_b = ParaId::from(6_u32);
let spawner = sp_core::testing::TaskExecutor::new();
let validation_code_hash = dummy_validation_code().hash();
let mut subsystem_client = MockSubsystemClient::default();
subsystem_client.validation_code_hash.insert(para_a, validation_code_hash);
let subsystem_client = Arc::new(subsystem_client);
let subsystem =
RuntimeApiSubsystem::new(subsystem_client.clone(), Metrics(None), SpawnGlue(spawner));
let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap());
let test_task = async move {
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(
relay_parent,
Request::ValidationCodeHash(para_a, OccupiedCoreAssumption::Included, tx),
),
})
.await;
assert_eq!(rx.await.unwrap().unwrap(), Some(validation_code_hash));
let (tx, rx) = oneshot::channel();
ctx_handle
.send(FromOrchestra::Communication {
msg: RuntimeApiMessage::Request(
relay_parent,
Request::ValidationCodeHash(para_b, OccupiedCoreAssumption::Included, tx),
),
})
.await;
assert_eq!(rx.await.unwrap().unwrap(), None);
ctx_handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
};
futures::executor::block_on(future::join(subsystem_task, test_task));
}