Migrate polkadot-primitives to v6 (#1543)

- Async-backing related primitives are stable `primitives::v6`
- Async-backing API is now part of `api_version(7)`
- It's enabled on Rococo and Westend runtimes

---------

Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>
Co-authored-by: Andrei Sandu <54316454+sandreim@users.noreply.github.com>
This commit is contained in:
Chris Sosnin
2023-09-27 13:32:02 +03:00
committed by GitHub
parent 5a2833cceb
commit 7cbe0c76ef
107 changed files with 2410 additions and 2792 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,76 @@
// 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 utility for tracking groups and their members within a session.
use polkadot_primitives::{
effective_minimum_backing_votes, GroupIndex, IndexedVec, ValidatorIndex,
};
use std::collections::HashMap;
/// Validator groups within a session, plus some helpful indexing for
/// looking up groups by validator indices or authority discovery ID.
#[derive(Debug, Clone)]
pub struct Groups {
groups: IndexedVec<GroupIndex, Vec<ValidatorIndex>>,
by_validator_index: HashMap<ValidatorIndex, GroupIndex>,
backing_threshold: u32,
}
impl Groups {
/// Create a new [`Groups`] tracker with the groups and discovery keys
/// from the session.
pub fn new(
groups: IndexedVec<GroupIndex, Vec<ValidatorIndex>>,
backing_threshold: u32,
) -> Self {
let mut by_validator_index = HashMap::new();
for (i, group) in groups.iter().enumerate() {
let index = GroupIndex(i as _);
for v in group {
by_validator_index.insert(*v, index);
}
}
Groups { groups, by_validator_index, backing_threshold }
}
/// Access all the underlying groups.
pub fn all(&self) -> &IndexedVec<GroupIndex, Vec<ValidatorIndex>> {
&self.groups
}
/// Get the underlying group validators by group index.
pub fn get(&self, group_index: GroupIndex) -> Option<&[ValidatorIndex]> {
self.groups.get(group_index).map(|x| &x[..])
}
/// Get the backing group size and backing threshold.
pub fn get_size_and_backing_threshold(
&self,
group_index: GroupIndex,
) -> Option<(usize, usize)> {
self.get(group_index)
.map(|g| (g.len(), effective_minimum_backing_votes(g.len(), self.backing_threshold)))
}
/// Get the group index for a validator by index.
pub fn by_validator_index(&self, validator_index: ValidatorIndex) -> Option<GroupIndex> {
self.by_validator_index.get(&validator_index).map(|x| *x)
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,283 @@
// 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 store of all statements under a given relay-parent.
//!
//! This structure doesn't attempt to do any spam protection, which must
//! be provided at a higher level.
//!
//! This keeps track of statements submitted with a number of different of
//! views into this data: views based on the candidate, views based on the validator
//! groups, and views based on the validators themselves.
use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec};
use polkadot_node_network_protocol::v2::StatementFilter;
use polkadot_primitives::{
CandidateHash, CompactStatement, GroupIndex, SignedStatement, ValidatorIndex,
};
use std::collections::hash_map::{Entry as HEntry, HashMap};
use super::groups::Groups;
/// Possible origins of a statement.
pub enum StatementOrigin {
/// The statement originated locally.
Local,
/// The statement originated from a remote peer.
Remote,
}
impl StatementOrigin {
fn is_local(&self) -> bool {
match *self {
StatementOrigin::Local => true,
StatementOrigin::Remote => false,
}
}
}
struct StoredStatement {
statement: SignedStatement,
known_by_backing: bool,
}
/// Storage for statements. Intended to be used for statements signed under
/// the same relay-parent. See module docs for more details.
pub struct StatementStore {
validator_meta: HashMap<ValidatorIndex, ValidatorMeta>,
// we keep statements per-group because even though only one group _should_ be
// producing statements about a candidate, until we have the candidate receipt
// itself, we can't tell which group that is.
group_statements: HashMap<(GroupIndex, CandidateHash), GroupStatements>,
known_statements: HashMap<Fingerprint, StoredStatement>,
}
impl StatementStore {
/// Create a new [`StatementStore`]
pub fn new(groups: &Groups) -> Self {
let mut validator_meta = HashMap::new();
for (g, group) in groups.all().iter().enumerate() {
for (i, v) in group.iter().enumerate() {
validator_meta.insert(
*v,
ValidatorMeta {
seconded_count: 0,
within_group_index: i,
group: GroupIndex(g as _),
},
);
}
}
StatementStore {
validator_meta,
group_statements: HashMap::new(),
known_statements: HashMap::new(),
}
}
/// Insert a statement. Returns `true` if was not known already, `false` if it was.
/// Ignores statements by unknown validators and returns an error.
pub fn insert(
&mut self,
groups: &Groups,
statement: SignedStatement,
origin: StatementOrigin,
) -> Result<bool, ValidatorUnknown> {
let validator_index = statement.validator_index();
let validator_meta = match self.validator_meta.get_mut(&validator_index) {
None => return Err(ValidatorUnknown),
Some(m) => m,
};
let compact = statement.payload().clone();
let fingerprint = (validator_index, compact.clone());
match self.known_statements.entry(fingerprint) {
HEntry::Occupied(mut e) => {
if let StatementOrigin::Local = origin {
e.get_mut().known_by_backing = true;
}
return Ok(false)
},
HEntry::Vacant(e) => {
e.insert(StoredStatement { statement, known_by_backing: origin.is_local() });
},
}
let candidate_hash = *compact.candidate_hash();
let seconded = if let CompactStatement::Seconded(_) = compact { true } else { false };
// cross-reference updates.
{
let group_index = validator_meta.group;
let group = match groups.get(group_index) {
Some(g) => g,
None => {
gum::error!(
target: crate::LOG_TARGET,
?group_index,
"groups passed into `insert` differ from those used at store creation"
);
return Err(ValidatorUnknown)
},
};
let group_statements = self
.group_statements
.entry((group_index, candidate_hash))
.or_insert_with(|| GroupStatements::with_group_size(group.len()));
if seconded {
validator_meta.seconded_count += 1;
group_statements.note_seconded(validator_meta.within_group_index);
} else {
group_statements.note_validated(validator_meta.within_group_index);
}
}
Ok(true)
}
/// Fill a `StatementFilter` to be used in the grid topology with all statements
/// we are already aware of.
pub fn fill_statement_filter(
&self,
group_index: GroupIndex,
candidate_hash: CandidateHash,
statement_filter: &mut StatementFilter,
) {
if let Some(statements) = self.group_statements.get(&(group_index, candidate_hash)) {
statement_filter.seconded_in_group |= statements.seconded.as_bitslice();
statement_filter.validated_in_group |= statements.valid.as_bitslice();
}
}
/// Get an iterator over stored signed statements by the group conforming to the
/// given filter.
///
/// Seconded statements are provided first.
pub fn group_statements<'a>(
&'a self,
groups: &'a Groups,
group_index: GroupIndex,
candidate_hash: CandidateHash,
filter: &'a StatementFilter,
) -> impl Iterator<Item = &'a SignedStatement> + 'a {
let group_validators = groups.get(group_index);
let seconded_statements = filter
.seconded_in_group
.iter_ones()
.filter_map(move |i| group_validators.as_ref().and_then(|g| g.get(i)))
.filter_map(move |v| {
self.known_statements.get(&(*v, CompactStatement::Seconded(candidate_hash)))
})
.map(|s| &s.statement);
let valid_statements = filter
.validated_in_group
.iter_ones()
.filter_map(move |i| group_validators.as_ref().and_then(|g| g.get(i)))
.filter_map(move |v| {
self.known_statements.get(&(*v, CompactStatement::Valid(candidate_hash)))
})
.map(|s| &s.statement);
seconded_statements.chain(valid_statements)
}
/// Get the full statement of this kind issued by this validator, if it is known.
pub fn validator_statement(
&self,
validator_index: ValidatorIndex,
statement: CompactStatement,
) -> Option<&SignedStatement> {
self.known_statements.get(&(validator_index, statement)).map(|s| &s.statement)
}
/// Get an iterator over all statements marked as being unknown by the backing subsystem.
pub fn fresh_statements_for_backing<'a>(
&'a self,
validators: &'a [ValidatorIndex],
candidate_hash: CandidateHash,
) -> impl Iterator<Item = &SignedStatement> + 'a {
let s_st = CompactStatement::Seconded(candidate_hash);
let v_st = CompactStatement::Valid(candidate_hash);
validators
.iter()
.flat_map(move |v| {
let a = self.known_statements.get(&(*v, s_st.clone()));
let b = self.known_statements.get(&(*v, v_st.clone()));
a.into_iter().chain(b)
})
.filter(|stored| !stored.known_by_backing)
.map(|stored| &stored.statement)
}
/// Get the amount of known `Seconded` statements by the given validator index.
pub fn seconded_count(&self, validator_index: &ValidatorIndex) -> usize {
self.validator_meta.get(validator_index).map_or(0, |m| m.seconded_count)
}
/// Note that a statement is known by the backing subsystem.
pub fn note_known_by_backing(
&mut self,
validator_index: ValidatorIndex,
statement: CompactStatement,
) {
if let Some(stored) = self.known_statements.get_mut(&(validator_index, statement)) {
stored.known_by_backing = true;
}
}
}
/// Error indicating that the validator was unknown.
pub struct ValidatorUnknown;
type Fingerprint = (ValidatorIndex, CompactStatement);
struct ValidatorMeta {
group: GroupIndex,
within_group_index: usize,
seconded_count: usize,
}
struct GroupStatements {
seconded: BitVec<u8, BitOrderLsb0>,
valid: BitVec<u8, BitOrderLsb0>,
}
impl GroupStatements {
fn with_group_size(group_size: usize) -> Self {
GroupStatements {
seconded: BitVec::repeat(false, group_size),
valid: BitVec::repeat(false, group_size),
}
}
fn note_seconded(&mut self, within_group_index: usize) {
self.seconded.set(within_group_index, true);
}
fn note_validated(&mut self, within_group_index: usize) {
self.valid.set(within_group_index, true);
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,611 @@
// 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/>.
#![allow(clippy::clone_on_copy)]
use super::*;
use crate::*;
use polkadot_node_network_protocol::{
grid_topology::TopologyPeerInfo,
request_response::{outgoing::Recipient, ReqProtocolNames},
view, ObservedRole,
};
use polkadot_node_primitives::Statement;
use polkadot_node_subsystem::messages::{
network_bridge_event::NewGossipTopology, AllMessages, ChainApiMessage, FragmentTreeMembership,
HypotheticalCandidate, NetworkBridgeEvent, ProspectiveParachainsMessage, ReportPeerMessage,
RuntimeApiMessage, RuntimeApiRequest,
};
use polkadot_node_subsystem_test_helpers as test_helpers;
use polkadot_node_subsystem_util::TimeoutExt;
use polkadot_primitives::{
AssignmentPair, AsyncBackingParams, BlockNumber, CommittedCandidateReceipt, CoreState,
GroupRotationInfo, HeadData, Header, IndexedVec, PersistedValidationData, ScheduledCore,
SessionIndex, SessionInfo, ValidatorPair,
};
use sc_keystore::LocalKeystore;
use sp_application_crypto::Pair as PairT;
use sp_authority_discovery::AuthorityPair as AuthorityDiscoveryPair;
use sp_keyring::Sr25519Keyring;
use assert_matches::assert_matches;
use futures::Future;
use parity_scale_codec::Encode;
use rand::{Rng, SeedableRng};
use test_helpers::mock::new_leaf;
use std::sync::Arc;
mod cluster;
mod grid;
mod requests;
type VirtualOverseer = test_helpers::TestSubsystemContextHandle<StatementDistributionMessage>;
const DEFAULT_ASYNC_BACKING_PARAMETERS: AsyncBackingParams =
AsyncBackingParams { max_candidate_depth: 4, allowed_ancestry_len: 3 };
// Some deterministic genesis hash for req/res protocol names
const GENESIS_HASH: Hash = Hash::repeat_byte(0xff);
struct TestConfig {
validator_count: usize,
// how many validators to place in each group.
group_size: usize,
// whether the local node should be a validator
local_validator: bool,
async_backing_params: Option<AsyncBackingParams>,
}
#[derive(Debug, Clone)]
struct TestLocalValidator {
validator_index: ValidatorIndex,
group_index: GroupIndex,
}
struct TestState {
config: TestConfig,
local: Option<TestLocalValidator>,
validators: Vec<ValidatorPair>,
session_info: SessionInfo,
req_sender: async_channel::Sender<sc_network::config::IncomingRequest>,
}
impl TestState {
fn from_config(
config: TestConfig,
req_sender: async_channel::Sender<sc_network::config::IncomingRequest>,
rng: &mut impl Rng,
) -> Self {
if config.group_size == 0 {
panic!("group size cannot be 0");
}
let mut validators = Vec::new();
let mut discovery_keys = Vec::new();
let mut assignment_keys = Vec::new();
let mut validator_groups = Vec::new();
let local_validator_pos = if config.local_validator {
// ensure local validator is always in a full group.
Some(rng.gen_range(0..config.validator_count).saturating_sub(config.group_size - 1))
} else {
None
};
for i in 0..config.validator_count {
let validator_pair = if Some(i) == local_validator_pos {
// Note: the specific key is used to ensure the keystore holds
// this key and the subsystem can detect that it is a validator.
Sr25519Keyring::Ferdie.pair().into()
} else {
ValidatorPair::generate().0
};
let assignment_id = AssignmentPair::generate().0.public();
let discovery_id = AuthorityDiscoveryPair::generate().0.public();
let group_index = i / config.group_size;
validators.push(validator_pair);
discovery_keys.push(discovery_id);
assignment_keys.push(assignment_id);
if validator_groups.len() == group_index {
validator_groups.push(vec![ValidatorIndex(i as _)]);
} else {
validator_groups.last_mut().unwrap().push(ValidatorIndex(i as _));
}
}
let local = if let Some(local_pos) = local_validator_pos {
Some(TestLocalValidator {
validator_index: ValidatorIndex(local_pos as _),
group_index: GroupIndex((local_pos / config.group_size) as _),
})
} else {
None
};
let validator_public = validator_pubkeys(&validators);
let session_info = SessionInfo {
validators: validator_public,
discovery_keys,
validator_groups: IndexedVec::from(validator_groups),
assignment_keys,
n_cores: 0,
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![],
dispute_period: 6,
random_seed: [0u8; 32],
};
TestState { config, local, validators, session_info, req_sender }
}
fn make_dummy_leaf(&self, relay_parent: Hash) -> TestLeaf {
TestLeaf {
number: 1,
hash: relay_parent,
parent_hash: Hash::repeat_byte(0),
session: 1,
availability_cores: self.make_availability_cores(|i| {
CoreState::Scheduled(ScheduledCore {
para_id: ParaId::from(i as u32),
collator: None,
})
}),
para_data: (0..self.session_info.validator_groups.len())
.map(|i| (ParaId::from(i as u32), PerParaData::new(1, vec![1, 2, 3].into())))
.collect(),
}
}
fn make_availability_cores(&self, f: impl Fn(usize) -> CoreState) -> Vec<CoreState> {
(0..self.session_info.validator_groups.len()).map(f).collect()
}
fn make_dummy_topology(&self) -> NewGossipTopology {
let validator_count = self.config.validator_count;
NewGossipTopology {
session: 1,
topology: SessionGridTopology::new(
(0..validator_count).collect(),
(0..validator_count)
.map(|i| TopologyPeerInfo {
peer_ids: Vec::new(),
validator_index: ValidatorIndex(i as u32),
discovery_id: AuthorityDiscoveryPair::generate().0.public(),
})
.collect(),
),
local_index: self.local.as_ref().map(|local| local.validator_index),
}
}
fn group_validators(
&self,
group_index: GroupIndex,
exclude_local: bool,
) -> Vec<ValidatorIndex> {
self.session_info
.validator_groups
.get(group_index)
.unwrap()
.iter()
.cloned()
.filter(|&i| {
self.local.as_ref().map_or(true, |l| !exclude_local || l.validator_index != i)
})
.collect()
}
fn discovery_id(&self, validator_index: ValidatorIndex) -> AuthorityDiscoveryId {
self.session_info.discovery_keys[validator_index.0 as usize].clone()
}
fn sign_statement(
&self,
validator_index: ValidatorIndex,
statement: CompactStatement,
context: &SigningContext,
) -> SignedStatement {
let payload = statement.signing_payload(context);
let pair = &self.validators[validator_index.0 as usize];
let signature = pair.sign(&payload[..]);
SignedStatement::new(statement, validator_index, signature, context, &pair.public())
.unwrap()
}
fn sign_full_statement(
&self,
validator_index: ValidatorIndex,
statement: Statement,
context: &SigningContext,
pvd: PersistedValidationData,
) -> SignedFullStatementWithPVD {
let payload = statement.to_compact().signing_payload(context);
let pair = &self.validators[validator_index.0 as usize];
let signature = pair.sign(&payload[..]);
SignedFullStatementWithPVD::new(
statement.supply_pvd(pvd),
validator_index,
signature,
context,
&pair.public(),
)
.unwrap()
}
// send a request out, returning a future which expects a response.
async fn send_request(
&mut self,
peer: PeerId,
request: AttestedCandidateRequest,
) -> impl Future<Output = sc_network::config::OutgoingResponse> {
let (tx, rx) = futures::channel::oneshot::channel();
let req = sc_network::config::IncomingRequest {
peer,
payload: request.encode(),
pending_response: tx,
};
self.req_sender.send(req).await.unwrap();
rx.map(|r| r.unwrap())
}
}
fn test_harness<T: Future<Output = VirtualOverseer>>(
config: TestConfig,
test: impl FnOnce(TestState, VirtualOverseer) -> T,
) {
let pool = sp_core::testing::TaskExecutor::new();
let keystore = if config.local_validator {
test_helpers::mock::make_ferdie_keystore()
} else {
Arc::new(LocalKeystore::in_memory()) as KeystorePtr
};
let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None);
let (statement_req_receiver, _) = IncomingRequest::get_config_receiver(&req_protocol_names);
let (candidate_req_receiver, req_cfg) =
IncomingRequest::get_config_receiver(&req_protocol_names);
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0);
let test_state = TestState::from_config(config, req_cfg.inbound_queue.unwrap(), &mut rng);
let (context, virtual_overseer) = test_helpers::make_subsystem_context(pool.clone());
let subsystem = async move {
let subsystem = crate::StatementDistributionSubsystem {
keystore,
v1_req_receiver: Some(statement_req_receiver),
req_receiver: Some(candidate_req_receiver),
metrics: Default::default(),
rng,
reputation: ReputationAggregator::new(|_| true),
};
if let Err(e) = subsystem.run(context).await {
panic!("Fatal error: {:?}", e);
}
};
let test_fut = test(test_state, virtual_overseer);
futures::pin_mut!(test_fut);
futures::pin_mut!(subsystem);
futures::executor::block_on(future::join(
async move {
let mut virtual_overseer = test_fut.await;
// Ensure we have handled all responses.
if let Ok(Some(msg)) = virtual_overseer.rx.try_next() {
panic!("Did not handle all responses: {:?}", msg);
}
// Conclude.
virtual_overseer.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
},
subsystem,
));
}
struct PerParaData {
min_relay_parent: BlockNumber,
head_data: HeadData,
}
impl PerParaData {
pub fn new(min_relay_parent: BlockNumber, head_data: HeadData) -> Self {
Self { min_relay_parent, head_data }
}
}
struct TestLeaf {
number: BlockNumber,
hash: Hash,
parent_hash: Hash,
session: SessionIndex,
availability_cores: Vec<CoreState>,
para_data: Vec<(ParaId, PerParaData)>,
}
impl TestLeaf {
pub fn para_data(&self, para_id: ParaId) -> &PerParaData {
self.para_data
.iter()
.find_map(|(p_id, data)| if *p_id == para_id { Some(data) } else { None })
.unwrap()
}
}
async fn activate_leaf(
virtual_overseer: &mut VirtualOverseer,
leaf: &TestLeaf,
test_state: &TestState,
is_new_session: bool,
) {
let activated = new_leaf(leaf.hash, leaf.number);
virtual_overseer
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(
activated,
))))
.await;
handle_leaf_activation(virtual_overseer, leaf, test_state, is_new_session).await;
}
async fn handle_leaf_activation(
virtual_overseer: &mut VirtualOverseer,
leaf: &TestLeaf,
test_state: &TestState,
is_new_session: bool,
) {
let TestLeaf { number, hash, parent_hash, para_data, session, availability_cores } = leaf;
assert_matches!(
virtual_overseer.recv().await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(parent, RuntimeApiRequest::AsyncBackingParams(tx))
) if parent == *hash => {
tx.send(Ok(test_state.config.async_backing_params.unwrap_or(DEFAULT_ASYNC_BACKING_PARAMETERS))).unwrap();
}
);
let mrp_response: Vec<(ParaId, BlockNumber)> = para_data
.iter()
.map(|(para_id, data)| (*para_id, data.min_relay_parent))
.collect();
assert_matches!(
virtual_overseer.recv().await,
AllMessages::ProspectiveParachains(
ProspectiveParachainsMessage::GetMinimumRelayParents(parent, tx)
) if parent == *hash => {
tx.send(mrp_response).unwrap();
}
);
let header = Header {
parent_hash: *parent_hash,
number: *number,
state_root: Hash::zero(),
extrinsics_root: Hash::zero(),
digest: Default::default(),
};
assert_matches!(
virtual_overseer.recv().await,
AllMessages::ChainApi(
ChainApiMessage::BlockHeader(parent, tx)
) if parent == *hash => {
tx.send(Ok(Some(header))).unwrap();
}
);
assert_matches!(
virtual_overseer.recv().await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(parent, RuntimeApiRequest::SessionIndexForChild(tx))) if parent == *hash => {
tx.send(Ok(*session)).unwrap();
}
);
assert_matches!(
virtual_overseer.recv().await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(parent, RuntimeApiRequest::AvailabilityCores(tx))) if parent == *hash => {
tx.send(Ok(availability_cores.clone())).unwrap();
}
);
let validator_groups = test_state.session_info.validator_groups.to_vec();
let group_rotation_info =
GroupRotationInfo { session_start_block: 1, group_rotation_frequency: 12, now: 1 };
assert_matches!(
virtual_overseer.recv().await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(parent, RuntimeApiRequest::ValidatorGroups(tx))) if parent == *hash => {
tx.send(Ok((validator_groups, group_rotation_info))).unwrap();
}
);
if is_new_session {
assert_matches!(
virtual_overseer.recv().await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(parent, RuntimeApiRequest::SessionInfo(s, tx))) if parent == *hash && s == *session => {
tx.send(Ok(Some(test_state.session_info.clone()))).unwrap();
}
);
assert_matches!(
virtual_overseer.recv().await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
parent,
RuntimeApiRequest::MinimumBackingVotes(session_index, tx),
)) if parent == *hash && session_index == *session => {
tx.send(Ok(2)).unwrap();
}
);
}
}
/// Intercepts an outgoing request, checks the fields, and sends the response.
async fn handle_sent_request(
virtual_overseer: &mut VirtualOverseer,
peer: PeerId,
candidate_hash: CandidateHash,
mask: StatementFilter,
candidate_receipt: CommittedCandidateReceipt,
persisted_validation_data: PersistedValidationData,
statements: Vec<UncheckedSignedStatement>,
) {
assert_matches!(
virtual_overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendRequests(mut requests, IfDisconnected::ImmediateError)) => {
assert_eq!(requests.len(), 1);
assert_matches!(
requests.pop().unwrap(),
Requests::AttestedCandidateV2(outgoing) => {
assert_eq!(outgoing.peer, Recipient::Peer(peer));
assert_eq!(outgoing.payload.candidate_hash, candidate_hash);
assert_eq!(outgoing.payload.mask, mask);
let res = AttestedCandidateResponse {
candidate_receipt,
persisted_validation_data,
statements,
};
outgoing.pending_response.send(Ok(res.encode())).unwrap();
}
);
}
);
}
async fn answer_expected_hypothetical_depth_request(
virtual_overseer: &mut VirtualOverseer,
responses: Vec<(HypotheticalCandidate, FragmentTreeMembership)>,
expected_leaf_hash: Option<Hash>,
expected_backed_in_path_only: bool,
) {
assert_matches!(
virtual_overseer.recv().await,
AllMessages::ProspectiveParachains(
ProspectiveParachainsMessage::GetHypotheticalFrontier(req, tx)
) => {
assert_eq!(req.fragment_tree_relay_parent, expected_leaf_hash);
assert_eq!(req.backed_in_path_only, expected_backed_in_path_only);
for (i, (candidate, _)) in responses.iter().enumerate() {
assert!(
req.candidates.iter().any(|c| &c == &candidate),
"did not receive request for hypothetical candidate {}",
i,
);
}
tx.send(responses).unwrap();
}
)
}
fn validator_pubkeys(val_ids: &[ValidatorPair]) -> IndexedVec<ValidatorIndex, ValidatorId> {
val_ids.iter().map(|v| v.public().into()).collect()
}
async fn connect_peer(
virtual_overseer: &mut VirtualOverseer,
peer: PeerId,
authority_ids: Option<HashSet<AuthorityDiscoveryId>>,
) {
virtual_overseer
.send(FromOrchestra::Communication {
msg: StatementDistributionMessage::NetworkBridgeUpdate(
NetworkBridgeEvent::PeerConnected(
peer,
ObservedRole::Authority,
ValidationVersion::V2.into(),
authority_ids,
),
),
})
.await;
}
// TODO: Add some tests using this?
#[allow(dead_code)]
async fn disconnect_peer(virtual_overseer: &mut VirtualOverseer, peer: PeerId) {
virtual_overseer
.send(FromOrchestra::Communication {
msg: StatementDistributionMessage::NetworkBridgeUpdate(
NetworkBridgeEvent::PeerDisconnected(peer),
),
})
.await;
}
async fn send_peer_view_change(virtual_overseer: &mut VirtualOverseer, peer: PeerId, view: View) {
virtual_overseer
.send(FromOrchestra::Communication {
msg: StatementDistributionMessage::NetworkBridgeUpdate(
NetworkBridgeEvent::PeerViewChange(peer, view),
),
})
.await;
}
async fn send_peer_message(
virtual_overseer: &mut VirtualOverseer,
peer: PeerId,
message: protocol_v2::StatementDistributionMessage,
) {
virtual_overseer
.send(FromOrchestra::Communication {
msg: StatementDistributionMessage::NetworkBridgeUpdate(
NetworkBridgeEvent::PeerMessage(peer, Versioned::V2(message)),
),
})
.await;
}
async fn send_new_topology(virtual_overseer: &mut VirtualOverseer, topology: NewGossipTopology) {
virtual_overseer
.send(FromOrchestra::Communication {
msg: StatementDistributionMessage::NetworkBridgeUpdate(
NetworkBridgeEvent::NewGossipTopology(topology),
),
})
.await;
}
async fn overseer_recv_with_timeout(
overseer: &mut VirtualOverseer,
timeout: Duration,
) -> Option<AllMessages> {
gum::trace!("waiting for message...");
overseer.recv().timeout(timeout).await
}
fn next_group_index(
group_index: GroupIndex,
validator_count: usize,
group_size: usize,
) -> GroupIndex {
let next_group = group_index.0 + 1;
let num_groups =
validator_count / group_size + if validator_count % group_size > 0 { 1 } else { 0 };
GroupIndex::from(next_group % num_groups as u32)
}
File diff suppressed because it is too large Load Diff