mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-25 08:15:44 +00:00
subsystem-bench: add regression tests for availability read and write (#3311)
### What's been done - `subsystem-bench` has been split into two parts: a cli benchmark runner and a library. - The cli runner is quite simple. It just allows us to run `.yaml` based test sequences. Now it should only be used to run benchmarks during development. - The library is used in the cli runner and in regression tests. Some code is changed to make the library independent of the runner. - Added first regression tests for availability read and write that replicate existing test sequences. ### How we run regression tests - Regression tests are simply rust integration tests without the harnesses. - They should only be compiled under the `subsystem-benchmarks` feature to prevent them from running with other tests. - This doesn't work when running tests with `nextest` in CI, so additional filters have been added to the `nextest` runs. - Each benchmark run takes a different time in the beginning, so we "warm up" the tests until their CPU usage differs by only 1%. - After the warm-up, we run the benchmarks a few more times and compare the average with the exception using a precision. ### What is still wrong? - I haven't managed to set up approval voting tests. The spread of their results is too large and can't be narrowed down in a reasonable amount of time in the warm-up phase. - The tests start an unconfigurable prometheus endpoint inside, which causes errors because they use the same 9999 port. I disable it with a flag, but I think it's better to extract the endpoint launching outside the test, as we already do with `valgrind` and `pyroscope`. But we still use `prometheus` inside the tests. ### Future work * https://github.com/paritytech/polkadot-sdk/issues/3528 * https://github.com/paritytech/polkadot-sdk/issues/3529 * https://github.com/paritytech/polkadot-sdk/issues/3530 * https://github.com/paritytech/polkadot-sdk/issues/3531 --------- Co-authored-by: Alexander Samusev <41779041+alvicsam@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
// 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/>.
|
||||
|
||||
//! A generic av store subsystem mockup suitable to be used in benchmarks.
|
||||
|
||||
use crate::network::{HandleNetworkMessage, NetworkMessage};
|
||||
use futures::{channel::oneshot, FutureExt};
|
||||
use parity_scale_codec::Encode;
|
||||
use polkadot_node_network_protocol::request_response::{
|
||||
v1::{AvailableDataFetchingResponse, ChunkFetchingResponse, ChunkResponse},
|
||||
Requests,
|
||||
};
|
||||
use polkadot_node_primitives::{AvailableData, ErasureChunk};
|
||||
use polkadot_node_subsystem::{
|
||||
messages::AvailabilityStoreMessage, overseer, SpawnedSubsystem, SubsystemError,
|
||||
};
|
||||
use polkadot_node_subsystem_types::OverseerSignal;
|
||||
use polkadot_primitives::CandidateHash;
|
||||
use sc_network::ProtocolName;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct AvailabilityStoreState {
|
||||
candidate_hashes: HashMap<CandidateHash, usize>,
|
||||
chunks: Vec<Vec<ErasureChunk>>,
|
||||
}
|
||||
|
||||
const LOG_TARGET: &str = "subsystem-bench::av-store-mock";
|
||||
|
||||
/// Mockup helper. Contains Ccunks and full availability data of all parachain blocks
|
||||
/// used in a test.
|
||||
pub struct NetworkAvailabilityState {
|
||||
pub candidate_hashes: HashMap<CandidateHash, usize>,
|
||||
pub available_data: Vec<AvailableData>,
|
||||
pub chunks: Vec<Vec<ErasureChunk>>,
|
||||
}
|
||||
|
||||
// Implement access to the state.
|
||||
impl HandleNetworkMessage for NetworkAvailabilityState {
|
||||
fn handle(
|
||||
&self,
|
||||
message: NetworkMessage,
|
||||
_node_sender: &mut futures::channel::mpsc::UnboundedSender<NetworkMessage>,
|
||||
) -> Option<NetworkMessage> {
|
||||
match message {
|
||||
NetworkMessage::RequestFromNode(peer, request) => match request {
|
||||
Requests::ChunkFetchingV1(outgoing_request) => {
|
||||
gum::debug!(target: LOG_TARGET, request = ?outgoing_request, "Received `RequestFromNode`");
|
||||
let validator_index: usize = outgoing_request.payload.index.0 as usize;
|
||||
let candidate_hash = outgoing_request.payload.candidate_hash;
|
||||
|
||||
let candidate_index = self
|
||||
.candidate_hashes
|
||||
.get(&candidate_hash)
|
||||
.expect("candidate was generated previously; qed");
|
||||
gum::warn!(target: LOG_TARGET, ?candidate_hash, candidate_index, "Candidate mapped to index");
|
||||
|
||||
let chunk: ChunkResponse =
|
||||
self.chunks.get(*candidate_index).unwrap()[validator_index].clone().into();
|
||||
let response = Ok((
|
||||
ChunkFetchingResponse::from(Some(chunk)).encode(),
|
||||
ProtocolName::Static("dummy"),
|
||||
));
|
||||
|
||||
if let Err(err) = outgoing_request.pending_response.send(response) {
|
||||
gum::error!(target: LOG_TARGET, ?err, "Failed to send `ChunkFetchingResponse`");
|
||||
}
|
||||
|
||||
None
|
||||
},
|
||||
Requests::AvailableDataFetchingV1(outgoing_request) => {
|
||||
let candidate_hash = outgoing_request.payload.candidate_hash;
|
||||
let candidate_index = self
|
||||
.candidate_hashes
|
||||
.get(&candidate_hash)
|
||||
.expect("candidate was generated previously; qed");
|
||||
gum::debug!(target: LOG_TARGET, ?candidate_hash, candidate_index, "Candidate mapped to index");
|
||||
|
||||
let available_data = self.available_data.get(*candidate_index).unwrap().clone();
|
||||
|
||||
let response = Ok((
|
||||
AvailableDataFetchingResponse::from(Some(available_data)).encode(),
|
||||
ProtocolName::Static("dummy"),
|
||||
));
|
||||
outgoing_request
|
||||
.pending_response
|
||||
.send(response)
|
||||
.expect("Response is always sent succesfully");
|
||||
None
|
||||
},
|
||||
_ => Some(NetworkMessage::RequestFromNode(peer, request)),
|
||||
},
|
||||
|
||||
message => Some(message),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A mock of the availability store subsystem. This one also generates all the
|
||||
/// candidates that a
|
||||
pub struct MockAvailabilityStore {
|
||||
state: AvailabilityStoreState,
|
||||
}
|
||||
|
||||
impl MockAvailabilityStore {
|
||||
pub fn new(
|
||||
chunks: Vec<Vec<ErasureChunk>>,
|
||||
candidate_hashes: HashMap<CandidateHash, usize>,
|
||||
) -> MockAvailabilityStore {
|
||||
Self { state: AvailabilityStoreState { chunks, candidate_hashes } }
|
||||
}
|
||||
|
||||
async fn respond_to_query_all_request(
|
||||
&self,
|
||||
candidate_hash: CandidateHash,
|
||||
send_chunk: impl Fn(usize) -> bool,
|
||||
tx: oneshot::Sender<Vec<ErasureChunk>>,
|
||||
) {
|
||||
let candidate_index = self
|
||||
.state
|
||||
.candidate_hashes
|
||||
.get(&candidate_hash)
|
||||
.expect("candidate was generated previously; qed");
|
||||
gum::debug!(target: LOG_TARGET, ?candidate_hash, candidate_index, "Candidate mapped to index");
|
||||
|
||||
let v = self
|
||||
.state
|
||||
.chunks
|
||||
.get(*candidate_index)
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|c| send_chunk(c.index.0 as usize))
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let _ = tx.send(v);
|
||||
}
|
||||
}
|
||||
|
||||
#[overseer::subsystem(AvailabilityStore, error=SubsystemError, prefix=self::overseer)]
|
||||
impl<Context> MockAvailabilityStore {
|
||||
fn start(self, ctx: Context) -> SpawnedSubsystem {
|
||||
let future = self.run(ctx).map(|_| Ok(())).boxed();
|
||||
|
||||
SpawnedSubsystem { name: "test-environment", future }
|
||||
}
|
||||
}
|
||||
|
||||
#[overseer::contextbounds(AvailabilityStore, prefix = self::overseer)]
|
||||
impl MockAvailabilityStore {
|
||||
async fn run<Context>(self, mut ctx: Context) {
|
||||
gum::debug!(target: LOG_TARGET, "Subsystem running");
|
||||
loop {
|
||||
let msg = ctx.recv().await.expect("Overseer never fails us");
|
||||
|
||||
match msg {
|
||||
orchestra::FromOrchestra::Signal(signal) =>
|
||||
if signal == OverseerSignal::Conclude {
|
||||
return
|
||||
},
|
||||
orchestra::FromOrchestra::Communication { msg } => match msg {
|
||||
AvailabilityStoreMessage::QueryAvailableData(candidate_hash, tx) => {
|
||||
gum::debug!(target: LOG_TARGET, candidate_hash = ?candidate_hash, "Responding to QueryAvailableData");
|
||||
|
||||
// We never have the full available data.
|
||||
let _ = tx.send(None);
|
||||
},
|
||||
AvailabilityStoreMessage::QueryAllChunks(candidate_hash, tx) => {
|
||||
// We always have our own chunk.
|
||||
gum::debug!(target: LOG_TARGET, candidate_hash = ?candidate_hash, "Responding to QueryAllChunks");
|
||||
self.respond_to_query_all_request(candidate_hash, |index| index == 0, tx)
|
||||
.await;
|
||||
},
|
||||
AvailabilityStoreMessage::QueryChunkSize(candidate_hash, tx) => {
|
||||
gum::debug!(target: LOG_TARGET, candidate_hash = ?candidate_hash, "Responding to QueryChunkSize");
|
||||
|
||||
let candidate_index = self
|
||||
.state
|
||||
.candidate_hashes
|
||||
.get(&candidate_hash)
|
||||
.expect("candidate was generated previously; qed");
|
||||
gum::debug!(target: LOG_TARGET, ?candidate_hash, candidate_index, "Candidate mapped to index");
|
||||
|
||||
let chunk_size =
|
||||
self.state.chunks.get(*candidate_index).unwrap()[0].encoded_size();
|
||||
let _ = tx.send(Some(chunk_size));
|
||||
},
|
||||
AvailabilityStoreMessage::StoreChunk { candidate_hash, chunk, tx } => {
|
||||
gum::debug!(target: LOG_TARGET, chunk_index = ?chunk.index ,candidate_hash = ?candidate_hash, "Responding to StoreChunk");
|
||||
let _ = tx.send(Ok(()));
|
||||
},
|
||||
_ => {
|
||||
unimplemented!("Unexpected av-store message")
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
// 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/>.
|
||||
|
||||
//! A generic runtime api subsystem mockup suitable to be used in benchmarks.
|
||||
|
||||
use futures::FutureExt;
|
||||
use itertools::Itertools;
|
||||
use polkadot_node_subsystem::{
|
||||
messages::ChainApiMessage, overseer, SpawnedSubsystem, SubsystemError,
|
||||
};
|
||||
use polkadot_node_subsystem_types::OverseerSignal;
|
||||
use polkadot_primitives::Header;
|
||||
use sp_core::H256;
|
||||
use std::collections::HashMap;
|
||||
|
||||
const LOG_TARGET: &str = "subsystem-bench::chain-api-mock";
|
||||
|
||||
/// State used to respond to `BlockHeader` requests.
|
||||
pub struct ChainApiState {
|
||||
pub block_headers: HashMap<H256, Header>,
|
||||
}
|
||||
|
||||
pub struct MockChainApi {
|
||||
state: ChainApiState,
|
||||
}
|
||||
|
||||
impl ChainApiState {
|
||||
fn get_header_by_number(&self, requested_number: u32) -> Option<&Header> {
|
||||
self.block_headers.values().find(|header| header.number == requested_number)
|
||||
}
|
||||
}
|
||||
|
||||
impl MockChainApi {
|
||||
pub fn new(state: ChainApiState) -> MockChainApi {
|
||||
Self { state }
|
||||
}
|
||||
}
|
||||
|
||||
#[overseer::subsystem(ChainApi, error=SubsystemError, prefix=self::overseer)]
|
||||
impl<Context> MockChainApi {
|
||||
fn start(self, ctx: Context) -> SpawnedSubsystem {
|
||||
let future = self.run(ctx).map(|_| Ok(())).boxed();
|
||||
|
||||
SpawnedSubsystem { name: "test-environment", future }
|
||||
}
|
||||
}
|
||||
|
||||
#[overseer::contextbounds(ChainApi, prefix = self::overseer)]
|
||||
impl MockChainApi {
|
||||
async fn run<Context>(self, mut ctx: Context) {
|
||||
loop {
|
||||
let msg = ctx.recv().await.expect("Overseer never fails us");
|
||||
|
||||
match msg {
|
||||
orchestra::FromOrchestra::Signal(signal) =>
|
||||
if signal == OverseerSignal::Conclude {
|
||||
return
|
||||
},
|
||||
orchestra::FromOrchestra::Communication { msg } => {
|
||||
gum::debug!(target: LOG_TARGET, msg=?msg, "recv message");
|
||||
|
||||
match msg {
|
||||
ChainApiMessage::BlockHeader(hash, response_channel) => {
|
||||
let _ = response_channel.send(Ok(Some(
|
||||
self.state
|
||||
.block_headers
|
||||
.get(&hash)
|
||||
.cloned()
|
||||
.expect("Relay chain block hashes are known"),
|
||||
)));
|
||||
},
|
||||
ChainApiMessage::FinalizedBlockNumber(val) => {
|
||||
val.send(Ok(0)).unwrap();
|
||||
},
|
||||
ChainApiMessage::FinalizedBlockHash(requested_number, sender) => {
|
||||
let hash = self
|
||||
.state
|
||||
.get_header_by_number(requested_number)
|
||||
.expect("Unknow block number")
|
||||
.hash();
|
||||
sender.send(Ok(Some(hash))).unwrap();
|
||||
},
|
||||
ChainApiMessage::BlockNumber(requested_hash, sender) => {
|
||||
sender
|
||||
.send(Ok(Some(
|
||||
self.state
|
||||
.block_headers
|
||||
.get(&requested_hash)
|
||||
.expect("Unknown block hash")
|
||||
.number,
|
||||
)))
|
||||
.unwrap();
|
||||
},
|
||||
ChainApiMessage::Ancestors { hash, k: _, response_channel } => {
|
||||
let block_number = self
|
||||
.state
|
||||
.block_headers
|
||||
.get(&hash)
|
||||
.expect("Unknown block hash")
|
||||
.number;
|
||||
let ancestors = self
|
||||
.state
|
||||
.block_headers
|
||||
.iter()
|
||||
.filter(|(_, header)| header.number < block_number)
|
||||
.sorted_by(|a, b| a.1.number.cmp(&b.1.number))
|
||||
.map(|(hash, _)| *hash)
|
||||
.collect_vec();
|
||||
response_channel.send(Ok(ancestors)).unwrap();
|
||||
},
|
||||
_ => {
|
||||
unimplemented!("Unexpected chain-api message")
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// 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/>.
|
||||
|
||||
//! Dummy subsystem mocks.
|
||||
|
||||
use futures::FutureExt;
|
||||
use paste::paste;
|
||||
use polkadot_node_subsystem::{overseer, SpawnedSubsystem, SubsystemError};
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
|
||||
const LOG_TARGET: &str = "subsystem-bench::mockery";
|
||||
|
||||
macro_rules! mock {
|
||||
// Just query by relay parent
|
||||
($subsystem_name:ident) => {
|
||||
paste! {
|
||||
pub struct [<Mock $subsystem_name >] {}
|
||||
#[overseer::subsystem($subsystem_name, error=SubsystemError, prefix=self::overseer)]
|
||||
impl<Context> [<Mock $subsystem_name >] {
|
||||
fn start(self, ctx: Context) -> SpawnedSubsystem {
|
||||
let future = self.run(ctx).map(|_| Ok(())).boxed();
|
||||
|
||||
// The name will appear in substrate CPU task metrics as `task_group`.`
|
||||
SpawnedSubsystem { name: "test-environment", future }
|
||||
}
|
||||
}
|
||||
|
||||
#[overseer::contextbounds($subsystem_name, prefix = self::overseer)]
|
||||
impl [<Mock $subsystem_name >] {
|
||||
async fn run<Context>(self, mut ctx: Context) {
|
||||
let mut count_total_msg = 0;
|
||||
loop {
|
||||
futures::select!{
|
||||
msg = ctx.recv().fuse() => {
|
||||
match msg.unwrap() {
|
||||
orchestra::FromOrchestra::Signal(signal) => {
|
||||
match signal {
|
||||
polkadot_node_subsystem_types::OverseerSignal::Conclude => {return},
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
orchestra::FromOrchestra::Communication { msg } => {
|
||||
gum::debug!(target: LOG_TARGET, msg = ?msg, "mocked subsystem received message");
|
||||
}
|
||||
}
|
||||
|
||||
count_total_msg +=1;
|
||||
}
|
||||
_ = sleep(Duration::from_secs(6)).fuse() => {
|
||||
if count_total_msg > 0 {
|
||||
gum::trace!(target: LOG_TARGET, "Subsystem {} processed {} messages since last time", stringify!($subsystem_name), count_total_msg);
|
||||
}
|
||||
count_total_msg = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Generate dummy implementation for all subsystems
|
||||
mock!(AvailabilityStore);
|
||||
mock!(StatementDistribution);
|
||||
mock!(BitfieldSigning);
|
||||
mock!(BitfieldDistribution);
|
||||
mock!(Provisioner);
|
||||
mock!(NetworkBridgeRx);
|
||||
mock!(CollationGeneration);
|
||||
mock!(CollatorProtocol);
|
||||
mock!(GossipSupport);
|
||||
mock!(DisputeDistribution);
|
||||
mock!(DisputeCoordinator);
|
||||
mock!(ProspectiveParachains);
|
||||
mock!(PvfChecker);
|
||||
mock!(CandidateBacking);
|
||||
mock!(AvailabilityDistribution);
|
||||
mock!(CandidateValidation);
|
||||
mock!(AvailabilityRecovery);
|
||||
mock!(NetworkBridgeTx);
|
||||
mock!(ChainApi);
|
||||
mock!(ChainSelection);
|
||||
mock!(ApprovalVoting);
|
||||
mock!(ApprovalDistribution);
|
||||
mock!(RuntimeApi);
|
||||
@@ -0,0 +1,88 @@
|
||||
// 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 polkadot_node_subsystem::HeadSupportsParachains;
|
||||
use polkadot_node_subsystem_types::Hash;
|
||||
use sp_consensus::SyncOracle;
|
||||
|
||||
pub mod av_store;
|
||||
pub mod chain_api;
|
||||
pub mod dummy;
|
||||
pub mod network_bridge;
|
||||
pub mod runtime_api;
|
||||
|
||||
pub struct AlwaysSupportsParachains {}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl HeadSupportsParachains for AlwaysSupportsParachains {
|
||||
async fn head_supports_parachains(&self, _head: &Hash) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// An orchestra with dummy subsystems
|
||||
#[macro_export]
|
||||
macro_rules! dummy_builder {
|
||||
($spawn_task_handle: ident, $metrics: ident) => {{
|
||||
use $crate::mock::dummy::*;
|
||||
|
||||
// Initialize a mock overseer.
|
||||
// All subsystem except approval_voting and approval_distribution are mock subsystems.
|
||||
Overseer::builder()
|
||||
.approval_voting(MockApprovalVoting {})
|
||||
.approval_distribution(MockApprovalDistribution {})
|
||||
.availability_recovery(MockAvailabilityRecovery {})
|
||||
.candidate_validation(MockCandidateValidation {})
|
||||
.chain_api(MockChainApi {})
|
||||
.chain_selection(MockChainSelection {})
|
||||
.dispute_coordinator(MockDisputeCoordinator {})
|
||||
.runtime_api(MockRuntimeApi {})
|
||||
.network_bridge_tx(MockNetworkBridgeTx {})
|
||||
.availability_distribution(MockAvailabilityDistribution {})
|
||||
.availability_store(MockAvailabilityStore {})
|
||||
.pvf_checker(MockPvfChecker {})
|
||||
.candidate_backing(MockCandidateBacking {})
|
||||
.statement_distribution(MockStatementDistribution {})
|
||||
.bitfield_signing(MockBitfieldSigning {})
|
||||
.bitfield_distribution(MockBitfieldDistribution {})
|
||||
.provisioner(MockProvisioner {})
|
||||
.network_bridge_rx(MockNetworkBridgeRx {})
|
||||
.collation_generation(MockCollationGeneration {})
|
||||
.collator_protocol(MockCollatorProtocol {})
|
||||
.gossip_support(MockGossipSupport {})
|
||||
.dispute_distribution(MockDisputeDistribution {})
|
||||
.prospective_parachains(MockProspectiveParachains {})
|
||||
.activation_external_listeners(Default::default())
|
||||
.span_per_active_leaf(Default::default())
|
||||
.active_leaves(Default::default())
|
||||
.metrics($metrics)
|
||||
.supports_parachains(AlwaysSupportsParachains {})
|
||||
.spawner(SpawnGlue($spawn_task_handle))
|
||||
}};
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TestSyncOracle {}
|
||||
|
||||
impl SyncOracle for TestSyncOracle {
|
||||
fn is_major_syncing(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_offline(&self) -> bool {
|
||||
unimplemented!("not used by subsystem benchmarks")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
// 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/>.
|
||||
|
||||
//! Mocked `network-bridge` subsystems that uses a `NetworkInterface` to access
|
||||
//! the emulated network.
|
||||
|
||||
use crate::{
|
||||
configuration::TestAuthorities,
|
||||
network::{NetworkEmulatorHandle, NetworkInterfaceReceiver, NetworkMessage, RequestExt},
|
||||
};
|
||||
use futures::{channel::mpsc::UnboundedSender, FutureExt, StreamExt};
|
||||
use polkadot_node_network_protocol::Versioned;
|
||||
use polkadot_node_subsystem::{
|
||||
messages::NetworkBridgeTxMessage, overseer, SpawnedSubsystem, SubsystemError,
|
||||
};
|
||||
use polkadot_node_subsystem_types::{
|
||||
messages::{ApprovalDistributionMessage, BitfieldDistributionMessage, NetworkBridgeEvent},
|
||||
OverseerSignal,
|
||||
};
|
||||
use sc_network::{request_responses::ProtocolConfig, RequestFailure};
|
||||
|
||||
const LOG_TARGET: &str = "subsystem-bench::network-bridge";
|
||||
const CHUNK_REQ_PROTOCOL_NAME_V1: &str =
|
||||
"/ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff/req_chunk/1";
|
||||
|
||||
/// A mock of the network bridge tx subsystem.
|
||||
pub struct MockNetworkBridgeTx {
|
||||
/// A network emulator handle
|
||||
network: NetworkEmulatorHandle,
|
||||
/// A channel to the network interface,
|
||||
to_network_interface: UnboundedSender<NetworkMessage>,
|
||||
/// Test authorithies
|
||||
test_authorithies: TestAuthorities,
|
||||
}
|
||||
|
||||
/// A mock of the network bridge tx subsystem.
|
||||
pub struct MockNetworkBridgeRx {
|
||||
/// A network interface receiver
|
||||
network_receiver: NetworkInterfaceReceiver,
|
||||
/// Chunk request sender
|
||||
chunk_request_sender: Option<ProtocolConfig>,
|
||||
}
|
||||
|
||||
impl MockNetworkBridgeTx {
|
||||
pub fn new(
|
||||
network: NetworkEmulatorHandle,
|
||||
to_network_interface: UnboundedSender<NetworkMessage>,
|
||||
test_authorithies: TestAuthorities,
|
||||
) -> MockNetworkBridgeTx {
|
||||
Self { network, to_network_interface, test_authorithies }
|
||||
}
|
||||
}
|
||||
|
||||
impl MockNetworkBridgeRx {
|
||||
pub fn new(
|
||||
network_receiver: NetworkInterfaceReceiver,
|
||||
chunk_request_sender: Option<ProtocolConfig>,
|
||||
) -> MockNetworkBridgeRx {
|
||||
Self { network_receiver, chunk_request_sender }
|
||||
}
|
||||
}
|
||||
|
||||
#[overseer::subsystem(NetworkBridgeTx, error=SubsystemError, prefix=self::overseer)]
|
||||
impl<Context> MockNetworkBridgeTx {
|
||||
fn start(self, ctx: Context) -> SpawnedSubsystem {
|
||||
let future = self.run(ctx).map(|_| Ok(())).boxed();
|
||||
|
||||
SpawnedSubsystem { name: "network-bridge-tx", future }
|
||||
}
|
||||
}
|
||||
|
||||
#[overseer::subsystem(NetworkBridgeRx, error=SubsystemError, prefix=self::overseer)]
|
||||
impl<Context> MockNetworkBridgeRx {
|
||||
fn start(self, ctx: Context) -> SpawnedSubsystem {
|
||||
let future = self.run(ctx).map(|_| Ok(())).boxed();
|
||||
|
||||
SpawnedSubsystem { name: "network-bridge-rx", future }
|
||||
}
|
||||
}
|
||||
|
||||
#[overseer::contextbounds(NetworkBridgeTx, prefix = self::overseer)]
|
||||
impl MockNetworkBridgeTx {
|
||||
async fn run<Context>(self, mut ctx: Context) {
|
||||
// Main subsystem loop.
|
||||
loop {
|
||||
let subsystem_message = ctx.recv().await.expect("Overseer never fails us");
|
||||
match subsystem_message {
|
||||
orchestra::FromOrchestra::Signal(signal) =>
|
||||
if signal == OverseerSignal::Conclude {
|
||||
return
|
||||
},
|
||||
orchestra::FromOrchestra::Communication { msg } => match msg {
|
||||
NetworkBridgeTxMessage::SendRequests(requests, _if_disconnected) => {
|
||||
for request in requests {
|
||||
gum::debug!(target: LOG_TARGET, request = ?request, "Processing request");
|
||||
let peer_id =
|
||||
request.authority_id().expect("all nodes are authorities").clone();
|
||||
|
||||
if !self.network.is_peer_connected(&peer_id) {
|
||||
// Attempting to send a request to a disconnected peer.
|
||||
request
|
||||
.into_response_sender()
|
||||
.send(Err(RequestFailure::NotConnected))
|
||||
.expect("send never fails");
|
||||
continue
|
||||
}
|
||||
|
||||
let peer_message =
|
||||
NetworkMessage::RequestFromNode(peer_id.clone(), request);
|
||||
|
||||
let _ = self.to_network_interface.unbounded_send(peer_message);
|
||||
}
|
||||
},
|
||||
NetworkBridgeTxMessage::ReportPeer(_) => {
|
||||
// ingore rep changes
|
||||
},
|
||||
NetworkBridgeTxMessage::SendValidationMessage(peers, message) => {
|
||||
for peer in peers {
|
||||
self.to_network_interface
|
||||
.unbounded_send(NetworkMessage::MessageFromNode(
|
||||
self.test_authorithies
|
||||
.peer_id_to_authority
|
||||
.get(&peer)
|
||||
.unwrap()
|
||||
.clone(),
|
||||
message.clone(),
|
||||
))
|
||||
.expect("Should not fail");
|
||||
}
|
||||
},
|
||||
_ => unimplemented!("Unexpected network bridge message"),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[overseer::contextbounds(NetworkBridgeRx, prefix = self::overseer)]
|
||||
impl MockNetworkBridgeRx {
|
||||
async fn run<Context>(mut self, mut ctx: Context) {
|
||||
// Main subsystem loop.
|
||||
let mut from_network_interface = self.network_receiver.0;
|
||||
loop {
|
||||
futures::select! {
|
||||
maybe_peer_message = from_network_interface.next() => {
|
||||
if let Some(message) = maybe_peer_message {
|
||||
match message {
|
||||
NetworkMessage::MessageFromPeer(peer_id, message) => match message {
|
||||
Versioned::V2(
|
||||
polkadot_node_network_protocol::v2::ValidationProtocol::BitfieldDistribution(
|
||||
bitfield,
|
||||
),
|
||||
) => {
|
||||
ctx.send_message(
|
||||
BitfieldDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage(peer_id, polkadot_node_network_protocol::Versioned::V2(bitfield)))
|
||||
).await;
|
||||
},
|
||||
Versioned::V3(
|
||||
polkadot_node_network_protocol::v3::ValidationProtocol::ApprovalDistribution(msg)
|
||||
) => {
|
||||
ctx.send_message(
|
||||
ApprovalDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage(peer_id, polkadot_node_network_protocol::Versioned::V3(msg)))
|
||||
).await;
|
||||
}
|
||||
_ => {
|
||||
unimplemented!("We only talk v2 network protocol")
|
||||
},
|
||||
},
|
||||
NetworkMessage::RequestFromPeer(request) => {
|
||||
if let Some(protocol) = self.chunk_request_sender.as_mut() {
|
||||
assert_eq!(&*protocol.name, CHUNK_REQ_PROTOCOL_NAME_V1);
|
||||
if let Some(inbound_queue) = protocol.inbound_queue.as_ref() {
|
||||
inbound_queue
|
||||
.send(request)
|
||||
.await
|
||||
.expect("Forwarding requests to subsystem never fails");
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
panic!("NetworkMessage::RequestFromNode is not expected to be received from a peer")
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
subsystem_message = ctx.recv().fuse() => {
|
||||
match subsystem_message.expect("Overseer never fails us") {
|
||||
orchestra::FromOrchestra::Signal(signal) => if signal == OverseerSignal::Conclude { return },
|
||||
_ => {
|
||||
unimplemented!("Unexpected network bridge rx message")
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
// 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/>.
|
||||
|
||||
//! A generic runtime api subsystem mockup suitable to be used in benchmarks.
|
||||
|
||||
use crate::configuration::{TestAuthorities, TestConfiguration};
|
||||
use bitvec::prelude::BitVec;
|
||||
use futures::FutureExt;
|
||||
use itertools::Itertools;
|
||||
use polkadot_node_subsystem::{
|
||||
messages::{RuntimeApiMessage, RuntimeApiRequest},
|
||||
overseer, SpawnedSubsystem, SubsystemError,
|
||||
};
|
||||
use polkadot_node_subsystem_types::OverseerSignal;
|
||||
use polkadot_primitives::{
|
||||
vstaging::NodeFeatures, CandidateEvent, CandidateReceipt, CoreState, GroupIndex, IndexedVec,
|
||||
OccupiedCore, SessionIndex, SessionInfo, ValidatorIndex,
|
||||
};
|
||||
use sp_consensus_babe::Epoch as BabeEpoch;
|
||||
use sp_core::H256;
|
||||
use std::collections::HashMap;
|
||||
|
||||
const LOG_TARGET: &str = "subsystem-bench::runtime-api-mock";
|
||||
|
||||
/// Minimal state to answer requests.
|
||||
pub struct RuntimeApiState {
|
||||
// All authorities in the test,
|
||||
authorities: TestAuthorities,
|
||||
// Candidate hashes per block
|
||||
candidate_hashes: HashMap<H256, Vec<CandidateReceipt>>,
|
||||
// Included candidates per bock
|
||||
included_candidates: HashMap<H256, Vec<CandidateEvent>>,
|
||||
babe_epoch: Option<BabeEpoch>,
|
||||
// The session child index,
|
||||
session_index: SessionIndex,
|
||||
}
|
||||
|
||||
/// A mocked `runtime-api` subsystem.
|
||||
pub struct MockRuntimeApi {
|
||||
state: RuntimeApiState,
|
||||
config: TestConfiguration,
|
||||
}
|
||||
|
||||
impl MockRuntimeApi {
|
||||
pub fn new(
|
||||
config: TestConfiguration,
|
||||
authorities: TestAuthorities,
|
||||
candidate_hashes: HashMap<H256, Vec<CandidateReceipt>>,
|
||||
included_candidates: HashMap<H256, Vec<CandidateEvent>>,
|
||||
babe_epoch: Option<BabeEpoch>,
|
||||
session_index: SessionIndex,
|
||||
) -> MockRuntimeApi {
|
||||
Self {
|
||||
state: RuntimeApiState {
|
||||
authorities,
|
||||
candidate_hashes,
|
||||
included_candidates,
|
||||
babe_epoch,
|
||||
session_index,
|
||||
},
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
fn session_info(&self) -> SessionInfo {
|
||||
session_info_for_peers(&self.config, &self.state.authorities)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a test session info with all passed authorities as consensus validators.
|
||||
pub fn session_info_for_peers(
|
||||
configuration: &TestConfiguration,
|
||||
authorities: &TestAuthorities,
|
||||
) -> SessionInfo {
|
||||
let all_validators = (0..configuration.n_validators)
|
||||
.map(|i| ValidatorIndex(i as _))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let validator_groups = all_validators
|
||||
.chunks(configuration.max_validators_per_core)
|
||||
.map(Vec::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
SessionInfo {
|
||||
validators: authorities.validator_public.iter().cloned().collect(),
|
||||
discovery_keys: authorities.validator_authority_id.to_vec(),
|
||||
assignment_keys: authorities.validator_assignment_id.to_vec(),
|
||||
validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(validator_groups),
|
||||
n_cores: configuration.n_cores as u32,
|
||||
needed_approvals: configuration.needed_approvals as u32,
|
||||
zeroth_delay_tranche_width: configuration.zeroth_delay_tranche_width as u32,
|
||||
relay_vrf_modulo_samples: configuration.relay_vrf_modulo_samples as u32,
|
||||
n_delay_tranches: configuration.n_delay_tranches as u32,
|
||||
no_show_slots: configuration.no_show_slots as u32,
|
||||
active_validator_indices: (0..authorities.validator_authority_id.len())
|
||||
.map(|index| ValidatorIndex(index as u32))
|
||||
.collect_vec(),
|
||||
dispute_period: 6,
|
||||
random_seed: [0u8; 32],
|
||||
}
|
||||
}
|
||||
|
||||
#[overseer::subsystem(RuntimeApi, error=SubsystemError, prefix=self::overseer)]
|
||||
impl<Context> MockRuntimeApi {
|
||||
fn start(self, ctx: Context) -> SpawnedSubsystem {
|
||||
let future = self.run(ctx).map(|_| Ok(())).boxed();
|
||||
|
||||
SpawnedSubsystem { name: "test-environment", future }
|
||||
}
|
||||
}
|
||||
|
||||
#[overseer::contextbounds(RuntimeApi, prefix = self::overseer)]
|
||||
impl MockRuntimeApi {
|
||||
async fn run<Context>(self, mut ctx: Context) {
|
||||
let validator_group_count = self.session_info().validator_groups.len();
|
||||
|
||||
loop {
|
||||
let msg = ctx.recv().await.expect("Overseer never fails us");
|
||||
|
||||
match msg {
|
||||
orchestra::FromOrchestra::Signal(signal) =>
|
||||
if signal == OverseerSignal::Conclude {
|
||||
return
|
||||
},
|
||||
orchestra::FromOrchestra::Communication { msg } => {
|
||||
gum::debug!(target: LOG_TARGET, msg=?msg, "recv message");
|
||||
|
||||
match msg {
|
||||
RuntimeApiMessage::Request(
|
||||
request,
|
||||
RuntimeApiRequest::CandidateEvents(sender),
|
||||
) => {
|
||||
let candidate_events = self.state.included_candidates.get(&request);
|
||||
let _ = sender.send(Ok(candidate_events.cloned().unwrap_or_default()));
|
||||
},
|
||||
RuntimeApiMessage::Request(
|
||||
_block_hash,
|
||||
RuntimeApiRequest::SessionInfo(_session_index, sender),
|
||||
) => {
|
||||
let _ = sender.send(Ok(Some(self.session_info())));
|
||||
},
|
||||
RuntimeApiMessage::Request(
|
||||
_block_hash,
|
||||
RuntimeApiRequest::SessionExecutorParams(_session_index, sender),
|
||||
) => {
|
||||
let _ = sender.send(Ok(Some(Default::default())));
|
||||
},
|
||||
RuntimeApiMessage::Request(
|
||||
_request,
|
||||
RuntimeApiRequest::NodeFeatures(_session_index, sender),
|
||||
) => {
|
||||
let _ = sender.send(Ok(NodeFeatures::EMPTY));
|
||||
},
|
||||
RuntimeApiMessage::Request(
|
||||
_block_hash,
|
||||
RuntimeApiRequest::Validators(sender),
|
||||
) => {
|
||||
let _ =
|
||||
sender.send(Ok(self.state.authorities.validator_public.clone()));
|
||||
},
|
||||
RuntimeApiMessage::Request(
|
||||
_block_hash,
|
||||
RuntimeApiRequest::SessionIndexForChild(sender),
|
||||
) => {
|
||||
// Session is always the same.
|
||||
let _ = sender.send(Ok(self.state.session_index));
|
||||
},
|
||||
RuntimeApiMessage::Request(
|
||||
block_hash,
|
||||
RuntimeApiRequest::AvailabilityCores(sender),
|
||||
) => {
|
||||
let candidate_hashes = self
|
||||
.state
|
||||
.candidate_hashes
|
||||
.get(&block_hash)
|
||||
.expect("Relay chain block hashes are generated at test start");
|
||||
|
||||
// All cores are always occupied.
|
||||
let cores = candidate_hashes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, candidate_receipt)| {
|
||||
// Ensure test breaks if badly configured.
|
||||
assert!(index < validator_group_count);
|
||||
|
||||
CoreState::Occupied(OccupiedCore {
|
||||
next_up_on_available: None,
|
||||
occupied_since: 0,
|
||||
time_out_at: 0,
|
||||
next_up_on_time_out: None,
|
||||
availability: BitVec::default(),
|
||||
group_responsible: GroupIndex(index as u32),
|
||||
candidate_hash: candidate_receipt.hash(),
|
||||
candidate_descriptor: candidate_receipt.descriptor.clone(),
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let _ = sender.send(Ok(cores));
|
||||
},
|
||||
RuntimeApiMessage::Request(
|
||||
_request,
|
||||
RuntimeApiRequest::CurrentBabeEpoch(sender),
|
||||
) => {
|
||||
let _ = sender.send(Ok(self
|
||||
.state
|
||||
.babe_epoch
|
||||
.clone()
|
||||
.expect("Babe epoch unpopulated")));
|
||||
},
|
||||
// Long term TODO: implement more as needed.
|
||||
message => {
|
||||
unimplemented!("Unexpected runtime-api message: {:?}", message)
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user