mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-11 04:37:21 +00:00
e58e854a32
The PR adds two things: 1. Runtime API exposing the whole claim queue 2. Consumes the API in `collation-generation` to fetch the next scheduled `ParaEntry` for an occupied core. Related to https://github.com/paritytech/polkadot-sdk/issues/1797
947 lines
30 KiB
Rust
947 lines
30 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 super::*;
|
|
use assert_matches::assert_matches;
|
|
use futures::{
|
|
lock::Mutex,
|
|
task::{Context as FuturesContext, Poll},
|
|
Future,
|
|
};
|
|
use polkadot_node_primitives::{BlockData, Collation, CollationResult, MaybeCompressedPoV, PoV};
|
|
use polkadot_node_subsystem::{
|
|
errors::RuntimeApiError,
|
|
messages::{AllMessages, RuntimeApiMessage, RuntimeApiRequest},
|
|
ActivatedLeaf,
|
|
};
|
|
use polkadot_node_subsystem_test_helpers::{subsystem_test_harness, TestSubsystemContextHandle};
|
|
use polkadot_node_subsystem_util::TimeoutExt;
|
|
use polkadot_primitives::{
|
|
AsyncBackingParams, CollatorPair, HeadData, Id as ParaId, Id, PersistedValidationData,
|
|
ScheduledCore, ValidationCode,
|
|
};
|
|
use rstest::rstest;
|
|
use sp_keyring::sr25519::Keyring as Sr25519Keyring;
|
|
use std::pin::Pin;
|
|
use test_helpers::{dummy_candidate_descriptor, dummy_hash, dummy_head_data, dummy_validator};
|
|
|
|
type VirtualOverseer = TestSubsystemContextHandle<CollationGenerationMessage>;
|
|
|
|
fn test_harness<T: Future<Output = VirtualOverseer>>(test: impl FnOnce(VirtualOverseer) -> T) {
|
|
let pool = sp_core::testing::TaskExecutor::new();
|
|
let (context, virtual_overseer) =
|
|
polkadot_node_subsystem_test_helpers::make_subsystem_context(pool);
|
|
let subsystem = async move {
|
|
let subsystem = crate::CollationGenerationSubsystem::new(Metrics::default());
|
|
|
|
subsystem.run(context).await;
|
|
};
|
|
|
|
let test_fut = test(virtual_overseer);
|
|
|
|
futures::pin_mut!(test_fut);
|
|
futures::executor::block_on(futures::future::join(
|
|
async move {
|
|
let mut virtual_overseer = test_fut.await;
|
|
// Ensure we have handled all responses.
|
|
if let Ok(Some(msg)) = virtual_overseer.rx.try_next() {
|
|
panic!("Did not handle all responses: {:?}", msg);
|
|
}
|
|
// Conclude.
|
|
virtual_overseer.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
|
|
},
|
|
subsystem,
|
|
));
|
|
}
|
|
|
|
fn test_collation() -> Collation {
|
|
Collation {
|
|
upward_messages: Default::default(),
|
|
horizontal_messages: Default::default(),
|
|
new_validation_code: None,
|
|
head_data: dummy_head_data(),
|
|
proof_of_validity: MaybeCompressedPoV::Raw(PoV { block_data: BlockData(Vec::new()) }),
|
|
processed_downward_messages: 0_u32,
|
|
hrmp_watermark: 0_u32.into(),
|
|
}
|
|
}
|
|
|
|
fn test_collation_compressed() -> Collation {
|
|
let mut collation = test_collation();
|
|
let compressed = collation.proof_of_validity.clone().into_compressed();
|
|
collation.proof_of_validity = MaybeCompressedPoV::Compressed(compressed);
|
|
collation
|
|
}
|
|
|
|
fn test_validation_data() -> PersistedValidationData {
|
|
let mut persisted_validation_data = PersistedValidationData::default();
|
|
persisted_validation_data.max_pov_size = 1024;
|
|
persisted_validation_data
|
|
}
|
|
|
|
// Box<dyn Future<Output = Collation> + Unpin + Send
|
|
struct TestCollator;
|
|
|
|
impl Future for TestCollator {
|
|
type Output = Option<CollationResult>;
|
|
|
|
fn poll(self: Pin<&mut Self>, _cx: &mut FuturesContext) -> Poll<Self::Output> {
|
|
Poll::Ready(Some(CollationResult { collation: test_collation(), result_sender: None }))
|
|
}
|
|
}
|
|
|
|
impl Unpin for TestCollator {}
|
|
|
|
async fn overseer_recv(overseer: &mut VirtualOverseer) -> AllMessages {
|
|
const TIMEOUT: std::time::Duration = std::time::Duration::from_millis(2000);
|
|
|
|
overseer
|
|
.recv()
|
|
.timeout(TIMEOUT)
|
|
.await
|
|
.expect(&format!("{:?} is long enough to receive messages", TIMEOUT))
|
|
}
|
|
|
|
fn test_config<Id: Into<ParaId>>(para_id: Id) -> CollationGenerationConfig {
|
|
CollationGenerationConfig {
|
|
key: CollatorPair::generate().0,
|
|
collator: Some(Box::new(|_: Hash, _vd: &PersistedValidationData| TestCollator.boxed())),
|
|
para_id: para_id.into(),
|
|
}
|
|
}
|
|
|
|
fn test_config_no_collator<Id: Into<ParaId>>(para_id: Id) -> CollationGenerationConfig {
|
|
CollationGenerationConfig {
|
|
key: CollatorPair::generate().0,
|
|
collator: None,
|
|
para_id: para_id.into(),
|
|
}
|
|
}
|
|
|
|
fn scheduled_core_for<Id: Into<ParaId>>(para_id: Id) -> ScheduledCore {
|
|
ScheduledCore { para_id: para_id.into(), collator: None }
|
|
}
|
|
|
|
#[rstest]
|
|
#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT - 1)]
|
|
#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)]
|
|
fn requests_availability_per_relay_parent(#[case] runtime_version: u32) {
|
|
let activated_hashes: Vec<Hash> =
|
|
vec![[1; 32].into(), [4; 32].into(), [9; 32].into(), [16; 32].into()];
|
|
|
|
let requested_availability_cores = Arc::new(Mutex::new(Vec::new()));
|
|
|
|
let overseer_requested_availability_cores = requested_availability_cores.clone();
|
|
let overseer = |mut handle: TestSubsystemContextHandle<CollationGenerationMessage>| async move {
|
|
loop {
|
|
match handle.try_recv().await {
|
|
None => break,
|
|
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(hash, RuntimeApiRequest::AvailabilityCores(tx)))) => {
|
|
overseer_requested_availability_cores.lock().await.push(hash);
|
|
tx.send(Ok(vec![])).unwrap();
|
|
}
|
|
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(_hash, RuntimeApiRequest::Validators(tx)))) => {
|
|
tx.send(Ok(vec![dummy_validator(); 3])).unwrap();
|
|
}
|
|
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
_hash,
|
|
RuntimeApiRequest::AsyncBackingParams(
|
|
tx,
|
|
),
|
|
))) => {
|
|
tx.send(Err(RuntimeApiError::NotSupported { runtime_api_name: "doesnt_matter" })).unwrap();
|
|
},
|
|
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
_hash,
|
|
RuntimeApiRequest::Version(tx),
|
|
))) => {
|
|
tx.send(Ok(runtime_version)).unwrap();
|
|
},
|
|
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
_hash,
|
|
RuntimeApiRequest::ClaimQueue(tx),
|
|
))) if runtime_version >= RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT => {
|
|
tx.send(Ok(BTreeMap::new())).unwrap();
|
|
},
|
|
Some(msg) => panic!("didn't expect any other overseer requests given no availability cores; got {:?}", msg),
|
|
}
|
|
}
|
|
};
|
|
|
|
let subsystem_activated_hashes = activated_hashes.clone();
|
|
subsystem_test_harness(overseer, |mut ctx| async move {
|
|
handle_new_activations(
|
|
Arc::new(test_config(123u32)),
|
|
subsystem_activated_hashes,
|
|
&mut ctx,
|
|
Metrics(None),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
});
|
|
|
|
let mut requested_availability_cores = Arc::try_unwrap(requested_availability_cores)
|
|
.expect("overseer should have shut down by now")
|
|
.into_inner();
|
|
requested_availability_cores.sort();
|
|
|
|
assert_eq!(requested_availability_cores, activated_hashes);
|
|
}
|
|
|
|
#[rstest]
|
|
#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT - 1)]
|
|
#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)]
|
|
fn requests_validation_data_for_scheduled_matches(#[case] runtime_version: u32) {
|
|
let activated_hashes: Vec<Hash> = vec![
|
|
Hash::repeat_byte(1),
|
|
Hash::repeat_byte(4),
|
|
Hash::repeat_byte(9),
|
|
Hash::repeat_byte(16),
|
|
];
|
|
|
|
let requested_validation_data = Arc::new(Mutex::new(Vec::new()));
|
|
|
|
let overseer_requested_validation_data = requested_validation_data.clone();
|
|
let overseer = |mut handle: TestSubsystemContextHandle<CollationGenerationMessage>| async move {
|
|
loop {
|
|
match handle.try_recv().await {
|
|
None => break,
|
|
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
hash,
|
|
RuntimeApiRequest::AvailabilityCores(tx),
|
|
))) => {
|
|
tx.send(Ok(vec![
|
|
CoreState::Free,
|
|
// this is weird, see explanation below
|
|
CoreState::Scheduled(scheduled_core_for(
|
|
(hash.as_fixed_bytes()[0] * 4) as u32,
|
|
)),
|
|
CoreState::Scheduled(scheduled_core_for(
|
|
(hash.as_fixed_bytes()[0] * 5) as u32,
|
|
)),
|
|
]))
|
|
.unwrap();
|
|
},
|
|
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
hash,
|
|
RuntimeApiRequest::PersistedValidationData(
|
|
_para_id,
|
|
_occupied_core_assumption,
|
|
tx,
|
|
),
|
|
))) => {
|
|
overseer_requested_validation_data.lock().await.push(hash);
|
|
tx.send(Ok(None)).unwrap();
|
|
},
|
|
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
_hash,
|
|
RuntimeApiRequest::Validators(tx),
|
|
))) => {
|
|
tx.send(Ok(vec![dummy_validator(); 3])).unwrap();
|
|
},
|
|
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
_hash,
|
|
RuntimeApiRequest::AsyncBackingParams(tx),
|
|
))) => {
|
|
tx.send(Err(RuntimeApiError::NotSupported {
|
|
runtime_api_name: "doesnt_matter",
|
|
}))
|
|
.unwrap();
|
|
},
|
|
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
_hash,
|
|
RuntimeApiRequest::Version(tx),
|
|
))) => {
|
|
tx.send(Ok(runtime_version)).unwrap();
|
|
},
|
|
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
_hash,
|
|
RuntimeApiRequest::ClaimQueue(tx),
|
|
))) if runtime_version >= RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT => {
|
|
tx.send(Ok(BTreeMap::new())).unwrap();
|
|
},
|
|
Some(msg) => {
|
|
panic!("didn't expect any other overseer requests; got {:?}", msg)
|
|
},
|
|
}
|
|
}
|
|
};
|
|
|
|
subsystem_test_harness(overseer, |mut ctx| async move {
|
|
handle_new_activations(
|
|
Arc::new(test_config(16)),
|
|
activated_hashes,
|
|
&mut ctx,
|
|
Metrics(None),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
});
|
|
|
|
let requested_validation_data = Arc::try_unwrap(requested_validation_data)
|
|
.expect("overseer should have shut down by now")
|
|
.into_inner();
|
|
|
|
// the only activated hash should be from the 4 hash:
|
|
// each activated hash generates two scheduled cores: one with its value * 4, one with its value
|
|
// * 5 given that the test configuration has a `para_id` of 16, there's only one way to get that
|
|
// value: with the 4 hash.
|
|
assert_eq!(requested_validation_data, vec![[4; 32].into()]);
|
|
}
|
|
|
|
#[rstest]
|
|
#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT - 1)]
|
|
#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)]
|
|
fn sends_distribute_collation_message(#[case] runtime_version: u32) {
|
|
let activated_hashes: Vec<Hash> = vec![
|
|
Hash::repeat_byte(1),
|
|
Hash::repeat_byte(4),
|
|
Hash::repeat_byte(9),
|
|
Hash::repeat_byte(16),
|
|
];
|
|
|
|
// empty vec doesn't allocate on the heap, so it's ok we throw it away
|
|
let to_collator_protocol = Arc::new(Mutex::new(Vec::new()));
|
|
let inner_to_collator_protocol = to_collator_protocol.clone();
|
|
|
|
let overseer = |mut handle: TestSubsystemContextHandle<CollationGenerationMessage>| async move {
|
|
loop {
|
|
match handle.try_recv().await {
|
|
None => break,
|
|
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
hash,
|
|
RuntimeApiRequest::AvailabilityCores(tx),
|
|
))) => {
|
|
tx.send(Ok(vec![
|
|
CoreState::Free,
|
|
// this is weird, see explanation below
|
|
CoreState::Scheduled(scheduled_core_for(
|
|
(hash.as_fixed_bytes()[0] * 4) as u32,
|
|
)),
|
|
CoreState::Scheduled(scheduled_core_for(
|
|
(hash.as_fixed_bytes()[0] * 5) as u32,
|
|
)),
|
|
]))
|
|
.unwrap();
|
|
},
|
|
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
_hash,
|
|
RuntimeApiRequest::PersistedValidationData(
|
|
_para_id,
|
|
_occupied_core_assumption,
|
|
tx,
|
|
),
|
|
))) => {
|
|
tx.send(Ok(Some(test_validation_data()))).unwrap();
|
|
},
|
|
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
_hash,
|
|
RuntimeApiRequest::Validators(tx),
|
|
))) => {
|
|
tx.send(Ok(vec![dummy_validator(); 3])).unwrap();
|
|
},
|
|
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
_hash,
|
|
RuntimeApiRequest::ValidationCodeHash(
|
|
_para_id,
|
|
OccupiedCoreAssumption::Free,
|
|
tx,
|
|
),
|
|
))) => {
|
|
tx.send(Ok(Some(ValidationCode(vec![1, 2, 3]).hash()))).unwrap();
|
|
},
|
|
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
_hash,
|
|
RuntimeApiRequest::AsyncBackingParams(tx),
|
|
))) => {
|
|
tx.send(Err(RuntimeApiError::NotSupported {
|
|
runtime_api_name: "doesnt_matter",
|
|
}))
|
|
.unwrap();
|
|
},
|
|
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
_hash,
|
|
RuntimeApiRequest::Version(tx),
|
|
))) => {
|
|
tx.send(Ok(runtime_version)).unwrap();
|
|
},
|
|
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
_hash,
|
|
RuntimeApiRequest::ClaimQueue(tx),
|
|
))) if runtime_version >= RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT => {
|
|
tx.send(Ok(BTreeMap::new())).unwrap();
|
|
},
|
|
Some(msg @ AllMessages::CollatorProtocol(_)) => {
|
|
inner_to_collator_protocol.lock().await.push(msg);
|
|
},
|
|
Some(msg) => {
|
|
panic!("didn't expect any other overseer requests; got {:?}", msg)
|
|
},
|
|
}
|
|
}
|
|
};
|
|
|
|
let config = Arc::new(test_config(16));
|
|
let subsystem_config = config.clone();
|
|
|
|
subsystem_test_harness(overseer, |mut ctx| async move {
|
|
handle_new_activations(subsystem_config, activated_hashes, &mut ctx, Metrics(None))
|
|
.await
|
|
.unwrap();
|
|
});
|
|
|
|
let mut to_collator_protocol = Arc::try_unwrap(to_collator_protocol)
|
|
.expect("subsystem should have shut down by now")
|
|
.into_inner();
|
|
|
|
// we expect a single message to be sent, containing a candidate receipt.
|
|
// we don't care too much about the `commitments_hash` right now, but let's ensure that we've
|
|
// calculated the correct descriptor
|
|
let expect_pov_hash = test_collation_compressed().proof_of_validity.into_compressed().hash();
|
|
let expect_validation_data_hash = test_validation_data().hash();
|
|
let expect_relay_parent = Hash::repeat_byte(4);
|
|
let expect_validation_code_hash = ValidationCode(vec![1, 2, 3]).hash();
|
|
let expect_payload = collator_signature_payload(
|
|
&expect_relay_parent,
|
|
&config.para_id,
|
|
&expect_validation_data_hash,
|
|
&expect_pov_hash,
|
|
&expect_validation_code_hash,
|
|
);
|
|
let expect_descriptor = CandidateDescriptor {
|
|
signature: config.key.sign(&expect_payload),
|
|
para_id: config.para_id,
|
|
relay_parent: expect_relay_parent,
|
|
collator: config.key.public(),
|
|
persisted_validation_data_hash: expect_validation_data_hash,
|
|
pov_hash: expect_pov_hash,
|
|
erasure_root: dummy_hash(), // this isn't something we're checking right now
|
|
para_head: test_collation().head_data.hash(),
|
|
validation_code_hash: expect_validation_code_hash,
|
|
};
|
|
|
|
assert_eq!(to_collator_protocol.len(), 1);
|
|
match AllMessages::from(to_collator_protocol.pop().unwrap()) {
|
|
AllMessages::CollatorProtocol(CollatorProtocolMessage::DistributeCollation {
|
|
candidate_receipt,
|
|
..
|
|
}) => {
|
|
let CandidateReceipt { descriptor, .. } = candidate_receipt;
|
|
// signature generation is non-deterministic, so we can't just assert that the
|
|
// expected descriptor is correct. What we can do is validate that the produced
|
|
// descriptor has a valid signature, then just copy in the generated signature
|
|
// and check the rest of the fields for equality.
|
|
assert!(CollatorPair::verify(
|
|
&descriptor.signature,
|
|
&collator_signature_payload(
|
|
&descriptor.relay_parent,
|
|
&descriptor.para_id,
|
|
&descriptor.persisted_validation_data_hash,
|
|
&descriptor.pov_hash,
|
|
&descriptor.validation_code_hash,
|
|
)
|
|
.as_ref(),
|
|
&descriptor.collator,
|
|
));
|
|
let expect_descriptor = {
|
|
let mut expect_descriptor = expect_descriptor;
|
|
expect_descriptor.signature = descriptor.signature.clone();
|
|
expect_descriptor.erasure_root = descriptor.erasure_root;
|
|
expect_descriptor
|
|
};
|
|
assert_eq!(descriptor, expect_descriptor);
|
|
},
|
|
_ => panic!("received wrong message type"),
|
|
}
|
|
}
|
|
|
|
#[rstest]
|
|
#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT - 1)]
|
|
#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)]
|
|
fn fallback_when_no_validation_code_hash_api(#[case] runtime_version: u32) {
|
|
// This is a variant of the above test, but with the validation code hash API disabled.
|
|
|
|
let activated_hashes: Vec<Hash> = vec![
|
|
Hash::repeat_byte(1),
|
|
Hash::repeat_byte(4),
|
|
Hash::repeat_byte(9),
|
|
Hash::repeat_byte(16),
|
|
];
|
|
|
|
// empty vec doesn't allocate on the heap, so it's ok we throw it away
|
|
let to_collator_protocol = Arc::new(Mutex::new(Vec::new()));
|
|
let inner_to_collator_protocol = to_collator_protocol.clone();
|
|
|
|
let overseer = |mut handle: TestSubsystemContextHandle<CollationGenerationMessage>| async move {
|
|
loop {
|
|
match handle.try_recv().await {
|
|
None => break,
|
|
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
hash,
|
|
RuntimeApiRequest::AvailabilityCores(tx),
|
|
))) => {
|
|
tx.send(Ok(vec![
|
|
CoreState::Free,
|
|
CoreState::Scheduled(scheduled_core_for(
|
|
(hash.as_fixed_bytes()[0] * 4) as u32,
|
|
)),
|
|
CoreState::Scheduled(scheduled_core_for(
|
|
(hash.as_fixed_bytes()[0] * 5) as u32,
|
|
)),
|
|
]))
|
|
.unwrap();
|
|
},
|
|
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
_hash,
|
|
RuntimeApiRequest::PersistedValidationData(
|
|
_para_id,
|
|
_occupied_core_assumption,
|
|
tx,
|
|
),
|
|
))) => {
|
|
tx.send(Ok(Some(test_validation_data()))).unwrap();
|
|
},
|
|
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
_hash,
|
|
RuntimeApiRequest::Validators(tx),
|
|
))) => {
|
|
tx.send(Ok(vec![dummy_validator(); 3])).unwrap();
|
|
},
|
|
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
_hash,
|
|
RuntimeApiRequest::ValidationCodeHash(
|
|
_para_id,
|
|
OccupiedCoreAssumption::Free,
|
|
tx,
|
|
),
|
|
))) => {
|
|
tx.send(Err(RuntimeApiError::NotSupported {
|
|
runtime_api_name: "validation_code_hash",
|
|
}))
|
|
.unwrap();
|
|
},
|
|
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
_hash,
|
|
RuntimeApiRequest::ValidationCode(_para_id, OccupiedCoreAssumption::Free, tx),
|
|
))) => {
|
|
tx.send(Ok(Some(ValidationCode(vec![1, 2, 3])))).unwrap();
|
|
},
|
|
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
_hash,
|
|
RuntimeApiRequest::AsyncBackingParams(tx),
|
|
))) => {
|
|
tx.send(Err(RuntimeApiError::NotSupported {
|
|
runtime_api_name: "doesnt_matter",
|
|
}))
|
|
.unwrap();
|
|
},
|
|
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
_hash,
|
|
RuntimeApiRequest::Version(tx),
|
|
))) => {
|
|
tx.send(Ok(runtime_version)).unwrap();
|
|
},
|
|
Some(msg @ AllMessages::CollatorProtocol(_)) => {
|
|
inner_to_collator_protocol.lock().await.push(msg);
|
|
},
|
|
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
_hash,
|
|
RuntimeApiRequest::ClaimQueue(tx),
|
|
))) if runtime_version >= RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT => {
|
|
let res = BTreeMap::<CoreIndex, VecDeque<ParaId>>::new();
|
|
tx.send(Ok(res)).unwrap();
|
|
},
|
|
Some(msg) => {
|
|
panic!("didn't expect any other overseer requests; got {:?}", msg)
|
|
},
|
|
}
|
|
}
|
|
};
|
|
|
|
let config = Arc::new(test_config(16u32));
|
|
let subsystem_config = config.clone();
|
|
|
|
// empty vec doesn't allocate on the heap, so it's ok we throw it away
|
|
subsystem_test_harness(overseer, |mut ctx| async move {
|
|
handle_new_activations(subsystem_config, activated_hashes, &mut ctx, Metrics(None))
|
|
.await
|
|
.unwrap();
|
|
});
|
|
|
|
let to_collator_protocol = Arc::try_unwrap(to_collator_protocol)
|
|
.expect("subsystem should have shut down by now")
|
|
.into_inner();
|
|
|
|
let expect_validation_code_hash = ValidationCode(vec![1, 2, 3]).hash();
|
|
|
|
assert_eq!(to_collator_protocol.len(), 1);
|
|
match &to_collator_protocol[0] {
|
|
AllMessages::CollatorProtocol(CollatorProtocolMessage::DistributeCollation {
|
|
candidate_receipt,
|
|
..
|
|
}) => {
|
|
let CandidateReceipt { descriptor, .. } = candidate_receipt;
|
|
assert_eq!(expect_validation_code_hash, descriptor.validation_code_hash);
|
|
},
|
|
_ => panic!("received wrong message type"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn submit_collation_is_no_op_before_initialization() {
|
|
test_harness(|mut virtual_overseer| async move {
|
|
virtual_overseer
|
|
.send(FromOrchestra::Communication {
|
|
msg: CollationGenerationMessage::SubmitCollation(SubmitCollationParams {
|
|
relay_parent: Hash::repeat_byte(0),
|
|
collation: test_collation(),
|
|
parent_head: vec![1, 2, 3].into(),
|
|
validation_code_hash: Hash::repeat_byte(1).into(),
|
|
result_sender: None,
|
|
}),
|
|
})
|
|
.await;
|
|
|
|
virtual_overseer
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn submit_collation_leads_to_distribution() {
|
|
let relay_parent = Hash::repeat_byte(0);
|
|
let validation_code_hash = ValidationCodeHash::from(Hash::repeat_byte(42));
|
|
let parent_head = HeadData::from(vec![1, 2, 3]);
|
|
let para_id = ParaId::from(5);
|
|
let expected_pvd = PersistedValidationData {
|
|
parent_head: parent_head.clone(),
|
|
relay_parent_number: 10,
|
|
relay_parent_storage_root: Hash::repeat_byte(1),
|
|
max_pov_size: 1024,
|
|
};
|
|
|
|
test_harness(|mut virtual_overseer| async move {
|
|
virtual_overseer
|
|
.send(FromOrchestra::Communication {
|
|
msg: CollationGenerationMessage::Initialize(test_config_no_collator(para_id)),
|
|
})
|
|
.await;
|
|
|
|
virtual_overseer
|
|
.send(FromOrchestra::Communication {
|
|
msg: CollationGenerationMessage::SubmitCollation(SubmitCollationParams {
|
|
relay_parent,
|
|
collation: test_collation(),
|
|
parent_head: vec![1, 2, 3].into(),
|
|
validation_code_hash,
|
|
result_sender: None,
|
|
}),
|
|
})
|
|
.await;
|
|
|
|
assert_matches!(
|
|
overseer_recv(&mut virtual_overseer).await,
|
|
AllMessages::RuntimeApi(RuntimeApiMessage::Request(rp, RuntimeApiRequest::Validators(tx))) => {
|
|
assert_eq!(rp, relay_parent);
|
|
let _ = tx.send(Ok(vec![
|
|
Sr25519Keyring::Alice.public().into(),
|
|
Sr25519Keyring::Bob.public().into(),
|
|
Sr25519Keyring::Charlie.public().into(),
|
|
]));
|
|
}
|
|
);
|
|
|
|
assert_matches!(
|
|
overseer_recv(&mut virtual_overseer).await,
|
|
AllMessages::RuntimeApi(RuntimeApiMessage::Request(rp, RuntimeApiRequest::PersistedValidationData(id, a, tx))) => {
|
|
assert_eq!(rp, relay_parent);
|
|
assert_eq!(id, para_id);
|
|
assert_eq!(a, OccupiedCoreAssumption::TimedOut);
|
|
|
|
// Candidate receipt should be constructed with the real parent head.
|
|
let mut pvd = expected_pvd.clone();
|
|
pvd.parent_head = vec![4, 5, 6].into();
|
|
let _ = tx.send(Ok(Some(pvd)));
|
|
}
|
|
);
|
|
|
|
assert_matches!(
|
|
overseer_recv(&mut virtual_overseer).await,
|
|
AllMessages::CollatorProtocol(CollatorProtocolMessage::DistributeCollation {
|
|
candidate_receipt,
|
|
parent_head_data_hash,
|
|
..
|
|
}) => {
|
|
let CandidateReceipt { descriptor, .. } = candidate_receipt;
|
|
assert_eq!(parent_head_data_hash, parent_head.hash());
|
|
assert_eq!(descriptor.persisted_validation_data_hash, expected_pvd.hash());
|
|
assert_eq!(descriptor.para_head, dummy_head_data().hash());
|
|
assert_eq!(descriptor.validation_code_hash, validation_code_hash);
|
|
}
|
|
);
|
|
|
|
virtual_overseer
|
|
});
|
|
}
|
|
|
|
// There is one core in `Occupied` state and async backing is enabled. On new head activation
|
|
// `CollationGeneration` should produce and distribute a new collation.
|
|
#[rstest]
|
|
#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT - 1)]
|
|
#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)]
|
|
fn distribute_collation_for_occupied_core_with_async_backing_enabled(#[case] runtime_version: u32) {
|
|
let activated_hash: Hash = [1; 32].into();
|
|
let para_id = ParaId::from(5);
|
|
|
|
// One core, in occupied state. The data in `CoreState` and `ClaimQueue` should match.
|
|
let cores: Vec<CoreState> = vec![CoreState::Occupied(polkadot_primitives::OccupiedCore {
|
|
next_up_on_available: Some(ScheduledCore { para_id, collator: None }),
|
|
occupied_since: 1,
|
|
time_out_at: 10,
|
|
next_up_on_time_out: Some(ScheduledCore { para_id, collator: None }),
|
|
availability: Default::default(), // doesn't matter
|
|
group_responsible: polkadot_primitives::GroupIndex(0),
|
|
candidate_hash: Default::default(),
|
|
candidate_descriptor: dummy_candidate_descriptor(dummy_hash()),
|
|
})];
|
|
let claim_queue = BTreeMap::from([(CoreIndex::from(0), VecDeque::from([para_id]))]);
|
|
|
|
test_harness(|mut virtual_overseer| async move {
|
|
helpers::initialize_collator(&mut virtual_overseer, para_id).await;
|
|
helpers::activate_new_head(&mut virtual_overseer, activated_hash).await;
|
|
helpers::handle_runtime_calls_on_new_head_activation(
|
|
&mut virtual_overseer,
|
|
activated_hash,
|
|
AsyncBackingParams { max_candidate_depth: 1, allowed_ancestry_len: 1 },
|
|
cores,
|
|
runtime_version,
|
|
claim_queue,
|
|
)
|
|
.await;
|
|
helpers::handle_core_processing_for_a_leaf(
|
|
&mut virtual_overseer,
|
|
activated_hash,
|
|
para_id,
|
|
// `CoreState` is `Occupied` => `OccupiedCoreAssumption` is `Included`
|
|
OccupiedCoreAssumption::Included,
|
|
)
|
|
.await;
|
|
|
|
virtual_overseer
|
|
});
|
|
}
|
|
|
|
// There is one core in `Occupied` state and async backing is disabled. On new head activation
|
|
// no new collation should be generated.
|
|
#[rstest]
|
|
#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT - 1)]
|
|
#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)]
|
|
fn no_collation_is_distributed_for_occupied_core_with_async_backing_disabled(
|
|
#[case] runtime_version: u32,
|
|
) {
|
|
let activated_hash: Hash = [1; 32].into();
|
|
let para_id = ParaId::from(5);
|
|
|
|
// One core, in occupied state. The data in `CoreState` and `ClaimQueue` should match.
|
|
let cores: Vec<CoreState> = vec![CoreState::Occupied(polkadot_primitives::OccupiedCore {
|
|
next_up_on_available: Some(ScheduledCore { para_id, collator: None }),
|
|
occupied_since: 1,
|
|
time_out_at: 10,
|
|
next_up_on_time_out: Some(ScheduledCore { para_id, collator: None }),
|
|
availability: Default::default(), // doesn't matter
|
|
group_responsible: polkadot_primitives::GroupIndex(0),
|
|
candidate_hash: Default::default(),
|
|
candidate_descriptor: dummy_candidate_descriptor(dummy_hash()),
|
|
})];
|
|
let claim_queue = BTreeMap::from([(CoreIndex::from(0), VecDeque::from([para_id]))]);
|
|
|
|
test_harness(|mut virtual_overseer| async move {
|
|
helpers::initialize_collator(&mut virtual_overseer, para_id).await;
|
|
helpers::activate_new_head(&mut virtual_overseer, activated_hash).await;
|
|
helpers::handle_runtime_calls_on_new_head_activation(
|
|
&mut virtual_overseer,
|
|
activated_hash,
|
|
AsyncBackingParams { max_candidate_depth: 0, allowed_ancestry_len: 0 },
|
|
cores,
|
|
runtime_version,
|
|
claim_queue,
|
|
)
|
|
.await;
|
|
|
|
virtual_overseer
|
|
});
|
|
}
|
|
|
|
mod helpers {
|
|
use super::*;
|
|
|
|
// Sends `Initialize` with a collator config
|
|
pub async fn initialize_collator(virtual_overseer: &mut VirtualOverseer, para_id: ParaId) {
|
|
virtual_overseer
|
|
.send(FromOrchestra::Communication {
|
|
msg: CollationGenerationMessage::Initialize(test_config(para_id)),
|
|
})
|
|
.await;
|
|
}
|
|
|
|
// Sends `ActiveLeaves` for a single leaf with the specified hash. Block number is hardcoded.
|
|
pub async fn activate_new_head(virtual_overseer: &mut VirtualOverseer, activated_hash: Hash) {
|
|
virtual_overseer
|
|
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate {
|
|
activated: Some(ActivatedLeaf {
|
|
hash: activated_hash,
|
|
number: 10,
|
|
unpin_handle: polkadot_node_subsystem_test_helpers::mock::dummy_unpin_handle(
|
|
activated_hash,
|
|
),
|
|
span: Arc::new(overseer::jaeger::Span::Disabled),
|
|
}),
|
|
..Default::default()
|
|
})))
|
|
.await;
|
|
}
|
|
|
|
// Handle all runtime calls performed in `handle_new_activations`. Conditionally expects a
|
|
// `CLAIM_QUEUE_RUNTIME_REQUIREMENT` call if the passed `runtime_version` is greater or equal to
|
|
// `CLAIM_QUEUE_RUNTIME_REQUIREMENT`
|
|
pub async fn handle_runtime_calls_on_new_head_activation(
|
|
virtual_overseer: &mut VirtualOverseer,
|
|
activated_hash: Hash,
|
|
async_backing_params: AsyncBackingParams,
|
|
cores: Vec<CoreState>,
|
|
runtime_version: u32,
|
|
claim_queue: BTreeMap<CoreIndex, VecDeque<Id>>,
|
|
) {
|
|
assert_matches!(
|
|
overseer_recv(virtual_overseer).await,
|
|
AllMessages::RuntimeApi(RuntimeApiMessage::Request(hash, RuntimeApiRequest::AvailabilityCores(tx))) => {
|
|
assert_eq!(hash, activated_hash);
|
|
let _ = tx.send(Ok(cores));
|
|
}
|
|
);
|
|
|
|
assert_matches!(
|
|
overseer_recv(virtual_overseer).await,
|
|
AllMessages::RuntimeApi(RuntimeApiMessage::Request(hash, RuntimeApiRequest::Validators(tx))) => {
|
|
assert_eq!(hash, activated_hash);
|
|
let _ = tx.send(Ok(vec![
|
|
Sr25519Keyring::Alice.public().into(),
|
|
Sr25519Keyring::Bob.public().into(),
|
|
Sr25519Keyring::Charlie.public().into(),
|
|
]));
|
|
}
|
|
);
|
|
|
|
assert_matches!(
|
|
overseer_recv(virtual_overseer).await,
|
|
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
hash,
|
|
RuntimeApiRequest::AsyncBackingParams(
|
|
tx,
|
|
),
|
|
)) => {
|
|
assert_eq!(hash, activated_hash);
|
|
let _ = tx.send(Ok(async_backing_params));
|
|
}
|
|
);
|
|
|
|
assert_matches!(
|
|
overseer_recv(virtual_overseer).await,
|
|
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
hash,
|
|
RuntimeApiRequest::Version(tx),
|
|
)) => {
|
|
assert_eq!(hash, activated_hash);
|
|
let _ = tx.send(Ok(runtime_version));
|
|
}
|
|
);
|
|
|
|
if runtime_version == RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT {
|
|
assert_matches!(
|
|
overseer_recv(virtual_overseer).await,
|
|
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
hash,
|
|
RuntimeApiRequest::ClaimQueue(tx),
|
|
)) => {
|
|
assert_eq!(hash, activated_hash);
|
|
let _ = tx.send(Ok(claim_queue));
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
// Handles all runtime requests performed in `handle_new_activations` for the case when a
|
|
// collation should be prepared for the new leaf
|
|
pub async fn handle_core_processing_for_a_leaf(
|
|
virtual_overseer: &mut VirtualOverseer,
|
|
activated_hash: Hash,
|
|
para_id: ParaId,
|
|
expected_occupied_core_assumption: OccupiedCoreAssumption,
|
|
) {
|
|
// Some hardcoded data - if needed, extract to parameters
|
|
let validation_code_hash = ValidationCodeHash::from(Hash::repeat_byte(42));
|
|
let parent_head = HeadData::from(vec![1, 2, 3]);
|
|
let pvd = PersistedValidationData {
|
|
parent_head: parent_head.clone(),
|
|
relay_parent_number: 10,
|
|
relay_parent_storage_root: Hash::repeat_byte(1),
|
|
max_pov_size: 1024,
|
|
};
|
|
|
|
assert_matches!(
|
|
overseer_recv(virtual_overseer).await,
|
|
AllMessages::RuntimeApi(RuntimeApiMessage::Request(hash, RuntimeApiRequest::PersistedValidationData(id, a, tx))) => {
|
|
assert_eq!(hash, activated_hash);
|
|
assert_eq!(id, para_id);
|
|
assert_eq!(a, expected_occupied_core_assumption);
|
|
|
|
let _ = tx.send(Ok(Some(pvd.clone())));
|
|
}
|
|
);
|
|
|
|
assert_matches!(
|
|
overseer_recv(virtual_overseer).await,
|
|
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
hash,
|
|
RuntimeApiRequest::ValidationCodeHash(
|
|
id,
|
|
assumption,
|
|
tx,
|
|
),
|
|
)) => {
|
|
assert_eq!(hash, activated_hash);
|
|
assert_eq!(id, para_id);
|
|
assert_eq!(assumption, expected_occupied_core_assumption);
|
|
|
|
let _ = tx.send(Ok(Some(validation_code_hash)));
|
|
}
|
|
);
|
|
|
|
assert_matches!(
|
|
overseer_recv(virtual_overseer).await,
|
|
AllMessages::CollatorProtocol(CollatorProtocolMessage::DistributeCollation{
|
|
candidate_receipt,
|
|
parent_head_data_hash,
|
|
..
|
|
}) => {
|
|
assert_eq!(parent_head_data_hash, parent_head.hash());
|
|
assert_eq!(candidate_receipt.descriptor().persisted_validation_data_hash, pvd.hash());
|
|
assert_eq!(candidate_receipt.descriptor().para_head, dummy_head_data().hash());
|
|
assert_eq!(candidate_receipt.descriptor().validation_code_hash, validation_code_hash);
|
|
}
|
|
);
|
|
}
|
|
}
|