feat: initialize Kurdistan SDK - independent fork of Polkadot SDK
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi 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.
|
||||
|
||||
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Helper functions and tools to generate mock data useful for testing this subsystem.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use sp_keyring::Sr25519Keyring;
|
||||
|
||||
use pezkuwi_erasure_coding::{branches, obtain_chunks_v1 as obtain_chunks};
|
||||
use pezkuwi_node_primitives::{AvailableData, BlockData, ErasureChunk, PoV, Proof};
|
||||
use pezkuwi_primitives::{
|
||||
CandidateCommitments, CandidateHash, ChunkIndex, CommittedCandidateReceiptV2, GroupIndex, Hash,
|
||||
HeadData, Id as ParaId, IndexedVec, OccupiedCore, PersistedValidationData, SessionInfo,
|
||||
ValidatorIndex,
|
||||
};
|
||||
use pezkuwi_primitives_test_helpers::{
|
||||
dummy_collator, dummy_collator_signature, dummy_hash, dummy_validation_code,
|
||||
CandidateDescriptor, CommittedCandidateReceipt,
|
||||
};
|
||||
|
||||
/// Create dummy session info with two validator groups.
|
||||
pub fn make_session_info() -> SessionInfo {
|
||||
let validators = vec![
|
||||
Sr25519Keyring::Ferdie, // <- this node, role: validator
|
||||
Sr25519Keyring::Alice,
|
||||
Sr25519Keyring::Bob,
|
||||
Sr25519Keyring::Charlie,
|
||||
Sr25519Keyring::Dave,
|
||||
Sr25519Keyring::Eve,
|
||||
Sr25519Keyring::One,
|
||||
];
|
||||
|
||||
let validator_groups: IndexedVec<GroupIndex, Vec<ValidatorIndex>> =
|
||||
[vec![5, 0, 3], vec![1, 6, 2, 4]]
|
||||
.iter()
|
||||
.map(|g| g.into_iter().map(|v| ValidatorIndex(*v)).collect())
|
||||
.collect();
|
||||
|
||||
SessionInfo {
|
||||
discovery_keys: validators.iter().map(|k| k.public().into()).collect(),
|
||||
// Not used:
|
||||
n_cores: validator_groups.len() as u32,
|
||||
validator_groups,
|
||||
// Not used values:
|
||||
validators: validators.iter().map(|k| k.public().into()).collect(),
|
||||
assignment_keys: Vec::new(),
|
||||
zeroth_delay_tranche_width: 0,
|
||||
relay_vrf_modulo_samples: 0,
|
||||
n_delay_tranches: 0,
|
||||
no_show_slots: 0,
|
||||
needed_approvals: 0,
|
||||
active_validator_indices: Vec::new(),
|
||||
dispute_period: 6,
|
||||
random_seed: [0u8; 32],
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for constructing occupied cores.
|
||||
///
|
||||
/// Takes all the values we care about and fills the rest with dummy values on `build`.
|
||||
pub struct OccupiedCoreBuilder {
|
||||
pub group_responsible: GroupIndex,
|
||||
pub para_id: ParaId,
|
||||
pub relay_parent: Hash,
|
||||
pub n_validators: usize,
|
||||
pub chunk_index: ChunkIndex,
|
||||
}
|
||||
|
||||
impl OccupiedCoreBuilder {
|
||||
pub fn build(self) -> (OccupiedCore, (CandidateHash, ErasureChunk)) {
|
||||
let pov = PoV { block_data: BlockData(vec![45, 46, 47]) };
|
||||
let pov_hash = pov.hash();
|
||||
let (erasure_root, chunk) =
|
||||
get_valid_chunk_data(pov.clone(), self.n_validators, self.chunk_index);
|
||||
let candidate_receipt = TestCandidateBuilder {
|
||||
para_id: self.para_id,
|
||||
pov_hash,
|
||||
relay_parent: self.relay_parent,
|
||||
erasure_root,
|
||||
..Default::default()
|
||||
}
|
||||
.build();
|
||||
let core = OccupiedCore {
|
||||
next_up_on_available: None,
|
||||
occupied_since: 0,
|
||||
time_out_at: 0,
|
||||
next_up_on_time_out: None,
|
||||
availability: Default::default(),
|
||||
group_responsible: self.group_responsible,
|
||||
candidate_hash: candidate_receipt.hash(),
|
||||
candidate_descriptor: candidate_receipt.descriptor.clone(),
|
||||
};
|
||||
(core, (candidate_receipt.hash(), chunk))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TestCandidateBuilder {
|
||||
para_id: ParaId,
|
||||
head_data: HeadData,
|
||||
pov_hash: Hash,
|
||||
relay_parent: Hash,
|
||||
erasure_root: Hash,
|
||||
}
|
||||
|
||||
impl TestCandidateBuilder {
|
||||
pub fn build(self) -> CommittedCandidateReceiptV2 {
|
||||
CommittedCandidateReceipt {
|
||||
descriptor: CandidateDescriptor {
|
||||
para_id: self.para_id,
|
||||
pov_hash: self.pov_hash,
|
||||
relay_parent: self.relay_parent,
|
||||
erasure_root: self.erasure_root,
|
||||
collator: dummy_collator(),
|
||||
persisted_validation_data_hash: dummy_hash(),
|
||||
signature: dummy_collator_signature(),
|
||||
para_head: dummy_hash(),
|
||||
validation_code_hash: dummy_validation_code().hash(),
|
||||
},
|
||||
commitments: CandidateCommitments { head_data: self.head_data, ..Default::default() },
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
// Get chunk for index 0
|
||||
pub fn get_valid_chunk_data(
|
||||
pov: PoV,
|
||||
n_validators: usize,
|
||||
chunk_index: ChunkIndex,
|
||||
) -> (Hash, ErasureChunk) {
|
||||
let persisted = PersistedValidationData {
|
||||
parent_head: HeadData(vec![7, 8, 9]),
|
||||
relay_parent_number: Default::default(),
|
||||
max_pov_size: 1024,
|
||||
relay_parent_storage_root: Default::default(),
|
||||
};
|
||||
let available_data = AvailableData { validation_data: persisted, pov: Arc::new(pov) };
|
||||
let chunks = obtain_chunks(n_validators, &available_data).unwrap();
|
||||
let branches = branches(chunks.as_ref());
|
||||
let root = branches.root();
|
||||
let chunk = branches
|
||||
.enumerate()
|
||||
.map(|(index, (proof, chunk))| ErasureChunk {
|
||||
chunk: chunk.to_vec(),
|
||||
index: ChunkIndex(index as _),
|
||||
proof: Proof::try_from(proof).unwrap(),
|
||||
})
|
||||
.nth(chunk_index.0 as usize)
|
||||
.expect("There really should be enough chunks.");
|
||||
(root, chunk)
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi 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.
|
||||
|
||||
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use futures::{executor, future, Future};
|
||||
use rstest::rstest;
|
||||
|
||||
use pezkuwi_node_network_protocol::request_response::{
|
||||
IncomingRequest, Protocol, ReqProtocolNames,
|
||||
};
|
||||
use pezkuwi_primitives::{node_features, Block, CoreState, Hash, NodeFeatures};
|
||||
use sp_keystore::KeystorePtr;
|
||||
|
||||
use super::*;
|
||||
|
||||
mod state;
|
||||
/// State for test harnesses.
|
||||
use state::{TestHarness, TestState};
|
||||
|
||||
/// Mock data useful for testing.
|
||||
pub(crate) mod mock;
|
||||
|
||||
fn test_harness<T: Future<Output = ()>>(
|
||||
keystore: KeystorePtr,
|
||||
req_protocol_names: ReqProtocolNames,
|
||||
test_fx: impl FnOnce(TestHarness) -> T,
|
||||
) -> std::result::Result<(), FatalError> {
|
||||
sp_tracing::init_for_tests();
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (context, virtual_overseer) =
|
||||
pezkuwi_node_subsystem_test_helpers::make_subsystem_context(pool.clone());
|
||||
|
||||
let (pov_req_receiver, _pov_req_cfg) = IncomingRequest::get_config_receiver::<
|
||||
Block,
|
||||
sc_network::NetworkWorker<Block, Hash>,
|
||||
>(&req_protocol_names);
|
||||
let (chunk_req_v1_receiver, chunk_req_v1_cfg) = IncomingRequest::get_config_receiver::<
|
||||
Block,
|
||||
sc_network::NetworkWorker<Block, Hash>,
|
||||
>(&req_protocol_names);
|
||||
let (chunk_req_v2_receiver, chunk_req_v2_cfg) = IncomingRequest::get_config_receiver::<
|
||||
Block,
|
||||
sc_network::NetworkWorker<Block, Hash>,
|
||||
>(&req_protocol_names);
|
||||
let subsystem = AvailabilityDistributionSubsystem::new(
|
||||
keystore,
|
||||
IncomingRequestReceivers { pov_req_receiver, chunk_req_v1_receiver, chunk_req_v2_receiver },
|
||||
req_protocol_names,
|
||||
Default::default(),
|
||||
);
|
||||
let subsystem = subsystem.run(context);
|
||||
|
||||
let test_fut =
|
||||
test_fx(TestHarness { virtual_overseer, chunk_req_v1_cfg, chunk_req_v2_cfg, pool });
|
||||
|
||||
futures::pin_mut!(test_fut);
|
||||
futures::pin_mut!(subsystem);
|
||||
|
||||
executor::block_on(future::join(test_fut, subsystem)).1
|
||||
}
|
||||
|
||||
pub fn node_features_with_mapping_enabled() -> NodeFeatures {
|
||||
let mut node_features = NodeFeatures::new();
|
||||
node_features.resize(node_features::FeatureIndex::AvailabilityChunkMapping as usize + 1, false);
|
||||
node_features.set(node_features::FeatureIndex::AvailabilityChunkMapping as u8 as usize, true);
|
||||
node_features
|
||||
}
|
||||
|
||||
/// Simple basic check, whether the subsystem works as expected.
|
||||
///
|
||||
/// Exceptional cases are tested as unit tests in `fetch_task`.
|
||||
#[rstest]
|
||||
#[case(NodeFeatures::EMPTY, Protocol::ChunkFetchingV1)]
|
||||
#[case(NodeFeatures::EMPTY, Protocol::ChunkFetchingV2)]
|
||||
#[case(node_features_with_mapping_enabled(), Protocol::ChunkFetchingV1)]
|
||||
#[case(node_features_with_mapping_enabled(), Protocol::ChunkFetchingV2)]
|
||||
fn check_basic(#[case] node_features: NodeFeatures, #[case] chunk_resp_protocol: Protocol) {
|
||||
let req_protocol_names = ReqProtocolNames::new(&Hash::repeat_byte(0xff), None);
|
||||
let state =
|
||||
TestState::new(node_features.clone(), req_protocol_names.clone(), chunk_resp_protocol);
|
||||
|
||||
if node_features == node_features_with_mapping_enabled() &&
|
||||
chunk_resp_protocol == Protocol::ChunkFetchingV1
|
||||
{
|
||||
// For this specific case, chunk fetching is not possible, because the ValidatorIndex is not
|
||||
// equal to the ChunkIndex and the peer does not send back the actual ChunkIndex.
|
||||
let _ = test_harness(state.keystore.clone(), req_protocol_names, move |harness| {
|
||||
state.run_assert_timeout(harness)
|
||||
});
|
||||
} else {
|
||||
test_harness(state.keystore.clone(), req_protocol_names, move |harness| state.run(harness))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether requester tries all validators in group.
|
||||
#[rstest]
|
||||
#[case(NodeFeatures::EMPTY, Protocol::ChunkFetchingV1)]
|
||||
#[case(NodeFeatures::EMPTY, Protocol::ChunkFetchingV2)]
|
||||
#[case(node_features_with_mapping_enabled(), Protocol::ChunkFetchingV1)]
|
||||
#[case(node_features_with_mapping_enabled(), Protocol::ChunkFetchingV2)]
|
||||
fn check_fetch_tries_all(
|
||||
#[case] node_features: NodeFeatures,
|
||||
#[case] chunk_resp_protocol: Protocol,
|
||||
) {
|
||||
let req_protocol_names = ReqProtocolNames::new(&Hash::repeat_byte(0xff), None);
|
||||
let mut state =
|
||||
TestState::new(node_features.clone(), req_protocol_names.clone(), chunk_resp_protocol);
|
||||
for (_, v) in state.chunks.iter_mut() {
|
||||
// 4 validators in group, so this should still succeed:
|
||||
v.push(None);
|
||||
v.push(None);
|
||||
v.push(None);
|
||||
}
|
||||
|
||||
if node_features == node_features_with_mapping_enabled() &&
|
||||
chunk_resp_protocol == Protocol::ChunkFetchingV1
|
||||
{
|
||||
// For this specific case, chunk fetching is not possible, because the ValidatorIndex is not
|
||||
// equal to the ChunkIndex and the peer does not send back the actual ChunkIndex.
|
||||
let _ = test_harness(state.keystore.clone(), req_protocol_names, move |harness| {
|
||||
state.run_assert_timeout(harness)
|
||||
});
|
||||
} else {
|
||||
test_harness(state.keystore.clone(), req_protocol_names, move |harness| state.run(harness))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether requester tries all validators in group
|
||||
///
|
||||
/// Check that requester will retry the fetch on error on the next block still pending
|
||||
/// availability.
|
||||
#[rstest]
|
||||
#[case(NodeFeatures::EMPTY, Protocol::ChunkFetchingV1)]
|
||||
#[case(NodeFeatures::EMPTY, Protocol::ChunkFetchingV2)]
|
||||
#[case(node_features_with_mapping_enabled(), Protocol::ChunkFetchingV1)]
|
||||
#[case(node_features_with_mapping_enabled(), Protocol::ChunkFetchingV2)]
|
||||
fn check_fetch_retry(#[case] node_features: NodeFeatures, #[case] chunk_resp_protocol: Protocol) {
|
||||
let req_protocol_names = ReqProtocolNames::new(&Hash::repeat_byte(0xff), None);
|
||||
let mut state =
|
||||
TestState::new(node_features.clone(), req_protocol_names.clone(), chunk_resp_protocol);
|
||||
state
|
||||
.cores
|
||||
.insert(state.relay_chain[2], state.cores.get(&state.relay_chain[1]).unwrap().clone());
|
||||
// We only care about the first three blocks.
|
||||
// 1. scheduled
|
||||
// 2. occupied
|
||||
// 3. still occupied
|
||||
state.relay_chain.truncate(3);
|
||||
|
||||
// Get rid of unused valid chunks:
|
||||
let valid_candidate_hashes: HashSet<_> = state
|
||||
.cores
|
||||
.get(&state.relay_chain[1])
|
||||
.iter()
|
||||
.flat_map(|v| v.iter())
|
||||
.filter_map(|c| match c {
|
||||
CoreState::Occupied(core) => Some(core.candidate_hash),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
state.valid_chunks.retain(|(ch, _)| valid_candidate_hashes.contains(ch));
|
||||
|
||||
for (_, v) in state.chunks.iter_mut() {
|
||||
// This should still succeed as cores are still pending availability on next block.
|
||||
v.push(None);
|
||||
v.push(None);
|
||||
v.push(None);
|
||||
v.push(None);
|
||||
v.push(None);
|
||||
}
|
||||
|
||||
if node_features == node_features_with_mapping_enabled() &&
|
||||
chunk_resp_protocol == Protocol::ChunkFetchingV1
|
||||
{
|
||||
// For this specific case, chunk fetching is not possible, because the ValidatorIndex is not
|
||||
// equal to the ChunkIndex and the peer does not send back the actual ChunkIndex.
|
||||
let _ = test_harness(state.keystore.clone(), req_protocol_names, move |harness| {
|
||||
state.run_assert_timeout(harness)
|
||||
});
|
||||
} else {
|
||||
test_harness(state.keystore.clone(), req_protocol_names, move |harness| state.run(harness))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,450 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi 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.
|
||||
|
||||
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use network::{request_responses::OutgoingResponse, ProtocolName, RequestFailure};
|
||||
use pezkuwi_node_subsystem_test_helpers::TestSubsystemContextHandle;
|
||||
use pezkuwi_node_subsystem_util::{availability_chunks::availability_chunk_index, TimeoutExt};
|
||||
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
FutureExt, SinkExt, StreamExt,
|
||||
};
|
||||
use futures_timer::Delay;
|
||||
|
||||
use sc_network as network;
|
||||
use sc_network::{config as netconfig, config::RequestResponseConfig, IfDisconnected};
|
||||
use sp_core::{testing::TaskExecutor, traits::SpawnNamed};
|
||||
use sp_keystore::KeystorePtr;
|
||||
|
||||
use pezkuwi_node_network_protocol::request_response::{
|
||||
v1, v2, IncomingRequest, OutgoingRequest, Protocol, ReqProtocolNames, Requests,
|
||||
};
|
||||
use pezkuwi_node_primitives::ErasureChunk;
|
||||
use pezkuwi_node_subsystem::{
|
||||
messages::{
|
||||
AllMessages, AvailabilityDistributionMessage, AvailabilityStoreMessage, ChainApiMessage,
|
||||
NetworkBridgeTxMessage, RuntimeApiMessage, RuntimeApiRequest,
|
||||
},
|
||||
ActiveLeavesUpdate, FromOrchestra, OverseerSignal,
|
||||
};
|
||||
use pezkuwi_node_subsystem_test_helpers as test_helpers;
|
||||
use pezkuwi_primitives::{
|
||||
CandidateHash, ChunkIndex, CoreIndex, CoreState, ExecutorParams, GroupIndex, Hash,
|
||||
Id as ParaId, NodeFeatures, ScheduledCore, SessionInfo, ValidatorIndex,
|
||||
};
|
||||
use test_helpers::mock::{make_ferdie_keystore, new_leaf};
|
||||
|
||||
use super::mock::{make_session_info, OccupiedCoreBuilder};
|
||||
use crate::LOG_TARGET;
|
||||
|
||||
type VirtualOverseer = pezkuwi_node_subsystem_test_helpers::TestSubsystemContextHandle<
|
||||
AvailabilityDistributionMessage,
|
||||
>;
|
||||
pub struct TestHarness {
|
||||
pub virtual_overseer: VirtualOverseer,
|
||||
pub chunk_req_v1_cfg: RequestResponseConfig,
|
||||
pub chunk_req_v2_cfg: RequestResponseConfig,
|
||||
pub pool: TaskExecutor,
|
||||
}
|
||||
|
||||
/// `TestState` for mocking execution of this subsystem.
|
||||
///
|
||||
/// The `Default` instance provides data, which makes the system succeed by providing a couple of
|
||||
/// valid occupied cores. You can tune the data before calling `TestState::run`. E.g. modify some
|
||||
/// chunks to be invalid, the test will then still pass if you remove that chunk from
|
||||
/// `valid_chunks`.
|
||||
#[derive(Clone)]
|
||||
pub struct TestState {
|
||||
/// Simulated relay chain heads:
|
||||
pub relay_chain: Vec<Hash>,
|
||||
/// Whenever the subsystem tries to fetch an erasure chunk one item of the given vec will be
|
||||
/// popped. So you can experiment with serving invalid chunks or no chunks on request and see
|
||||
/// whether the subsystem still succeeds with its goal.
|
||||
pub chunks: HashMap<(CandidateHash, ValidatorIndex), Vec<Option<ErasureChunk>>>,
|
||||
/// All chunks that are valid and should be accepted.
|
||||
pub valid_chunks: HashSet<(CandidateHash, ValidatorIndex)>,
|
||||
pub session_info: SessionInfo,
|
||||
/// Cores per relay chain block.
|
||||
pub cores: HashMap<Hash, Vec<CoreState>>,
|
||||
pub keystore: KeystorePtr,
|
||||
pub node_features: NodeFeatures,
|
||||
pub chunk_response_protocol: Protocol,
|
||||
pub req_protocol_names: ReqProtocolNames,
|
||||
pub our_chunk_index: ChunkIndex,
|
||||
}
|
||||
|
||||
impl TestState {
|
||||
/// Initialize a default test state.
|
||||
pub fn new(
|
||||
node_features: NodeFeatures,
|
||||
req_protocol_names: ReqProtocolNames,
|
||||
chunk_response_protocol: Protocol,
|
||||
) -> Self {
|
||||
let relay_chain: Vec<_> = (1u8..10).map(Hash::repeat_byte).collect();
|
||||
let chain_a = ParaId::from(1);
|
||||
let chain_b = ParaId::from(2);
|
||||
|
||||
let chain_ids = vec![chain_a, chain_b];
|
||||
|
||||
let keystore = make_ferdie_keystore();
|
||||
|
||||
let session_info = make_session_info();
|
||||
|
||||
let our_chunk_index = availability_chunk_index(
|
||||
&node_features,
|
||||
session_info.validators.len(),
|
||||
CoreIndex(1),
|
||||
ValidatorIndex(0),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let (cores, chunks) = {
|
||||
let mut cores = HashMap::new();
|
||||
let mut chunks = HashMap::new();
|
||||
|
||||
cores.insert(
|
||||
relay_chain[0],
|
||||
vec![
|
||||
CoreState::Scheduled(ScheduledCore { para_id: chain_ids[0], collator: None }),
|
||||
CoreState::Scheduled(ScheduledCore { para_id: chain_ids[1], collator: None }),
|
||||
],
|
||||
);
|
||||
|
||||
let heads = {
|
||||
let mut advanced = relay_chain.iter();
|
||||
advanced.next();
|
||||
relay_chain.iter().zip(advanced)
|
||||
};
|
||||
for (relay_parent, relay_child) in heads {
|
||||
let (p_cores, p_chunks): (Vec<_>, Vec<_>) = chain_ids
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, para_id)| {
|
||||
let (core, chunk) = OccupiedCoreBuilder {
|
||||
group_responsible: GroupIndex(i as _),
|
||||
para_id: *para_id,
|
||||
relay_parent: *relay_parent,
|
||||
n_validators: session_info.validators.len(),
|
||||
chunk_index: our_chunk_index,
|
||||
}
|
||||
.build();
|
||||
(CoreState::Occupied(core), chunk)
|
||||
})
|
||||
.unzip();
|
||||
cores.insert(*relay_child, p_cores);
|
||||
// Skip chunks for our own group (won't get fetched):
|
||||
let mut chunks_other_groups = p_chunks.into_iter();
|
||||
chunks_other_groups.next();
|
||||
for (candidate, chunk) in chunks_other_groups {
|
||||
chunks.insert((candidate, ValidatorIndex(0)), vec![Some(chunk)]);
|
||||
}
|
||||
}
|
||||
(cores, chunks)
|
||||
};
|
||||
Self {
|
||||
relay_chain,
|
||||
valid_chunks: chunks.clone().keys().map(Clone::clone).collect(),
|
||||
chunks,
|
||||
session_info,
|
||||
cores,
|
||||
keystore,
|
||||
node_features,
|
||||
chunk_response_protocol,
|
||||
req_protocol_names,
|
||||
our_chunk_index,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run, but fail after some timeout.
|
||||
pub async fn run(self, harness: TestHarness) {
|
||||
// Make sure test won't run forever.
|
||||
let f = self.run_inner(harness).timeout(Duration::from_secs(5));
|
||||
assert!(f.await.is_some(), "Test ran into timeout");
|
||||
}
|
||||
|
||||
/// Run, and assert an expected timeout.
|
||||
pub async fn run_assert_timeout(self, harness: TestHarness) {
|
||||
// Make sure test won't run forever.
|
||||
let f = self.run_inner(harness).timeout(Duration::from_secs(5));
|
||||
assert!(f.await.is_none(), "Test should have run into timeout");
|
||||
}
|
||||
|
||||
/// Run tests with the given mock values in `TestState`.
|
||||
///
|
||||
/// This will simply advance through the simulated chain and examines whether the subsystem
|
||||
/// behaves as expected: It will succeed if all valid chunks of other backing groups get stored
|
||||
/// and no other.
|
||||
///
|
||||
/// We try to be as agnostic about details as possible, how the subsystem achieves those goals
|
||||
/// should not be a matter to this test suite.
|
||||
async fn run_inner(mut self, mut harness: TestHarness) {
|
||||
// We skip genesis here (in reality ActiveLeavesUpdate can also skip a block):
|
||||
let updates = {
|
||||
let mut advanced = self.relay_chain.iter();
|
||||
advanced.next();
|
||||
self.relay_chain
|
||||
.iter()
|
||||
.zip(advanced)
|
||||
.map(|(old, new)| ActiveLeavesUpdate {
|
||||
activated: Some(new_leaf(*new, 1)),
|
||||
deactivated: vec![*old].into(),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
// We should be storing all valid chunks during execution:
|
||||
//
|
||||
// Test will fail if this does not happen until timeout.
|
||||
let mut remaining_stores = self.valid_chunks.len();
|
||||
|
||||
let TestSubsystemContextHandle { tx, mut rx, .. } = harness.virtual_overseer;
|
||||
|
||||
// Spawning necessary as incoming queue can only hold a single item, we don't want to dead
|
||||
// lock ;-)
|
||||
let update_tx = tx.clone();
|
||||
harness.pool.spawn(
|
||||
"sending-active-leaves-updates",
|
||||
None,
|
||||
async move {
|
||||
for update in updates {
|
||||
overseer_signal(update_tx.clone(), OverseerSignal::ActiveLeaves(update)).await;
|
||||
// We need to give the subsystem a little time to do its job, otherwise it will
|
||||
// cancel jobs as obsolete:
|
||||
Delay::new(Duration::from_millis(100)).await;
|
||||
}
|
||||
}
|
||||
.boxed(),
|
||||
);
|
||||
|
||||
while remaining_stores > 0 {
|
||||
gum::trace!(target: LOG_TARGET, remaining_stores, "Stores left to go");
|
||||
let msg = overseer_recv(&mut rx).await;
|
||||
match msg {
|
||||
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendRequests(
|
||||
reqs,
|
||||
IfDisconnected::ImmediateError,
|
||||
)) => {
|
||||
for req in reqs {
|
||||
// Forward requests:
|
||||
match self.chunk_response_protocol {
|
||||
Protocol::ChunkFetchingV1 => {
|
||||
let in_req = to_incoming_req_v1(
|
||||
&harness.pool,
|
||||
req,
|
||||
self.req_protocol_names.get_name(Protocol::ChunkFetchingV1),
|
||||
);
|
||||
|
||||
harness
|
||||
.chunk_req_v1_cfg
|
||||
.inbound_queue
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.send(in_req.into_raw())
|
||||
.await
|
||||
.unwrap();
|
||||
},
|
||||
Protocol::ChunkFetchingV2 => {
|
||||
let in_req = to_incoming_req_v2(
|
||||
&harness.pool,
|
||||
req,
|
||||
self.req_protocol_names.get_name(Protocol::ChunkFetchingV2),
|
||||
);
|
||||
|
||||
harness
|
||||
.chunk_req_v2_cfg
|
||||
.inbound_queue
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.send(in_req.into_raw())
|
||||
.await
|
||||
.unwrap();
|
||||
},
|
||||
_ => panic!("Unexpected protocol"),
|
||||
}
|
||||
}
|
||||
},
|
||||
AllMessages::AvailabilityStore(AvailabilityStoreMessage::QueryChunk(
|
||||
candidate_hash,
|
||||
validator_index,
|
||||
tx,
|
||||
)) => {
|
||||
let chunk = self
|
||||
.chunks
|
||||
.get_mut(&(candidate_hash, validator_index))
|
||||
.and_then(Vec::pop)
|
||||
.flatten();
|
||||
tx.send(chunk).expect("Receiver is expected to be alive");
|
||||
},
|
||||
AllMessages::AvailabilityStore(AvailabilityStoreMessage::StoreChunk {
|
||||
candidate_hash,
|
||||
chunk,
|
||||
validator_index,
|
||||
tx,
|
||||
..
|
||||
}) => {
|
||||
assert!(
|
||||
self.valid_chunks.contains(&(candidate_hash, validator_index)),
|
||||
"Only valid chunks should ever get stored."
|
||||
);
|
||||
assert_eq!(self.our_chunk_index, chunk.index);
|
||||
|
||||
tx.send(Ok(())).expect("Receiver is expected to be alive");
|
||||
gum::trace!(target: LOG_TARGET, "'Stored' fetched chunk.");
|
||||
remaining_stores -= 1;
|
||||
},
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(hash, req)) => {
|
||||
match req {
|
||||
RuntimeApiRequest::SessionIndexForChild(tx) => {
|
||||
// Always session index 1 for now:
|
||||
tx.send(Ok(1)).expect("Receiver should still be alive");
|
||||
},
|
||||
RuntimeApiRequest::SessionInfo(_, tx) => {
|
||||
tx.send(Ok(Some(self.session_info.clone())))
|
||||
.expect("Receiver should be alive.");
|
||||
},
|
||||
RuntimeApiRequest::SessionExecutorParams(_, tx) => {
|
||||
tx.send(Ok(Some(ExecutorParams::default())))
|
||||
.expect("Receiver should be alive.");
|
||||
},
|
||||
RuntimeApiRequest::AvailabilityCores(tx) => {
|
||||
gum::trace!(target: LOG_TARGET, cores= ?self.cores[&hash], hash = ?hash, "Sending out cores for hash");
|
||||
tx.send(Ok(self.cores[&hash].clone()))
|
||||
.expect("Receiver should still be alive");
|
||||
},
|
||||
RuntimeApiRequest::NodeFeatures(_, tx) => {
|
||||
tx.send(Ok(self.node_features.clone()))
|
||||
.expect("Receiver should still be alive");
|
||||
},
|
||||
_ => {
|
||||
panic!("Unexpected runtime request: {:?}", req);
|
||||
},
|
||||
}
|
||||
},
|
||||
AllMessages::ChainApi(ChainApiMessage::Ancestors { hash, k, response_channel }) => {
|
||||
let chain = &self.relay_chain;
|
||||
let maybe_block_position = chain.iter().position(|h| *h == hash);
|
||||
let ancestors = maybe_block_position
|
||||
.map(|idx| chain[..idx].iter().rev().take(k).copied().collect())
|
||||
.unwrap_or_default();
|
||||
response_channel.send(Ok(ancestors)).expect("Receiver is expected to be alive");
|
||||
},
|
||||
|
||||
_ => {
|
||||
panic!("Received unexpected message")
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
overseer_signal(tx, OverseerSignal::Conclude).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn overseer_signal(
|
||||
mut tx: mpsc::Sender<FromOrchestra<AvailabilityDistributionMessage>>,
|
||||
msg: impl Into<OverseerSignal>,
|
||||
) {
|
||||
let msg = msg.into();
|
||||
gum::trace!(target: LOG_TARGET, msg = ?msg, "sending message");
|
||||
tx.send(FromOrchestra::Signal(msg))
|
||||
.await
|
||||
.expect("Test subsystem no longer live");
|
||||
}
|
||||
|
||||
async fn overseer_recv(rx: &mut mpsc::UnboundedReceiver<AllMessages>) -> AllMessages {
|
||||
gum::trace!(target: LOG_TARGET, "waiting for message ...");
|
||||
rx.next().await.expect("Test subsystem no longer live")
|
||||
}
|
||||
|
||||
fn to_incoming_req_v1(
|
||||
executor: &TaskExecutor,
|
||||
outgoing: Requests,
|
||||
protocol_name: ProtocolName,
|
||||
) -> IncomingRequest<v1::ChunkFetchingRequest> {
|
||||
match outgoing {
|
||||
Requests::ChunkFetching(OutgoingRequest {
|
||||
pending_response,
|
||||
fallback_request: Some((fallback_request, fallback_protocol)),
|
||||
..
|
||||
}) => {
|
||||
assert_eq!(fallback_protocol, Protocol::ChunkFetchingV1);
|
||||
|
||||
let tx = spawn_message_forwarding(executor, protocol_name, pending_response);
|
||||
|
||||
IncomingRequest::new(
|
||||
// We don't really care:
|
||||
network::PeerId::random().into(),
|
||||
fallback_request,
|
||||
tx,
|
||||
)
|
||||
},
|
||||
_ => panic!("Unexpected request!"),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_incoming_req_v2(
|
||||
executor: &TaskExecutor,
|
||||
outgoing: Requests,
|
||||
protocol_name: ProtocolName,
|
||||
) -> IncomingRequest<v2::ChunkFetchingRequest> {
|
||||
match outgoing {
|
||||
Requests::ChunkFetching(OutgoingRequest {
|
||||
payload,
|
||||
pending_response,
|
||||
fallback_request: Some((_, fallback_protocol)),
|
||||
..
|
||||
}) => {
|
||||
assert_eq!(fallback_protocol, Protocol::ChunkFetchingV1);
|
||||
|
||||
let tx = spawn_message_forwarding(executor, protocol_name, pending_response);
|
||||
|
||||
IncomingRequest::new(
|
||||
// We don't really care:
|
||||
network::PeerId::random().into(),
|
||||
payload,
|
||||
tx,
|
||||
)
|
||||
},
|
||||
_ => panic!("Unexpected request!"),
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_message_forwarding(
|
||||
executor: &TaskExecutor,
|
||||
protocol_name: ProtocolName,
|
||||
pending_response: oneshot::Sender<Result<(Vec<u8>, ProtocolName), RequestFailure>>,
|
||||
) -> oneshot::Sender<OutgoingResponse> {
|
||||
let (tx, rx): (oneshot::Sender<netconfig::OutgoingResponse>, oneshot::Receiver<_>) =
|
||||
oneshot::channel();
|
||||
executor.spawn(
|
||||
"message-forwarding",
|
||||
None,
|
||||
async {
|
||||
let response = rx.await;
|
||||
let payload = response.expect("Unexpected canceled request").result;
|
||||
pending_response
|
||||
.send(payload.map_err(|_| RequestFailure::Refused).map(|r| (r, protocol_name)))
|
||||
.expect("Sending response is expected to work");
|
||||
}
|
||||
.boxed(),
|
||||
);
|
||||
|
||||
tx
|
||||
}
|
||||
Reference in New Issue
Block a user