983 lines
30 KiB
Rust
983 lines
30 KiB
Rust
// 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/>.
|
|
|
|
//! The tests for Approval Voting Parallel Subsystem.
|
|
|
|
use std::{
|
|
collections::{HashMap, HashSet},
|
|
future::Future,
|
|
sync::Arc,
|
|
time::Duration,
|
|
};
|
|
|
|
use crate::{
|
|
build_worker_handles, metrics::MetricsWatcher, prio_right, run_main_loop, start_workers,
|
|
validator_index_for_msg, ApprovalVotingParallelSubsystem, Metrics, WorkProvider,
|
|
};
|
|
use assert_matches::assert_matches;
|
|
use futures::{channel::oneshot, future, stream::PollNext, StreamExt};
|
|
use itertools::Itertools;
|
|
use pezkuwi_node_core_approval_voting::{ApprovalVotingWorkProvider, Config};
|
|
use pezkuwi_node_network_protocol::{peer_set::ValidationVersion, ObservedRole, PeerId, View};
|
|
use pezkuwi_node_primitives::approval::{
|
|
time::SystemClock,
|
|
v1::RELAY_VRF_MODULO_CONTEXT,
|
|
v2::{
|
|
AssignmentCertKindV2, AssignmentCertV2, CoreBitfield, IndirectAssignmentCertV2,
|
|
IndirectSignedApprovalVoteV2,
|
|
},
|
|
};
|
|
use pezkuwi_node_subsystem::{
|
|
messages::{ApprovalDistributionMessage, ApprovalVotingMessage, ApprovalVotingParallelMessage},
|
|
FromOrchestra,
|
|
};
|
|
use pezkuwi_node_subsystem_test_helpers::{mock::new_leaf, TestSubsystemContext};
|
|
use pezkuwi_overseer::{ActiveLeavesUpdate, OverseerSignal, SpawnGlue, TimeoutExt};
|
|
use pezkuwi_primitives::{CandidateHash, CoreIndex, Hash, ValidatorIndex};
|
|
use sc_keystore::{Keystore, LocalKeystore};
|
|
use sp_consensus::SyncOracle;
|
|
use sp_consensus_babe::{VrfPreOutput, VrfProof, VrfSignature};
|
|
use sp_core::{testing::TaskExecutor, H256};
|
|
use sp_keyring::Sr25519Keyring;
|
|
type VirtualOverseer =
|
|
pezkuwi_node_subsystem_test_helpers::TestSubsystemContextHandle<ApprovalVotingParallelMessage>;
|
|
|
|
const SLOT_DURATION_MILLIS: u64 = 6000;
|
|
|
|
pub mod test_constants {
|
|
pub(crate) const DATA_COL: u32 = 0;
|
|
pub(crate) const NUM_COLUMNS: u32 = 1;
|
|
}
|
|
|
|
fn fake_assignment_cert_v2(
|
|
block_hash: Hash,
|
|
validator: ValidatorIndex,
|
|
core_bitfield: CoreBitfield,
|
|
) -> IndirectAssignmentCertV2 {
|
|
let ctx = schnorrkel::signing_context(RELAY_VRF_MODULO_CONTEXT);
|
|
let msg = b"WhenTeyrchains?";
|
|
let mut prng = rand_core::OsRng;
|
|
let keypair = schnorrkel::Keypair::generate_with(&mut prng);
|
|
let (inout, proof, _) = keypair.vrf_sign(ctx.bytes(msg));
|
|
let preout = inout.to_preout();
|
|
|
|
IndirectAssignmentCertV2 {
|
|
block_hash,
|
|
validator,
|
|
cert: AssignmentCertV2 {
|
|
kind: AssignmentCertKindV2::RelayVRFModuloCompact { core_bitfield },
|
|
vrf: VrfSignature { pre_output: VrfPreOutput(preout), proof: VrfProof(proof) },
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Creates a meaningless signature
|
|
pub fn dummy_signature() -> pezkuwi_primitives::ValidatorSignature {
|
|
sp_core::crypto::UncheckedFrom::unchecked_from([1u8; 64])
|
|
}
|
|
|
|
fn build_subsystem(
|
|
sync_oracle: Box<dyn SyncOracle + Send>,
|
|
) -> (
|
|
ApprovalVotingParallelSubsystem,
|
|
TestSubsystemContext<ApprovalVotingParallelMessage, SpawnGlue<TaskExecutor>>,
|
|
VirtualOverseer,
|
|
) {
|
|
sp_tracing::init_for_tests();
|
|
|
|
let pool = sp_core::testing::TaskExecutor::new();
|
|
let (context, virtual_overseer) = pezkuwi_node_subsystem_test_helpers::make_subsystem_context::<
|
|
ApprovalVotingParallelMessage,
|
|
_,
|
|
>(pool.clone());
|
|
|
|
let keystore = LocalKeystore::in_memory();
|
|
let _ = keystore.sr25519_generate_new(
|
|
pezkuwi_primitives::TEYRCHAIN_KEY_TYPE_ID,
|
|
Some(&Sr25519Keyring::Alice.to_seed()),
|
|
);
|
|
|
|
let clock = Arc::new(SystemClock {});
|
|
let db = kvdb_memorydb::create(test_constants::NUM_COLUMNS);
|
|
let db = pezkuwi_node_subsystem_util::database::kvdb_impl::DbAdapter::new(db, &[]);
|
|
|
|
(
|
|
ApprovalVotingParallelSubsystem::with_config_and_clock(
|
|
Config {
|
|
col_approval_data: test_constants::DATA_COL,
|
|
slot_duration_millis: SLOT_DURATION_MILLIS,
|
|
},
|
|
Arc::new(db),
|
|
Arc::new(keystore),
|
|
sync_oracle,
|
|
Metrics::default(),
|
|
clock.clone(),
|
|
SpawnGlue(pool),
|
|
None,
|
|
),
|
|
context,
|
|
virtual_overseer,
|
|
)
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct TestSyncOracle {}
|
|
|
|
impl SyncOracle for TestSyncOracle {
|
|
fn is_major_syncing(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
fn is_offline(&self) -> bool {
|
|
unimplemented!("not used in network bridge")
|
|
}
|
|
}
|
|
|
|
fn test_harness<T, Clos, State>(
|
|
num_approval_distro_workers: usize,
|
|
prio_right: Clos,
|
|
subsystem_gracefully_exits: bool,
|
|
test_fn: impl FnOnce(
|
|
VirtualOverseer,
|
|
WorkProvider<ApprovalVotingMessage, Clos, State>,
|
|
Vec<WorkProvider<ApprovalDistributionMessage, Clos, State>>,
|
|
) -> T,
|
|
) where
|
|
T: Future<Output = VirtualOverseer>,
|
|
Clos: Clone + FnMut(&mut State) -> PollNext,
|
|
State: Default,
|
|
{
|
|
let (subsystem, context, virtual_overseer) = build_subsystem(Box::new(TestSyncOracle {}));
|
|
let mut metrics_watcher = MetricsWatcher::new(subsystem.metrics.clone());
|
|
let channel_size = 5;
|
|
|
|
let (to_approval_voting_worker, approval_voting_work_provider) =
|
|
build_worker_handles::<ApprovalVotingMessage, _, _>(
|
|
"to_approval_voting_worker".into(),
|
|
channel_size,
|
|
&mut metrics_watcher,
|
|
prio_right.clone(),
|
|
);
|
|
|
|
let approval_distribution_channels = { 0..num_approval_distro_workers }
|
|
.into_iter()
|
|
.map(|worker_index| {
|
|
build_worker_handles::<ApprovalDistributionMessage, _, _>(
|
|
format!("to_approval_distro/{}", worker_index),
|
|
channel_size,
|
|
&mut metrics_watcher,
|
|
prio_right.clone(),
|
|
)
|
|
})
|
|
.collect_vec();
|
|
|
|
let to_approval_distribution_workers =
|
|
approval_distribution_channels.iter().map(|(tx, _)| tx.clone()).collect_vec();
|
|
let approval_distribution_work_providers =
|
|
approval_distribution_channels.into_iter().map(|(_, rx)| rx).collect_vec();
|
|
|
|
let subsystem = async move {
|
|
let result = run_main_loop(
|
|
context,
|
|
to_approval_voting_worker,
|
|
to_approval_distribution_workers,
|
|
metrics_watcher,
|
|
)
|
|
.await;
|
|
|
|
if subsystem_gracefully_exits && result.is_err() {
|
|
result
|
|
} else {
|
|
Ok(())
|
|
}
|
|
};
|
|
|
|
let test_fut = test_fn(
|
|
virtual_overseer,
|
|
approval_voting_work_provider,
|
|
approval_distribution_work_providers,
|
|
);
|
|
|
|
futures::pin_mut!(test_fut);
|
|
futures::pin_mut!(subsystem);
|
|
|
|
futures::executor::block_on(future::join(
|
|
async move {
|
|
let _overseer = test_fut.await;
|
|
},
|
|
subsystem,
|
|
))
|
|
.1
|
|
.unwrap();
|
|
}
|
|
|
|
const TIMEOUT: Duration = Duration::from_millis(2000);
|
|
|
|
async fn overseer_signal(overseer: &mut VirtualOverseer, signal: OverseerSignal) {
|
|
overseer
|
|
.send(FromOrchestra::Signal(signal))
|
|
.timeout(TIMEOUT)
|
|
.await
|
|
.expect(&format!("{:?} is more than enough for sending signals.", TIMEOUT));
|
|
}
|
|
|
|
async fn overseer_message(overseer: &mut VirtualOverseer, msg: ApprovalVotingParallelMessage) {
|
|
overseer
|
|
.send(FromOrchestra::Communication { msg })
|
|
.timeout(TIMEOUT)
|
|
.await
|
|
.expect(&format!("{:?} is more than enough for sending signals.", TIMEOUT));
|
|
}
|
|
|
|
async fn run_start_workers() {
|
|
let (subsystem, mut context, _) = build_subsystem(Box::new(TestSyncOracle {}));
|
|
let mut metrics_watcher = MetricsWatcher::new(subsystem.metrics.clone());
|
|
let _workers = start_workers(&mut context, subsystem, &mut metrics_watcher).await.unwrap();
|
|
}
|
|
|
|
// Test starting the workers succeeds.
|
|
#[test]
|
|
fn start_workers_succeeds() {
|
|
futures::executor::block_on(run_start_workers());
|
|
}
|
|
|
|
// Test main loop forwards messages to the correct worker for all type of messages.
|
|
#[test]
|
|
fn test_main_loop_forwards_correctly() {
|
|
let num_approval_distro_workers = 4;
|
|
test_harness(
|
|
num_approval_distro_workers,
|
|
prio_right,
|
|
true,
|
|
|mut overseer, mut approval_voting_work_provider, mut rx_approval_distribution_workers| async move {
|
|
// 1. Check Signals are correctly forwarded to the workers.
|
|
let signal = OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(new_leaf(
|
|
Hash::random(),
|
|
1,
|
|
)));
|
|
overseer_signal(&mut overseer, signal.clone()).await;
|
|
let approval_voting_receives = approval_voting_work_provider.recv().await.unwrap();
|
|
assert_matches!(approval_voting_receives, FromOrchestra::Signal(_));
|
|
for rx_approval_distribution_worker in rx_approval_distribution_workers.iter_mut() {
|
|
let approval_distribution_receives =
|
|
rx_approval_distribution_worker.next().await.unwrap();
|
|
assert_matches!(approval_distribution_receives, FromOrchestra::Signal(_));
|
|
}
|
|
|
|
let (test_tx, _rx) = oneshot::channel();
|
|
let test_hash = Hash::random();
|
|
let test_block_nr = 2;
|
|
overseer_message(
|
|
&mut overseer,
|
|
ApprovalVotingParallelMessage::ApprovedAncestor(test_hash, test_block_nr, test_tx),
|
|
)
|
|
.await;
|
|
assert_matches!(
|
|
approval_voting_work_provider.recv().await.unwrap(),
|
|
FromOrchestra::Communication {
|
|
msg: ApprovalVotingMessage::ApprovedAncestor(hash, block_nr, _)
|
|
} => {
|
|
assert_eq!(hash, test_hash);
|
|
assert_eq!(block_nr, test_block_nr);
|
|
}
|
|
);
|
|
for rx_approval_distribution_worker in rx_approval_distribution_workers.iter_mut() {
|
|
assert!(rx_approval_distribution_worker
|
|
.next()
|
|
.timeout(Duration::from_millis(200))
|
|
.await
|
|
.is_none());
|
|
}
|
|
|
|
// 2. Check GetApprovalSignaturesForCandidate is correctly forwarded to the workers.
|
|
let (test_tx, _rx) = oneshot::channel();
|
|
let test_hash = CandidateHash(Hash::random());
|
|
overseer_message(
|
|
&mut overseer,
|
|
ApprovalVotingParallelMessage::GetApprovalSignaturesForCandidate(
|
|
test_hash, test_tx,
|
|
),
|
|
)
|
|
.await;
|
|
|
|
assert_matches!(
|
|
approval_voting_work_provider.recv().await.unwrap(),
|
|
FromOrchestra::Communication {
|
|
msg: ApprovalVotingMessage::GetApprovalSignaturesForCandidate(hash, _)
|
|
} => {
|
|
assert_eq!(hash, test_hash);
|
|
}
|
|
);
|
|
|
|
for rx_approval_distribution_worker in rx_approval_distribution_workers.iter_mut() {
|
|
assert!(rx_approval_distribution_worker
|
|
.next()
|
|
.timeout(Duration::from_millis(200))
|
|
.await
|
|
.is_none());
|
|
}
|
|
|
|
// 3. Check NewBlocks is correctly forwarded to the workers.
|
|
overseer_message(&mut overseer, ApprovalVotingParallelMessage::NewBlocks(vec![])).await;
|
|
for rx_approval_distribution_worker in rx_approval_distribution_workers.iter_mut() {
|
|
assert_matches!(rx_approval_distribution_worker.next().await.unwrap(),
|
|
FromOrchestra::Communication {
|
|
msg: ApprovalDistributionMessage::NewBlocks(blocks)
|
|
} => {
|
|
assert!(blocks.is_empty());
|
|
}
|
|
);
|
|
}
|
|
assert!(approval_voting_work_provider
|
|
.recv()
|
|
.timeout(Duration::from_millis(200))
|
|
.await
|
|
.is_none());
|
|
|
|
// 4. Check DistributeAssignment is correctly forwarded to the workers.
|
|
let validator_index = ValidatorIndex(17);
|
|
let assignment =
|
|
fake_assignment_cert_v2(Hash::random(), validator_index, CoreIndex(1).into());
|
|
overseer_message(
|
|
&mut overseer,
|
|
ApprovalVotingParallelMessage::DistributeAssignment(assignment.clone(), 1.into()),
|
|
)
|
|
.await;
|
|
|
|
for (index, rx_approval_distribution_worker) in
|
|
rx_approval_distribution_workers.iter_mut().enumerate()
|
|
{
|
|
if index == validator_index.0 as usize % num_approval_distro_workers {
|
|
assert_matches!(rx_approval_distribution_worker.next().await.unwrap(),
|
|
FromOrchestra::Communication {
|
|
msg: ApprovalDistributionMessage::DistributeAssignment(cert, bitfield)
|
|
} => {
|
|
assert_eq!(cert, assignment);
|
|
assert_eq!(bitfield, 1.into());
|
|
}
|
|
);
|
|
} else {
|
|
assert!(rx_approval_distribution_worker
|
|
.next()
|
|
.timeout(Duration::from_millis(200))
|
|
.await
|
|
.is_none());
|
|
}
|
|
}
|
|
assert!(approval_voting_work_provider
|
|
.recv()
|
|
.timeout(Duration::from_millis(200))
|
|
.await
|
|
.is_none());
|
|
|
|
// 5. Check DistributeApproval is correctly forwarded to the workers.
|
|
let validator_index = ValidatorIndex(26);
|
|
let expected_vote = IndirectSignedApprovalVoteV2 {
|
|
block_hash: H256::random(),
|
|
candidate_indices: 1.into(),
|
|
validator: validator_index,
|
|
signature: dummy_signature(),
|
|
};
|
|
|
|
overseer_message(
|
|
&mut overseer,
|
|
ApprovalVotingParallelMessage::DistributeApproval(expected_vote.clone()),
|
|
)
|
|
.await;
|
|
|
|
for (index, rx_approval_distribution_worker) in
|
|
rx_approval_distribution_workers.iter_mut().enumerate()
|
|
{
|
|
if index == validator_index.0 as usize % num_approval_distro_workers {
|
|
assert_matches!(rx_approval_distribution_worker.next().await.unwrap(),
|
|
FromOrchestra::Communication {
|
|
msg: ApprovalDistributionMessage::DistributeApproval(vote)
|
|
} => {
|
|
assert_eq!(vote, expected_vote);
|
|
}
|
|
);
|
|
} else {
|
|
assert!(rx_approval_distribution_worker
|
|
.next()
|
|
.timeout(Duration::from_millis(200))
|
|
.await
|
|
.is_none());
|
|
}
|
|
}
|
|
|
|
// 6. Check NetworkBridgeUpdate::PeerMessage is correctly forwarded just to one of the
|
|
// workers.
|
|
let approvals = vec![
|
|
IndirectSignedApprovalVoteV2 {
|
|
block_hash: H256::random(),
|
|
candidate_indices: 1.into(),
|
|
validator: validator_index,
|
|
signature: dummy_signature(),
|
|
},
|
|
IndirectSignedApprovalVoteV2 {
|
|
block_hash: H256::random(),
|
|
candidate_indices: 2.into(),
|
|
validator: validator_index,
|
|
signature: dummy_signature(),
|
|
},
|
|
];
|
|
let expected_msg = pezkuwi_node_network_protocol::ValidationProtocols::V3(
|
|
pezkuwi_node_network_protocol::v3::ApprovalDistributionMessage::Approvals(
|
|
approvals.clone(),
|
|
),
|
|
);
|
|
overseer_message(
|
|
&mut overseer,
|
|
ApprovalVotingParallelMessage::NetworkBridgeUpdate(
|
|
pezkuwi_node_subsystem::messages::NetworkBridgeEvent::PeerMessage(
|
|
PeerId::random(),
|
|
expected_msg.clone(),
|
|
),
|
|
),
|
|
)
|
|
.await;
|
|
|
|
for (index, rx_approval_distribution_worker) in
|
|
rx_approval_distribution_workers.iter_mut().enumerate()
|
|
{
|
|
if index == validator_index.0 as usize % num_approval_distro_workers {
|
|
assert_matches!(rx_approval_distribution_worker.next().await.unwrap(),
|
|
FromOrchestra::Communication {
|
|
msg: ApprovalDistributionMessage::NetworkBridgeUpdate(
|
|
pezkuwi_node_subsystem::messages::NetworkBridgeEvent::PeerMessage(
|
|
_,
|
|
msg,
|
|
),
|
|
)
|
|
} => {
|
|
assert_eq!(msg, expected_msg);
|
|
}
|
|
);
|
|
} else {
|
|
assert!(rx_approval_distribution_worker
|
|
.next()
|
|
.timeout(Duration::from_millis(200))
|
|
.await
|
|
.is_none());
|
|
}
|
|
}
|
|
assert!(approval_voting_work_provider
|
|
.recv()
|
|
.timeout(Duration::from_millis(200))
|
|
.await
|
|
.is_none());
|
|
|
|
assert!(approval_voting_work_provider
|
|
.recv()
|
|
.timeout(Duration::from_millis(200))
|
|
.await
|
|
.is_none());
|
|
|
|
// 7. Check NetworkBridgeUpdate::PeerConnected is correctly forwarded to all workers.
|
|
let expected_peer_id = PeerId::random();
|
|
overseer_message(
|
|
&mut overseer,
|
|
ApprovalVotingParallelMessage::NetworkBridgeUpdate(
|
|
pezkuwi_node_subsystem::messages::NetworkBridgeEvent::PeerConnected(
|
|
expected_peer_id,
|
|
ObservedRole::Authority,
|
|
ValidationVersion::V3.into(),
|
|
None,
|
|
),
|
|
),
|
|
)
|
|
.await;
|
|
|
|
for rx_approval_distribution_worker in rx_approval_distribution_workers.iter_mut() {
|
|
assert_matches!(rx_approval_distribution_worker.next().await.unwrap(),
|
|
FromOrchestra::Communication {
|
|
msg: ApprovalDistributionMessage::NetworkBridgeUpdate(
|
|
pezkuwi_node_subsystem::messages::NetworkBridgeEvent::PeerConnected(
|
|
peer_id,
|
|
role,
|
|
version,
|
|
authority_id,
|
|
),
|
|
)
|
|
} => {
|
|
assert_eq!(peer_id, expected_peer_id);
|
|
assert_eq!(role, ObservedRole::Authority);
|
|
assert_eq!(version, ValidationVersion::V3.into());
|
|
assert_eq!(authority_id, None);
|
|
}
|
|
);
|
|
}
|
|
assert!(approval_voting_work_provider
|
|
.recv()
|
|
.timeout(Duration::from_millis(200))
|
|
.await
|
|
.is_none());
|
|
|
|
// 8. Check ApprovalCheckingLagUpdate is correctly forwarded to all workers.
|
|
overseer_message(
|
|
&mut overseer,
|
|
ApprovalVotingParallelMessage::ApprovalCheckingLagUpdate(7),
|
|
)
|
|
.await;
|
|
|
|
for rx_approval_distribution_worker in rx_approval_distribution_workers.iter_mut() {
|
|
assert_matches!(rx_approval_distribution_worker.next().await.unwrap(),
|
|
FromOrchestra::Communication {
|
|
msg: ApprovalDistributionMessage::ApprovalCheckingLagUpdate(
|
|
lag
|
|
)
|
|
} => {
|
|
assert_eq!(lag, 7);
|
|
}
|
|
);
|
|
}
|
|
assert!(approval_voting_work_provider
|
|
.recv()
|
|
.timeout(Duration::from_millis(200))
|
|
.await
|
|
.is_none());
|
|
|
|
overseer_signal(&mut overseer, OverseerSignal::Conclude).await;
|
|
|
|
overseer
|
|
},
|
|
);
|
|
}
|
|
|
|
/// Test GetApprovalSignatures correctly gatheres the signatures from all workers.
|
|
#[test]
|
|
fn test_handle_get_approval_signatures() {
|
|
let num_approval_distro_workers = 4;
|
|
|
|
test_harness(
|
|
num_approval_distro_workers,
|
|
prio_right,
|
|
true,
|
|
|mut overseer, mut approval_voting_work_provider, mut rx_approval_distribution_workers| async move {
|
|
let (tx, rx) = oneshot::channel();
|
|
let first_block = Hash::random();
|
|
let second_block = Hash::random();
|
|
let expected_candidates: HashSet<_> =
|
|
vec![(first_block, 2), (second_block, 3)].into_iter().collect();
|
|
|
|
overseer_message(
|
|
&mut overseer,
|
|
ApprovalVotingParallelMessage::GetApprovalSignatures(
|
|
expected_candidates.clone(),
|
|
tx,
|
|
),
|
|
)
|
|
.await;
|
|
|
|
assert!(approval_voting_work_provider
|
|
.recv()
|
|
.timeout(Duration::from_millis(200))
|
|
.await
|
|
.is_none());
|
|
let mut all_votes = HashMap::new();
|
|
for (index, rx_approval_distribution_worker) in
|
|
rx_approval_distribution_workers.iter_mut().enumerate()
|
|
{
|
|
assert_matches!(rx_approval_distribution_worker.next().await.unwrap(),
|
|
FromOrchestra::Communication {
|
|
msg: ApprovalDistributionMessage::GetApprovalSignatures(
|
|
candidates, tx
|
|
)
|
|
} => {
|
|
assert_eq!(candidates, expected_candidates);
|
|
let to_send: HashMap<_, _> = {0..10}.into_iter().map(|validator| {
|
|
let validator_index = ValidatorIndex(validator as u32 * num_approval_distro_workers as u32 + index as u32);
|
|
(validator_index, (first_block, vec![2, 4], dummy_signature()))
|
|
}).collect();
|
|
tx.send(to_send.clone()).unwrap();
|
|
all_votes.extend(to_send.clone());
|
|
|
|
}
|
|
);
|
|
}
|
|
|
|
let received_votes = rx.await.unwrap();
|
|
assert_eq!(received_votes, all_votes);
|
|
overseer_signal(&mut overseer, OverseerSignal::Conclude).await;
|
|
|
|
overseer
|
|
},
|
|
)
|
|
}
|
|
|
|
/// Test subsystem exits with error when approval_voting_work_provider exits.
|
|
#[test]
|
|
fn test_subsystem_exits_with_error_if_approval_voting_worker_errors() {
|
|
let num_approval_distro_workers = 4;
|
|
|
|
test_harness(
|
|
num_approval_distro_workers,
|
|
prio_right,
|
|
false,
|
|
|overseer, approval_voting_work_provider, _rx_approval_distribution_workers| async move {
|
|
// Drop the approval_voting_work_provider to simulate an error.
|
|
std::mem::drop(approval_voting_work_provider);
|
|
|
|
overseer
|
|
},
|
|
)
|
|
}
|
|
|
|
/// Test subsystem exits with error when approval_distribution_workers exits.
|
|
#[test]
|
|
fn test_subsystem_exits_with_error_if_approval_distribution_worker_errors() {
|
|
let num_approval_distro_workers = 4;
|
|
|
|
test_harness(
|
|
num_approval_distro_workers,
|
|
prio_right,
|
|
false,
|
|
|overseer, _approval_voting_work_provider, rx_approval_distribution_workers| async move {
|
|
// Drop the approval_distribution_workers to simulate an error.
|
|
std::mem::drop(rx_approval_distribution_workers.into_iter().next().unwrap());
|
|
overseer
|
|
},
|
|
)
|
|
}
|
|
|
|
/// Test signals sent before messages are processed in order.
|
|
#[test]
|
|
fn test_signal_before_message_keeps_receive_order() {
|
|
let num_approval_distro_workers = 4;
|
|
|
|
test_harness(
|
|
num_approval_distro_workers,
|
|
prio_right,
|
|
true,
|
|
|mut overseer, mut approval_voting_work_provider, mut rx_approval_distribution_workers| async move {
|
|
let signal = OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(new_leaf(
|
|
Hash::random(),
|
|
1,
|
|
)));
|
|
overseer_signal(&mut overseer, signal.clone()).await;
|
|
|
|
let validator_index = ValidatorIndex(17);
|
|
let assignment =
|
|
fake_assignment_cert_v2(Hash::random(), validator_index, CoreIndex(1).into());
|
|
overseer_message(
|
|
&mut overseer,
|
|
ApprovalVotingParallelMessage::DistributeAssignment(assignment.clone(), 1.into()),
|
|
)
|
|
.await;
|
|
|
|
let approval_voting_receives = approval_voting_work_provider.recv().await.unwrap();
|
|
assert_matches!(approval_voting_receives, FromOrchestra::Signal(_));
|
|
let rx_approval_distribution_worker = rx_approval_distribution_workers
|
|
.get_mut(validator_index.0 as usize % num_approval_distro_workers)
|
|
.unwrap();
|
|
let approval_distribution_receives =
|
|
rx_approval_distribution_worker.next().await.unwrap();
|
|
assert_matches!(approval_distribution_receives, FromOrchestra::Signal(_));
|
|
assert_matches!(
|
|
rx_approval_distribution_worker.next().await.unwrap(),
|
|
FromOrchestra::Communication {
|
|
msg: ApprovalDistributionMessage::DistributeAssignment(_, _)
|
|
}
|
|
);
|
|
|
|
overseer_signal(&mut overseer, OverseerSignal::Conclude).await;
|
|
overseer
|
|
},
|
|
)
|
|
}
|
|
|
|
/// Test signals sent after messages are processed with the highest priority.
|
|
#[test]
|
|
fn test_signal_is_prioritized_when_unread_messages_in_the_queue() {
|
|
let num_approval_distro_workers = 4;
|
|
|
|
test_harness(
|
|
num_approval_distro_workers,
|
|
prio_right,
|
|
true,
|
|
|mut overseer, mut approval_voting_work_provider, mut rx_approval_distribution_workers| async move {
|
|
let validator_index = ValidatorIndex(17);
|
|
let assignment =
|
|
fake_assignment_cert_v2(Hash::random(), validator_index, CoreIndex(1).into());
|
|
overseer_message(
|
|
&mut overseer,
|
|
ApprovalVotingParallelMessage::DistributeAssignment(assignment.clone(), 1.into()),
|
|
)
|
|
.await;
|
|
|
|
let signal = OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(new_leaf(
|
|
Hash::random(),
|
|
1,
|
|
)));
|
|
overseer_signal(&mut overseer, signal.clone()).await;
|
|
|
|
let approval_voting_receives = approval_voting_work_provider.recv().await.unwrap();
|
|
assert_matches!(approval_voting_receives, FromOrchestra::Signal(_));
|
|
let rx_approval_distribution_worker = rx_approval_distribution_workers
|
|
.get_mut(validator_index.0 as usize % num_approval_distro_workers)
|
|
.unwrap();
|
|
let approval_distribution_receives =
|
|
rx_approval_distribution_worker.next().await.unwrap();
|
|
assert_matches!(approval_distribution_receives, FromOrchestra::Signal(_));
|
|
assert_matches!(
|
|
rx_approval_distribution_worker.next().await.unwrap(),
|
|
FromOrchestra::Communication {
|
|
msg: ApprovalDistributionMessage::DistributeAssignment(_, _)
|
|
}
|
|
);
|
|
|
|
overseer_signal(&mut overseer, OverseerSignal::Conclude).await;
|
|
overseer
|
|
},
|
|
)
|
|
}
|
|
|
|
/// Test peer view updates have higher priority than normal messages.
|
|
#[test]
|
|
fn test_peer_view_is_prioritized_when_unread_messages_in_the_queue() {
|
|
let num_approval_distro_workers = 4;
|
|
|
|
test_harness(
|
|
num_approval_distro_workers,
|
|
prio_right,
|
|
true,
|
|
|mut overseer, mut approval_voting_work_provider, mut rx_approval_distribution_workers| async move {
|
|
let validator_index = ValidatorIndex(17);
|
|
let approvals = vec![
|
|
IndirectSignedApprovalVoteV2 {
|
|
block_hash: H256::random(),
|
|
candidate_indices: 1.into(),
|
|
validator: validator_index,
|
|
signature: dummy_signature(),
|
|
},
|
|
IndirectSignedApprovalVoteV2 {
|
|
block_hash: H256::random(),
|
|
candidate_indices: 2.into(),
|
|
validator: validator_index,
|
|
signature: dummy_signature(),
|
|
},
|
|
];
|
|
let expected_msg = pezkuwi_node_network_protocol::ValidationProtocols::V3(
|
|
pezkuwi_node_network_protocol::v3::ApprovalDistributionMessage::Approvals(
|
|
approvals.clone(),
|
|
),
|
|
);
|
|
overseer_message(
|
|
&mut overseer,
|
|
ApprovalVotingParallelMessage::NetworkBridgeUpdate(
|
|
pezkuwi_node_subsystem::messages::NetworkBridgeEvent::PeerMessage(
|
|
PeerId::random(),
|
|
expected_msg.clone(),
|
|
),
|
|
),
|
|
)
|
|
.await;
|
|
|
|
overseer_message(
|
|
&mut overseer,
|
|
ApprovalVotingParallelMessage::NetworkBridgeUpdate(
|
|
pezkuwi_node_subsystem::messages::NetworkBridgeEvent::PeerViewChange(
|
|
PeerId::random(),
|
|
View::default(),
|
|
),
|
|
),
|
|
)
|
|
.await;
|
|
|
|
for (index, rx_approval_distribution_worker) in
|
|
rx_approval_distribution_workers.iter_mut().enumerate()
|
|
{
|
|
assert_matches!(rx_approval_distribution_worker.next().await.unwrap(),
|
|
FromOrchestra::Communication {
|
|
msg: ApprovalDistributionMessage::NetworkBridgeUpdate(
|
|
pezkuwi_node_subsystem::messages::NetworkBridgeEvent::PeerViewChange(
|
|
_,
|
|
_,
|
|
),
|
|
)
|
|
} => {
|
|
}
|
|
);
|
|
if index == validator_index.0 as usize % num_approval_distro_workers {
|
|
assert_matches!(rx_approval_distribution_worker.next().await.unwrap(),
|
|
FromOrchestra::Communication {
|
|
msg: ApprovalDistributionMessage::NetworkBridgeUpdate(
|
|
pezkuwi_node_subsystem::messages::NetworkBridgeEvent::PeerMessage(
|
|
_,
|
|
msg,
|
|
),
|
|
)
|
|
} => {
|
|
assert_eq!(msg, expected_msg);
|
|
}
|
|
);
|
|
} else {
|
|
assert!(rx_approval_distribution_worker
|
|
.next()
|
|
.timeout(Duration::from_millis(200))
|
|
.await
|
|
.is_none());
|
|
}
|
|
}
|
|
|
|
assert!(approval_voting_work_provider
|
|
.recv()
|
|
.timeout(Duration::from_millis(200))
|
|
.await
|
|
.is_none());
|
|
|
|
overseer_signal(&mut overseer, OverseerSignal::Conclude).await;
|
|
overseer
|
|
},
|
|
)
|
|
}
|
|
|
|
// Test validator_index_for_msg with empty messages.
|
|
#[test]
|
|
fn test_validator_index_with_empty_message() {
|
|
let result = validator_index_for_msg(pezkuwi_node_network_protocol::ValidationProtocols::V3(
|
|
pezkuwi_node_network_protocol::v3::ApprovalDistributionMessage::Assignments(vec![]),
|
|
));
|
|
|
|
assert_eq!(result, (None, Some(vec![])));
|
|
|
|
let result = validator_index_for_msg(pezkuwi_node_network_protocol::ValidationProtocols::V3(
|
|
pezkuwi_node_network_protocol::v3::ApprovalDistributionMessage::Approvals(vec![]),
|
|
));
|
|
|
|
assert_eq!(result, (None, Some(vec![])));
|
|
}
|
|
|
|
// Test validator_index_for_msg when all the messages are originating from the same validator.
|
|
#[test]
|
|
fn test_validator_index_with_all_messages_from_the_same_validator() {
|
|
let validator_index = ValidatorIndex(3);
|
|
let v3_assignment = pezkuwi_node_network_protocol::ValidationProtocols::V3(
|
|
pezkuwi_node_network_protocol::v3::ApprovalDistributionMessage::Assignments(vec![
|
|
(
|
|
fake_assignment_cert_v2(H256::random(), validator_index, CoreIndex(1).into()),
|
|
1.into(),
|
|
),
|
|
(
|
|
fake_assignment_cert_v2(H256::random(), validator_index, CoreIndex(3).into()),
|
|
3.into(),
|
|
),
|
|
]),
|
|
);
|
|
let result = validator_index_for_msg(v3_assignment.clone());
|
|
|
|
assert_eq!(result, (Some((validator_index, v3_assignment)), None));
|
|
|
|
let v3_approval = pezkuwi_node_network_protocol::ValidationProtocols::V3(
|
|
pezkuwi_node_network_protocol::v3::ApprovalDistributionMessage::Approvals(vec![
|
|
IndirectSignedApprovalVoteV2 {
|
|
block_hash: H256::random(),
|
|
candidate_indices: 1.into(),
|
|
validator: validator_index,
|
|
signature: dummy_signature(),
|
|
},
|
|
IndirectSignedApprovalVoteV2 {
|
|
block_hash: H256::random(),
|
|
candidate_indices: 1.into(),
|
|
validator: validator_index,
|
|
signature: dummy_signature(),
|
|
},
|
|
]),
|
|
);
|
|
let result = validator_index_for_msg(v3_approval.clone());
|
|
|
|
assert_eq!(result, (Some((validator_index, v3_approval)), None));
|
|
}
|
|
|
|
// Test validator_index_for_msg when all the messages are originating from different validators,
|
|
// so the function should split them by validator index, so we can forward them separately to the
|
|
// worker they are assigned to.
|
|
#[test]
|
|
fn test_validator_index_with_messages_from_different_validators() {
|
|
let first_validator_index = ValidatorIndex(3);
|
|
let second_validator_index = ValidatorIndex(4);
|
|
let assignments = vec![
|
|
(
|
|
fake_assignment_cert_v2(H256::random(), first_validator_index, CoreIndex(1).into()),
|
|
1.into(),
|
|
),
|
|
(
|
|
fake_assignment_cert_v2(H256::random(), second_validator_index, CoreIndex(3).into()),
|
|
3.into(),
|
|
),
|
|
];
|
|
|
|
let v3_assignment = pezkuwi_node_network_protocol::ValidationProtocols::V3(
|
|
pezkuwi_node_network_protocol::v3::ApprovalDistributionMessage::Assignments(
|
|
assignments.clone(),
|
|
),
|
|
);
|
|
let result = validator_index_for_msg(v3_assignment.clone());
|
|
|
|
assert_matches!(result, (None, Some(_)));
|
|
let messsages_split_by_validator = result.1.unwrap();
|
|
assert_eq!(messsages_split_by_validator.len(), assignments.len());
|
|
for (index, (validator_index, message)) in messsages_split_by_validator.into_iter().enumerate()
|
|
{
|
|
assert_eq!(validator_index, assignments[index].0.validator);
|
|
assert_eq!(
|
|
message,
|
|
pezkuwi_node_network_protocol::ValidationProtocols::V3(
|
|
pezkuwi_node_network_protocol::v3::ApprovalDistributionMessage::Assignments(
|
|
assignments.get(index).into_iter().cloned().collect(),
|
|
),
|
|
)
|
|
);
|
|
}
|
|
|
|
let approvals = vec![
|
|
IndirectSignedApprovalVoteV2 {
|
|
block_hash: H256::random(),
|
|
candidate_indices: 1.into(),
|
|
validator: first_validator_index,
|
|
signature: dummy_signature(),
|
|
},
|
|
IndirectSignedApprovalVoteV2 {
|
|
block_hash: H256::random(),
|
|
candidate_indices: 2.into(),
|
|
validator: second_validator_index,
|
|
signature: dummy_signature(),
|
|
},
|
|
];
|
|
let v3_approvals = pezkuwi_node_network_protocol::ValidationProtocols::V3(
|
|
pezkuwi_node_network_protocol::v3::ApprovalDistributionMessage::Approvals(
|
|
approvals.clone(),
|
|
),
|
|
);
|
|
let result = validator_index_for_msg(v3_approvals.clone());
|
|
|
|
assert_matches!(result, (None, Some(_)));
|
|
let messsages_split_by_validator = result.1.unwrap();
|
|
assert_eq!(messsages_split_by_validator.len(), approvals.len());
|
|
for (index, (validator_index, message)) in messsages_split_by_validator.into_iter().enumerate()
|
|
{
|
|
assert_eq!(validator_index, approvals[index].validator);
|
|
assert_eq!(
|
|
message,
|
|
pezkuwi_node_network_protocol::ValidationProtocols::V3(
|
|
pezkuwi_node_network_protocol::v3::ApprovalDistributionMessage::Approvals(
|
|
approvals.get(index).into_iter().cloned().collect(),
|
|
),
|
|
)
|
|
);
|
|
}
|
|
}
|