feat: initialize Kurdistan SDK - independent fork of Polkadot SDK

This commit is contained in:
2025-12-13 15:44:15 +03:00
commit 286de54384
6841 changed files with 1848356 additions and 0 deletions
@@ -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
}