Files
pezkuwi-subxt/polkadot/node/network/bridge/src/rx/tests.rs
T
Aaro Altonen 80616f6d03 Integrate litep2p into Polkadot SDK (#2944)
[litep2p](https://github.com/altonen/litep2p) is a libp2p-compatible P2P
networking library. It supports all of the features of `rust-libp2p`
that are currently being utilized by Polkadot SDK.

Compared to `rust-libp2p`, `litep2p` has a quite different architecture
which is why the new `litep2p` network backend is only able to use a
little of the existing code in `sc-network`. The design has been mainly
influenced by how we'd wish to structure our networking-related code in
Polkadot SDK: independent higher-levels protocols directly communicating
with the network over links that support bidirectional backpressure. A
good example would be `NotificationHandle`/`RequestResponseHandle`
abstractions which allow, e.g., `SyncingEngine` to directly communicate
with peers to announce/request blocks.

I've tried running `polkadot --network-backend litep2p` with a few
different peer configurations and there is a noticeable reduction in
networking CPU usage. For high load (`--out-peers 200`), networking CPU
usage goes down from ~110% to ~30% (80 pp) and for normal load
(`--out-peers 40`), the usage goes down from ~55% to ~18% (37 pp).

These should not be taken as final numbers because:

a) there are still some low-hanging optimization fruits, such as
enabling [receive window
auto-tuning](https://github.com/libp2p/rust-yamux/pull/176), integrating
`Peerset` more closely with `litep2p` or improving memory usage of the
WebSocket transport
b) fixing bugs/instabilities that incorrectly cause `litep2p` to do less
work will increase the networking CPU usage
c) verification in a more diverse set of tests/conditions is needed

Nevertheless, these numbers should give an early estimate for CPU usage
of the new networking backend.

This PR consists of three separate changes:
* introduce a generic `PeerId` (wrapper around `Multihash`) so that we
don't have use `NetworkService::PeerId` in every part of the code that
uses a `PeerId`
* introduce `NetworkBackend` trait, implement it for the libp2p network
stack and make Polkadot SDK generic over `NetworkBackend`
  * implement `NetworkBackend` for litep2p

The new library should be considered experimental which is why
`rust-libp2p` will remain as the default option for the time being. This
PR currently depends on the master branch of `litep2p` but I'll cut a
new release for the library once all review comments have been
addresses.

---------

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Co-authored-by: Dmitry Markin <dmitry@markin.tech>
Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>
Co-authored-by: Alexandru Vasile <alexandru.vasile@parity.io>
2024-04-08 16:44:13 +00:00

1553 lines
41 KiB
Rust

// 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 super::*;
use futures::{channel::oneshot, executor};
use overseer::jaeger;
use polkadot_node_network_protocol::{self as net_protocol, OurView};
use polkadot_node_subsystem::messages::NetworkBridgeEvent;
use assert_matches::assert_matches;
use async_trait::async_trait;
use parking_lot::Mutex;
use std::{
collections::HashSet,
sync::atomic::{AtomicBool, Ordering},
};
use sc_network::{
service::traits::{Direction, MessageSink, NotificationService},
IfDisconnected, Multiaddr, ObservedRole as SubstrateObservedRole, ProtocolName,
ReputationChange, Roles,
};
use polkadot_node_network_protocol::{
peer_set::PeerSetProtocolNames,
request_response::{outgoing::Requests, ReqProtocolNames},
view, ObservedRole, Versioned,
};
use polkadot_node_subsystem::{
messages::{
AllMessages, ApprovalDistributionMessage, BitfieldDistributionMessage,
GossipSupportMessage, StatementDistributionMessage,
},
ActiveLeavesUpdate, FromOrchestra, OverseerSignal,
};
use polkadot_node_subsystem_test_helpers::{
mock::new_leaf, SingleItemSink, SingleItemStream, TestSubsystemContextHandle,
};
use polkadot_node_subsystem_util::metered;
use polkadot_primitives::{AuthorityDiscoveryId, Hash};
use sp_keyring::Sr25519Keyring;
use crate::{network::Network, validator_discovery::AuthorityDiscovery};
#[derive(Debug, PartialEq)]
pub enum NetworkAction {
/// Note a change in reputation for a peer.
ReputationChange(PeerId, ReputationChange),
/// Disconnect a peer from the given peer-set.
DisconnectPeer(PeerId, PeerSet),
/// Write a notification to a given peer on the given peer-set.
WriteNotification(PeerId, PeerSet, Vec<u8>),
}
// The subsystem's view of the network.
#[derive(Clone)]
struct TestNetwork {
action_tx: Arc<Mutex<metered::UnboundedMeteredSender<NetworkAction>>>,
protocol_names: Arc<PeerSetProtocolNames>,
}
#[derive(Clone, Debug)]
struct TestAuthorityDiscovery;
// The test's view of the network. This receives updates from the subsystem in the form
// of `NetworkAction`s.
struct TestNetworkHandle {
action_rx: metered::UnboundedMeteredReceiver<NetworkAction>,
validation_tx: SingleItemSink<NotificationEvent>,
collation_tx: SingleItemSink<NotificationEvent>,
}
fn new_test_network(
protocol_names: PeerSetProtocolNames,
) -> (
TestNetwork,
TestNetworkHandle,
TestAuthorityDiscovery,
Box<dyn NotificationService>,
Box<dyn NotificationService>,
) {
let (action_tx, action_rx) = metered::unbounded();
let (validation_tx, validation_rx) = polkadot_node_subsystem_test_helpers::single_item_sink();
let (collation_tx, collation_rx) = polkadot_node_subsystem_test_helpers::single_item_sink();
let action_tx = Arc::new(Mutex::new(action_tx));
(
TestNetwork {
action_tx: action_tx.clone(),
protocol_names: Arc::new(protocol_names.clone()),
},
TestNetworkHandle { action_rx, validation_tx, collation_tx },
TestAuthorityDiscovery,
Box::new(TestNotificationService::new(
PeerSet::Validation,
action_tx.clone(),
validation_rx,
)),
Box::new(TestNotificationService::new(PeerSet::Collation, action_tx, collation_rx)),
)
}
#[async_trait]
impl Network for TestNetwork {
async fn set_reserved_peers(
&mut self,
_protocol: ProtocolName,
_: HashSet<Multiaddr>,
) -> Result<(), String> {
Ok(())
}
async fn remove_from_peers_set(
&mut self,
_protocol: ProtocolName,
_: Vec<PeerId>,
) -> Result<(), String> {
Ok(())
}
async fn start_request<AD: AuthorityDiscovery>(
&self,
_: &mut AD,
_: Requests,
_: &ReqProtocolNames,
_: IfDisconnected,
) {
}
fn report_peer(&self, who: PeerId, rep: ReputationChange) {
self.action_tx
.lock()
.unbounded_send(NetworkAction::ReputationChange(who, rep))
.unwrap();
}
fn disconnect_peer(&self, who: PeerId, protocol: ProtocolName) {
let (peer_set, version) = self.protocol_names.try_get_protocol(&protocol).unwrap();
assert_eq!(version, peer_set.get_main_version());
self.action_tx
.lock()
.unbounded_send(NetworkAction::DisconnectPeer(who, peer_set))
.unwrap();
}
fn peer_role(&self, _peer_id: PeerId, handshake: Vec<u8>) -> Option<SubstrateObservedRole> {
Roles::decode_all(&mut &handshake[..])
.ok()
.and_then(|role| Some(SubstrateObservedRole::from(role)))
}
}
#[async_trait]
impl validator_discovery::AuthorityDiscovery for TestAuthorityDiscovery {
async fn get_addresses_by_authority_id(
&mut self,
_authority: AuthorityDiscoveryId,
) -> Option<HashSet<Multiaddr>> {
None
}
async fn get_authority_ids_by_peer_id(
&mut self,
_peer_id: PeerId,
) -> Option<HashSet<AuthorityDiscoveryId>> {
None
}
}
impl TestNetworkHandle {
// Get the next network action.
async fn next_network_action(&mut self) -> NetworkAction {
self.action_rx.next().await.expect("subsystem concluded early")
}
// Wait for the next N network actions.
async fn next_network_actions(&mut self, n: usize) -> Vec<NetworkAction> {
let mut v = Vec::with_capacity(n);
for _ in 0..n {
v.push(self.next_network_action().await);
}
v
}
async fn connect_peer(
&mut self,
peer: PeerId,
protocol_version: ValidationVersion,
peer_set: PeerSet,
role: ObservedRole,
) {
fn observed_role_to_handshake(role: &ObservedRole) -> Vec<u8> {
match role {
&ObservedRole::Light => Roles::LIGHT.encode(),
&ObservedRole::Authority => Roles::AUTHORITY.encode(),
&ObservedRole::Full => Roles::FULL.encode(),
}
}
// because of how protocol negotiation works, if two peers support at least one common
// protocol, the protocol is negotiated over the main protocol (`ValidationVersion::V2`) but
// if either one of the peers used a fallback protocol for the negotiation (meaning they
// don't support the main protocol but some older version of it ), `negotiated_fallback` is
// set to that protocol.
let negotiated_fallback = match protocol_version {
ValidationVersion::V2 => None,
ValidationVersion::V1 => match peer_set {
PeerSet::Validation => Some(ProtocolName::from("/polkadot/validation/1")),
PeerSet::Collation => Some(ProtocolName::from("/polkadot/collation/1")),
},
ValidationVersion::V3 => match peer_set {
PeerSet::Validation => Some(ProtocolName::from("/polkadot/validation/3")),
PeerSet::Collation => unreachable!(),
},
};
match peer_set {
PeerSet::Validation => {
self.validation_tx
.send(NotificationEvent::NotificationStreamOpened {
peer,
direction: Direction::Inbound,
handshake: observed_role_to_handshake(&role),
negotiated_fallback,
})
.await
.expect("subsystem concluded early");
},
PeerSet::Collation => {
self.collation_tx
.send(NotificationEvent::NotificationStreamOpened {
peer,
direction: Direction::Inbound,
handshake: observed_role_to_handshake(&role),
negotiated_fallback,
})
.await
.expect("subsystem concluded early");
},
}
}
async fn disconnect_peer(&mut self, peer: PeerId, peer_set: PeerSet) {
match peer_set {
PeerSet::Validation => self
.validation_tx
.send(NotificationEvent::NotificationStreamClosed { peer })
.await
.expect("subsystem concluded early"),
PeerSet::Collation => self
.collation_tx
.send(NotificationEvent::NotificationStreamClosed { peer })
.await
.expect("subsystem concluded early"),
}
}
async fn peer_message(&mut self, peer: PeerId, peer_set: PeerSet, message: Vec<u8>) {
match peer_set {
PeerSet::Validation => self
.validation_tx
.send(NotificationEvent::NotificationReceived { peer, notification: message })
.await
.expect("subsystem concluded early"),
PeerSet::Collation => self
.collation_tx
.send(NotificationEvent::NotificationReceived { peer, notification: message })
.await
.expect("subsystem concluded early"),
}
}
}
/// Assert that the given actions contain the given `action`.
fn assert_network_actions_contains(actions: &[NetworkAction], action: &NetworkAction) {
if !actions.iter().any(|x| x == action) {
panic!("Could not find `{:?}` in `{:?}`", action, actions);
}
}
struct TestNotificationService {
peer_set: PeerSet,
action_tx: Arc<Mutex<metered::UnboundedMeteredSender<NetworkAction>>>,
rx: SingleItemStream<NotificationEvent>,
}
impl std::fmt::Debug for TestNotificationService {
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}
impl TestNotificationService {
pub fn new(
peer_set: PeerSet,
action_tx: Arc<Mutex<metered::UnboundedMeteredSender<NetworkAction>>>,
rx: SingleItemStream<NotificationEvent>,
) -> Self {
Self { peer_set, action_tx, rx }
}
}
struct TestMessageSink {
peer: PeerId,
peer_set: PeerSet,
action_tx: Arc<Mutex<metered::UnboundedMeteredSender<NetworkAction>>>,
}
impl TestMessageSink {
fn new(
peer: PeerId,
peer_set: PeerSet,
action_tx: Arc<Mutex<metered::UnboundedMeteredSender<NetworkAction>>>,
) -> TestMessageSink {
Self { peer, peer_set, action_tx }
}
}
#[async_trait::async_trait]
impl MessageSink for TestMessageSink {
fn send_sync_notification(&self, notification: Vec<u8>) {
self.action_tx
.lock()
.unbounded_send(NetworkAction::WriteNotification(
self.peer,
self.peer_set,
notification,
))
.unwrap();
}
async fn send_async_notification(
&self,
_notification: Vec<u8>,
) -> Result<(), sc_network::error::Error> {
unimplemented!();
}
}
#[async_trait::async_trait]
impl NotificationService for TestNotificationService {
/// Instruct `Notifications` to open a new substream for `peer`.
async fn open_substream(&mut self, _peer: PeerId) -> Result<(), ()> {
unimplemented!();
}
/// Instruct `Notifications` to close substream for `peer`.
async fn close_substream(&mut self, _peer: PeerId) -> Result<(), ()> {
unimplemented!();
}
/// Send synchronous `notification` to `peer`.
fn send_sync_notification(&mut self, _peer: &PeerId, _notification: Vec<u8>) {
unimplemented!();
}
/// Send asynchronous `notification` to `peer`, allowing sender to exercise backpressure.
async fn send_async_notification(
&mut self,
_peer: &PeerId,
_notification: Vec<u8>,
) -> Result<(), sc_network::error::Error> {
unimplemented!();
}
/// Set handshake for the notification protocol replacing the old handshake.
async fn set_handshake(&mut self, _handshake: Vec<u8>) -> Result<(), ()> {
unimplemented!();
}
fn try_set_handshake(&mut self, _handshake: Vec<u8>) -> Result<(), ()> {
unimplemented!();
}
/// Get next event from the `Notifications` event stream.
async fn next_event(&mut self) -> Option<NotificationEvent> {
self.rx.next().await
}
// Clone [`NotificationService`]
fn clone(&mut self) -> Result<Box<dyn NotificationService>, ()> {
unimplemented!();
}
/// Get protocol name.
fn protocol(&self) -> &ProtocolName {
unimplemented!();
}
/// Get notification sink of the peer.
fn message_sink(&self, peer: &PeerId) -> Option<Box<dyn MessageSink>> {
Some(Box::new(TestMessageSink::new(*peer, self.peer_set, self.action_tx.clone())))
}
}
#[derive(Clone)]
struct TestSyncOracle {
is_major_syncing: Arc<AtomicBool>,
done_syncing_sender: Arc<Mutex<Option<oneshot::Sender<()>>>>,
}
struct TestSyncOracleHandle {
done_syncing_receiver: oneshot::Receiver<()>,
is_major_syncing: Arc<AtomicBool>,
}
impl TestSyncOracleHandle {
fn set_done(&self) {
self.is_major_syncing.store(false, Ordering::SeqCst);
}
async fn await_mode_switch(self) {
let _ = self.done_syncing_receiver.await;
}
}
impl SyncOracle for TestSyncOracle {
fn is_major_syncing(&self) -> bool {
let is_major_syncing = self.is_major_syncing.load(Ordering::SeqCst);
if !is_major_syncing {
if let Some(sender) = self.done_syncing_sender.lock().take() {
let _ = sender.send(());
}
}
is_major_syncing
}
fn is_offline(&self) -> bool {
unimplemented!("not used in network bridge")
}
}
// val - result of `is_major_syncing`.
fn make_sync_oracle(is_major_syncing: bool) -> (TestSyncOracle, TestSyncOracleHandle) {
let (tx, rx) = oneshot::channel();
let is_major_syncing = Arc::new(AtomicBool::new(is_major_syncing));
(
TestSyncOracle {
is_major_syncing: is_major_syncing.clone(),
done_syncing_sender: Arc::new(Mutex::new(Some(tx))),
},
TestSyncOracleHandle { is_major_syncing, done_syncing_receiver: rx },
)
}
fn done_syncing_oracle() -> Box<dyn SyncOracle + Send> {
let (oracle, _) = make_sync_oracle(false);
Box::new(oracle)
}
type VirtualOverseer = TestSubsystemContextHandle<NetworkBridgeRxMessage>;
struct TestHarness {
network_handle: TestNetworkHandle,
virtual_overseer: VirtualOverseer,
shared: Shared,
}
// wait until all needed validation and collation peers have connected.
async fn await_peer_connections(
shared: &Shared,
num_validation_peers: usize,
num_collation_peers: usize,
) {
loop {
{
let shared = shared.0.lock();
if shared.validation_peers.len() == num_validation_peers &&
shared.collation_peers.len() == num_collation_peers
{
break
}
}
futures_timer::Delay::new(std::time::Duration::from_millis(100)).await;
}
}
fn test_harness<T: Future<Output = VirtualOverseer>>(
sync_oracle: Box<dyn SyncOracle + Send>,
test: impl FnOnce(TestHarness) -> T,
) {
let genesis_hash = Hash::repeat_byte(0xff);
let fork_id = None;
let peerset_protocol_names = PeerSetProtocolNames::new(genesis_hash, fork_id);
let pool = sp_core::testing::TaskExecutor::new();
let (network, network_handle, discovery, validation_service, collation_service) =
new_test_network(peerset_protocol_names.clone());
let (context, virtual_overseer) =
polkadot_node_subsystem_test_helpers::make_subsystem_context(pool);
let notification_sinks = Arc::new(Mutex::new(HashMap::new()));
let shared = Shared::default();
let bridge = NetworkBridgeRx {
network_service: network,
authority_discovery_service: discovery,
metrics: Metrics(None),
sync_oracle,
shared: shared.clone(),
peerset_protocol_names,
validation_service,
collation_service,
notification_sinks,
};
let network_bridge = run_network_in(bridge, context)
.map_err(|_| panic!("subsystem execution failed"))
.map(|_| ());
let test_fut = test(TestHarness { network_handle, virtual_overseer, shared });
futures::pin_mut!(test_fut);
futures::pin_mut!(network_bridge);
let _ = executor::block_on(future::join(
async move {
let mut virtual_overseer = test_fut.await;
virtual_overseer.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
},
network_bridge,
));
}
async fn assert_sends_validation_event_to_all(
event: NetworkBridgeEvent<net_protocol::VersionedValidationProtocol>,
virtual_overseer: &mut TestSubsystemContextHandle<NetworkBridgeRxMessage>,
) {
// Ordering must be consistent across:
// `fn dispatch_validation_event_to_all_unbounded`
// `dispatch_validation_events_to_all`
assert_matches!(
virtual_overseer.recv().await,
AllMessages::StatementDistribution(
StatementDistributionMessage::NetworkBridgeUpdate(e)
) if e == event.focus().expect("could not focus message")
);
assert_matches!(
virtual_overseer.recv().await,
AllMessages::BitfieldDistribution(
BitfieldDistributionMessage::NetworkBridgeUpdate(e)
) if e == event.focus().expect("could not focus message")
);
assert_matches!(
virtual_overseer.recv().await,
AllMessages::ApprovalDistribution(
ApprovalDistributionMessage::NetworkBridgeUpdate(e)
) if e == event.focus().expect("could not focus message")
);
assert_matches!(
virtual_overseer.recv().await,
AllMessages::GossipSupport(
GossipSupportMessage::NetworkBridgeUpdate(e)
) if e == event.focus().expect("could not focus message")
);
}
async fn assert_sends_collation_event_to_all(
event: NetworkBridgeEvent<net_protocol::VersionedCollationProtocol>,
virtual_overseer: &mut TestSubsystemContextHandle<NetworkBridgeRxMessage>,
) {
assert_matches!(
virtual_overseer.recv().await,
AllMessages::CollatorProtocol(
CollatorProtocolMessage::NetworkBridgeUpdate(e)
) if e == event.focus().expect("could not focus message")
)
}
#[test]
fn send_our_view_upon_connection() {
let (oracle, handle) = make_sync_oracle(false);
test_harness(Box::new(oracle), |test_harness| async move {
let TestHarness { mut network_handle, mut virtual_overseer, shared } = test_harness;
let peer = PeerId::random();
let head = Hash::repeat_byte(1);
virtual_overseer
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(
ActiveLeavesUpdate::start_work(new_leaf(head, 1)),
)))
.await;
handle.await_mode_switch().await;
network_handle
.connect_peer(peer, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
.await;
network_handle
.connect_peer(peer, ValidationVersion::V1, PeerSet::Collation, ObservedRole::Full)
.await;
await_peer_connections(&shared, 1, 1).await;
let view = view![head];
let actions = network_handle.next_network_actions(2).await;
assert_network_actions_contains(
&actions,
&NetworkAction::WriteNotification(
peer,
PeerSet::Validation,
WireMessage::<protocol_v1::ValidationProtocol>::ViewUpdate(view.clone()).encode(),
),
);
assert_network_actions_contains(
&actions,
&NetworkAction::WriteNotification(
peer,
PeerSet::Collation,
WireMessage::<protocol_v1::CollationProtocol>::ViewUpdate(view.clone()).encode(),
),
);
virtual_overseer
});
}
#[test]
fn sends_view_updates_to_peers() {
let (oracle, handle) = make_sync_oracle(false);
test_harness(Box::new(oracle), |test_harness| async move {
let TestHarness { mut network_handle, mut virtual_overseer, shared } = test_harness;
let peer_a = PeerId::random();
let peer_b = PeerId::random();
virtual_overseer
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate {
activated: Default::default(),
deactivated: Default::default(),
})))
.await;
handle.await_mode_switch().await;
network_handle
.connect_peer(peer_a, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
.await;
network_handle
.connect_peer(peer_b, ValidationVersion::V1, PeerSet::Collation, ObservedRole::Full)
.await;
await_peer_connections(&shared, 1, 1).await;
let actions = network_handle.next_network_actions(2).await;
let wire_message =
WireMessage::<protocol_v1::ValidationProtocol>::ViewUpdate(View::default()).encode();
assert_network_actions_contains(
&actions,
&NetworkAction::WriteNotification(peer_a, PeerSet::Validation, wire_message.clone()),
);
assert_network_actions_contains(
&actions,
&NetworkAction::WriteNotification(peer_b, PeerSet::Collation, wire_message.clone()),
);
let hash_a = Hash::repeat_byte(1);
virtual_overseer
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(
ActiveLeavesUpdate::start_work(new_leaf(hash_a, 1)),
)))
.await;
let actions = network_handle.next_network_actions(2).await;
let wire_message =
WireMessage::<protocol_v1::ValidationProtocol>::ViewUpdate(view![hash_a]).encode();
assert_network_actions_contains(
&actions,
&NetworkAction::WriteNotification(peer_a, PeerSet::Validation, wire_message.clone()),
);
assert_network_actions_contains(
&actions,
&NetworkAction::WriteNotification(peer_b, PeerSet::Collation, wire_message.clone()),
);
virtual_overseer
});
}
#[test]
fn do_not_send_view_update_until_synced() {
let (oracle, handle) = make_sync_oracle(true);
test_harness(Box::new(oracle), |test_harness| async move {
let TestHarness { mut network_handle, mut virtual_overseer, shared } = test_harness;
let peer_a = PeerId::random();
let peer_b = PeerId::random();
assert_ne!(peer_a, peer_b);
network_handle
.connect_peer(peer_a, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
.await;
network_handle
.connect_peer(peer_b, ValidationVersion::V1, PeerSet::Collation, ObservedRole::Full)
.await;
await_peer_connections(&shared, 1, 1).await;
{
let actions = network_handle.next_network_actions(2).await;
let wire_message =
WireMessage::<protocol_v1::ValidationProtocol>::ViewUpdate(View::default())
.encode();
assert_network_actions_contains(
&actions,
&NetworkAction::WriteNotification(
peer_a,
PeerSet::Validation,
wire_message.clone(),
),
);
assert_network_actions_contains(
&actions,
&NetworkAction::WriteNotification(peer_b, PeerSet::Collation, wire_message.clone()),
);
}
let hash_a = Hash::repeat_byte(1);
let hash_b = Hash::repeat_byte(1);
virtual_overseer
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(
ActiveLeavesUpdate::start_work(new_leaf(hash_a, 1)),
)))
.await;
// delay until the previous update has certainly been processed.
futures_timer::Delay::new(std::time::Duration::from_millis(100)).await;
handle.set_done();
virtual_overseer
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(
ActiveLeavesUpdate::start_work(new_leaf(hash_b, 1)),
)))
.await;
handle.await_mode_switch().await;
// There should be a mode switch only for the second view update.
{
let actions = network_handle.next_network_actions(2).await;
let wire_message =
WireMessage::<protocol_v1::ValidationProtocol>::ViewUpdate(view![hash_a, hash_b])
.encode();
assert_network_actions_contains(
&actions,
&NetworkAction::WriteNotification(
peer_a,
PeerSet::Validation,
wire_message.clone(),
),
);
assert_network_actions_contains(
&actions,
&NetworkAction::WriteNotification(peer_b, PeerSet::Collation, wire_message.clone()),
);
}
virtual_overseer
});
}
#[test]
fn do_not_send_view_update_when_only_finalized_block_changed() {
test_harness(done_syncing_oracle(), |test_harness| async move {
let TestHarness { mut network_handle, mut virtual_overseer, shared } = test_harness;
let peer_a = PeerId::random();
let peer_b = PeerId::random();
network_handle
.connect_peer(peer_a, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
.await;
network_handle
.connect_peer(peer_b, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
.await;
await_peer_connections(&shared, 2, 0).await;
let hash_a = Hash::repeat_byte(1);
virtual_overseer
.send(FromOrchestra::Signal(OverseerSignal::BlockFinalized(Hash::random(), 5)))
.await;
// Send some empty active leaves update
//
// This should not trigger a view update to our peers.
virtual_overseer
.send(FromOrchestra::Signal(
OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::default()),
))
.await;
// This should trigger the view update to our peers.
virtual_overseer
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(
ActiveLeavesUpdate::start_work(new_leaf(hash_a, 1)),
)))
.await;
let actions = network_handle.next_network_actions(4).await;
let wire_message =
WireMessage::<protocol_v1::ValidationProtocol>::ViewUpdate(View::new(vec![hash_a], 5))
.encode();
assert_network_actions_contains(
&actions,
&NetworkAction::WriteNotification(peer_a, PeerSet::Validation, wire_message.clone()),
);
assert_network_actions_contains(
&actions,
&NetworkAction::WriteNotification(peer_b, PeerSet::Validation, wire_message.clone()),
);
virtual_overseer
});
}
#[test]
fn peer_view_updates_sent_via_overseer() {
test_harness(done_syncing_oracle(), |test_harness| async move {
let TestHarness { mut network_handle, mut virtual_overseer, shared } = test_harness;
let peer = PeerId::random();
network_handle
.connect_peer(peer, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
.await;
await_peer_connections(&shared, 1, 0).await;
let view = view![Hash::repeat_byte(1)];
// bridge will inform about all connected peers.
{
assert_sends_validation_event_to_all(
NetworkBridgeEvent::PeerConnected(
peer,
ObservedRole::Full,
ValidationVersion::V1.into(),
None,
),
&mut virtual_overseer,
)
.await;
assert_sends_validation_event_to_all(
NetworkBridgeEvent::PeerViewChange(peer, View::default()),
&mut virtual_overseer,
)
.await;
}
network_handle
.peer_message(
peer,
PeerSet::Validation,
WireMessage::<protocol_v1::ValidationProtocol>::ViewUpdate(view.clone()).encode(),
)
.await;
assert_sends_validation_event_to_all(
NetworkBridgeEvent::PeerViewChange(peer, view),
&mut virtual_overseer,
)
.await;
virtual_overseer
});
}
#[test]
fn peer_messages_sent_via_overseer() {
test_harness(done_syncing_oracle(), |test_harness| async move {
let TestHarness { mut network_handle, mut virtual_overseer, shared } = test_harness;
let peer = PeerId::random();
network_handle
.connect_peer(peer, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
.await;
await_peer_connections(&shared, 1, 0).await;
// bridge will inform about all connected peers.
{
assert_sends_validation_event_to_all(
NetworkBridgeEvent::PeerConnected(
peer,
ObservedRole::Full,
ValidationVersion::V1.into(),
None,
),
&mut virtual_overseer,
)
.await;
assert_sends_validation_event_to_all(
NetworkBridgeEvent::PeerViewChange(peer, View::default()),
&mut virtual_overseer,
)
.await;
}
let approval_distribution_message =
protocol_v1::ApprovalDistributionMessage::Approvals(Vec::new());
let message_v1 = protocol_v1::ValidationProtocol::ApprovalDistribution(
approval_distribution_message.clone(),
);
network_handle
.peer_message(
peer,
PeerSet::Validation,
WireMessage::ProtocolMessage(message_v1.clone()).encode(),
)
.await;
network_handle.disconnect_peer(peer, PeerSet::Validation).await;
// Approval distribution message comes first, and the message is only sent to that
// subsystem. then a disconnection event arises that is sent to all validation networking
// subsystems.
assert_matches!(
virtual_overseer.recv().await,
AllMessages::ApprovalDistribution(
ApprovalDistributionMessage::NetworkBridgeUpdate(
NetworkBridgeEvent::PeerMessage(p, Versioned::V1(m))
)
) => {
assert_eq!(p, peer);
assert_eq!(m, approval_distribution_message);
}
);
assert_sends_validation_event_to_all(
NetworkBridgeEvent::PeerDisconnected(peer),
&mut virtual_overseer,
)
.await;
virtual_overseer
});
}
#[test]
fn peer_disconnect_from_just_one_peerset() {
test_harness(done_syncing_oracle(), |test_harness| async move {
let TestHarness { mut network_handle, mut virtual_overseer, shared } = test_harness;
let peer = PeerId::random();
network_handle
.connect_peer(peer, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
.await;
network_handle
.connect_peer(peer, ValidationVersion::V1, PeerSet::Collation, ObservedRole::Full)
.await;
await_peer_connections(&shared, 1, 1).await;
// bridge will inform about all connected peers.
{
assert_sends_validation_event_to_all(
NetworkBridgeEvent::PeerConnected(
peer,
ObservedRole::Full,
ValidationVersion::V1.into(),
None,
),
&mut virtual_overseer,
)
.await;
assert_sends_validation_event_to_all(
NetworkBridgeEvent::PeerViewChange(peer, View::default()),
&mut virtual_overseer,
)
.await;
}
{
assert_sends_collation_event_to_all(
NetworkBridgeEvent::PeerConnected(
peer,
ObservedRole::Full,
ValidationVersion::V1.into(),
None,
),
&mut virtual_overseer,
)
.await;
assert_sends_collation_event_to_all(
NetworkBridgeEvent::PeerViewChange(peer, View::default()),
&mut virtual_overseer,
)
.await;
}
network_handle.disconnect_peer(peer, PeerSet::Validation).await;
assert_sends_validation_event_to_all(
NetworkBridgeEvent::PeerDisconnected(peer),
&mut virtual_overseer,
)
.await;
// to show that we're still connected on the collation protocol, send a view update.
let hash_a = Hash::repeat_byte(1);
virtual_overseer
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(
ActiveLeavesUpdate::start_work(new_leaf(hash_a, 1)),
)))
.await;
let actions = network_handle.next_network_actions(3).await;
let wire_message =
WireMessage::<protocol_v1::ValidationProtocol>::ViewUpdate(view![hash_a]).encode();
assert_network_actions_contains(
&actions,
&NetworkAction::WriteNotification(peer, PeerSet::Collation, wire_message.clone()),
);
virtual_overseer
});
}
#[test]
fn relays_collation_protocol_messages() {
test_harness(done_syncing_oracle(), |test_harness| async move {
let TestHarness { mut network_handle, mut virtual_overseer, shared } = test_harness;
let peer_a = PeerId::random();
let peer_b = PeerId::random();
network_handle
.connect_peer(peer_a, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
.await;
network_handle
.connect_peer(peer_b, ValidationVersion::V1, PeerSet::Collation, ObservedRole::Full)
.await;
await_peer_connections(&shared, 1, 1).await;
// bridge will inform about all connected peers.
{
assert_sends_validation_event_to_all(
NetworkBridgeEvent::PeerConnected(
peer_a,
ObservedRole::Full,
ValidationVersion::V1.into(),
None,
),
&mut virtual_overseer,
)
.await;
assert_sends_validation_event_to_all(
NetworkBridgeEvent::PeerViewChange(peer_a, View::default()),
&mut virtual_overseer,
)
.await;
}
{
assert_sends_collation_event_to_all(
NetworkBridgeEvent::PeerConnected(
peer_b,
ObservedRole::Full,
ValidationVersion::V1.into(),
None,
),
&mut virtual_overseer,
)
.await;
assert_sends_collation_event_to_all(
NetworkBridgeEvent::PeerViewChange(peer_b, View::default()),
&mut virtual_overseer,
)
.await;
}
let collator_protocol_message = protocol_v1::CollatorProtocolMessage::Declare(
Sr25519Keyring::Alice.public().into(),
Default::default(),
sp_core::crypto::UncheckedFrom::unchecked_from([1u8; 64]),
);
let message_v1 =
protocol_v1::CollationProtocol::CollatorProtocol(collator_protocol_message.clone());
// peer A gets reported for sending a collation message.
// NOTE: this is not possible since peer A cannot send
// a collation message if it has not opened a collation protocol
// network_handle
// .peer_message(
// peer_a,
// PeerSet::Collation,
// WireMessage::ProtocolMessage(message_v1.clone()).encode(),
// )
// .await;
// let actions = network_handle.next_network_actions(3).await;
// assert_network_actions_contains(
// &actions,
// &NetworkAction::ReputationChange(peer_a, UNCONNECTED_PEERSET_COST.into()),
// );
// peer B has the message relayed.
network_handle
.peer_message(
peer_b,
PeerSet::Collation,
WireMessage::ProtocolMessage(message_v1.clone()).encode(),
)
.await;
assert_matches!(
virtual_overseer.recv().await,
AllMessages::CollatorProtocol(
CollatorProtocolMessage::NetworkBridgeUpdate(
NetworkBridgeEvent::PeerMessage(p, Versioned::V1(m))
)
) => {
assert_eq!(p, peer_b);
assert_eq!(m, collator_protocol_message);
}
);
virtual_overseer
});
}
#[test]
fn different_views_on_different_peer_sets() {
test_harness(done_syncing_oracle(), |test_harness| async move {
let TestHarness { mut network_handle, mut virtual_overseer, shared } = test_harness;
let peer = PeerId::random();
network_handle
.connect_peer(peer, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
.await;
network_handle
.connect_peer(peer, ValidationVersion::V1, PeerSet::Collation, ObservedRole::Full)
.await;
await_peer_connections(&shared, 1, 1).await;
// bridge will inform about all connected peers.
{
assert_sends_validation_event_to_all(
NetworkBridgeEvent::PeerConnected(
peer,
ObservedRole::Full,
ValidationVersion::V1.into(),
None,
),
&mut virtual_overseer,
)
.await;
assert_sends_validation_event_to_all(
NetworkBridgeEvent::PeerViewChange(peer, View::default()),
&mut virtual_overseer,
)
.await;
}
{
assert_sends_collation_event_to_all(
NetworkBridgeEvent::PeerConnected(
peer,
ObservedRole::Full,
ValidationVersion::V1.into(),
None,
),
&mut virtual_overseer,
)
.await;
assert_sends_collation_event_to_all(
NetworkBridgeEvent::PeerViewChange(peer, View::default()),
&mut virtual_overseer,
)
.await;
}
let view_a = view![Hash::repeat_byte(1)];
let view_b = view![Hash::repeat_byte(2)];
network_handle
.peer_message(
peer,
PeerSet::Validation,
WireMessage::<protocol_v1::ValidationProtocol>::ViewUpdate(view_a.clone()).encode(),
)
.await;
network_handle
.peer_message(
peer,
PeerSet::Collation,
WireMessage::<protocol_v1::CollationProtocol>::ViewUpdate(view_b.clone()).encode(),
)
.await;
assert_sends_validation_event_to_all(
NetworkBridgeEvent::PeerViewChange(peer, view_a.clone()),
&mut virtual_overseer,
)
.await;
assert_sends_collation_event_to_all(
NetworkBridgeEvent::PeerViewChange(peer, view_b.clone()),
&mut virtual_overseer,
)
.await;
virtual_overseer
});
}
#[test]
fn sent_views_include_finalized_number_update() {
test_harness(done_syncing_oracle(), |test_harness| async move {
let TestHarness { mut network_handle, mut virtual_overseer, shared } = test_harness;
let peer_a = PeerId::random();
network_handle
.connect_peer(peer_a, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
.await;
await_peer_connections(&shared, 1, 0).await;
let hash_a = Hash::repeat_byte(1);
let hash_b = Hash::repeat_byte(2);
virtual_overseer
.send(FromOrchestra::Signal(OverseerSignal::BlockFinalized(hash_a, 1)))
.await;
virtual_overseer
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(
ActiveLeavesUpdate::start_work(new_leaf(hash_b, 1)),
)))
.await;
let actions = network_handle.next_network_actions(2).await;
let wire_message =
WireMessage::<protocol_v1::ValidationProtocol>::ViewUpdate(View::new(vec![hash_b], 1))
.encode();
assert_network_actions_contains(
&actions,
&NetworkAction::WriteNotification(peer_a, PeerSet::Validation, wire_message.clone()),
);
virtual_overseer
});
}
#[test]
fn view_finalized_number_can_not_go_down() {
test_harness(done_syncing_oracle(), |test_harness| async move {
let TestHarness { mut network_handle, virtual_overseer, shared } = test_harness;
let peer_a = PeerId::random();
network_handle
.connect_peer(peer_a, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
.await;
await_peer_connections(&shared, 1, 0).await;
network_handle
.peer_message(
peer_a,
PeerSet::Validation,
WireMessage::<protocol_v1::ValidationProtocol>::ViewUpdate(View::new(
vec![Hash::repeat_byte(0x01)],
1,
))
.encode(),
)
.await;
network_handle
.peer_message(
peer_a,
PeerSet::Validation,
WireMessage::<protocol_v1::ValidationProtocol>::ViewUpdate(View::new(vec![], 0))
.encode(),
)
.await;
let actions = network_handle.next_network_actions(2).await;
assert_network_actions_contains(
&actions,
&NetworkAction::ReputationChange(peer_a, MALFORMED_VIEW_COST.into()),
);
virtual_overseer
});
}
#[test]
fn our_view_updates_decreasing_order_and_limited_to_max() {
test_harness(done_syncing_oracle(), |test_harness| async move {
let TestHarness { mut virtual_overseer, .. } = test_harness;
// to show that we're still connected on the collation protocol, send a view update.
let hashes = (0..MAX_VIEW_HEADS + 1).map(|i| Hash::repeat_byte(i as u8));
for (i, hash) in hashes.enumerate().rev() {
// These are in reverse order, so the subsystem must sort internally to
// get the correct view.
virtual_overseer
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(
ActiveLeavesUpdate::start_work(new_leaf(hash, i as _)),
)))
.await;
}
let our_views = (1..=MAX_VIEW_HEADS).rev().map(|start| {
OurView::new(
(start..=MAX_VIEW_HEADS)
.rev()
.map(|i| (Hash::repeat_byte(i as u8), Arc::new(jaeger::Span::Disabled))),
0,
)
});
for our_view in our_views {
assert_sends_validation_event_to_all(
NetworkBridgeEvent::OurViewChange(our_view.clone()),
&mut virtual_overseer,
)
.await;
assert_sends_collation_event_to_all(
NetworkBridgeEvent::OurViewChange(our_view),
&mut virtual_overseer,
)
.await;
}
virtual_overseer
});
}
#[test]
fn network_protocol_versioning_view_update() {
let (oracle, handle) = make_sync_oracle(false);
test_harness(Box::new(oracle), |test_harness| async move {
let TestHarness { mut network_handle, mut virtual_overseer, shared } = test_harness;
let peer_ids: Vec<_> = (0..4).map(|_| PeerId::random()).collect();
let peers = [
(peer_ids[0], PeerSet::Validation, ValidationVersion::V2),
(peer_ids[1], PeerSet::Collation, ValidationVersion::V1),
(peer_ids[2], PeerSet::Validation, ValidationVersion::V1),
(peer_ids[3], PeerSet::Collation, ValidationVersion::V2),
];
let head = Hash::repeat_byte(1);
virtual_overseer
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(
ActiveLeavesUpdate::start_work(new_leaf(head, 1)),
)))
.await;
handle.await_mode_switch().await;
let mut total_validation_peers = 0;
let mut total_collation_peers = 0;
for &(peer_id, peer_set, version) in &peers {
network_handle
.connect_peer(peer_id, version, peer_set, ObservedRole::Full)
.await;
match peer_set {
PeerSet::Validation => total_validation_peers += 1,
PeerSet::Collation => total_collation_peers += 1,
}
}
await_peer_connections(&shared, total_validation_peers, total_collation_peers).await;
let view = view![head];
let actions = network_handle.next_network_actions(4).await;
for &(peer_id, peer_set, version) in &peers {
let wire_msg = match version {
ValidationVersion::V1 =>
WireMessage::<protocol_v1::ValidationProtocol>::ViewUpdate(view.clone())
.encode(),
ValidationVersion::V2 =>
WireMessage::<protocol_v2::ValidationProtocol>::ViewUpdate(view.clone())
.encode(),
ValidationVersion::V3 =>
WireMessage::<protocol_v3::ValidationProtocol>::ViewUpdate(view.clone())
.encode(),
};
assert_network_actions_contains(
&actions,
&NetworkAction::WriteNotification(peer_id, peer_set, wire_msg),
);
}
virtual_overseer
});
}
#[test]
fn network_protocol_versioning_subsystem_msg() {
use polkadot_primitives::CandidateHash;
use std::task::Poll;
let (oracle, _handle) = make_sync_oracle(false);
test_harness(Box::new(oracle), |test_harness| async move {
let TestHarness { mut network_handle, mut virtual_overseer, shared } = test_harness;
let peer = PeerId::random();
network_handle
.connect_peer(peer, ValidationVersion::V2, PeerSet::Validation, ObservedRole::Full)
.await;
await_peer_connections(&shared, 1, 0).await;
// bridge will inform about all connected peers.
{
assert_sends_validation_event_to_all(
NetworkBridgeEvent::PeerConnected(
peer,
ObservedRole::Full,
ValidationVersion::V3.into(),
None,
),
&mut virtual_overseer,
)
.await;
assert_sends_validation_event_to_all(
NetworkBridgeEvent::PeerViewChange(peer, View::default()),
&mut virtual_overseer,
)
.await;
}
let approval_distribution_message =
protocol_v3::ApprovalDistributionMessage::Approvals(Vec::new());
let msg = protocol_v3::ValidationProtocol::ApprovalDistribution(
approval_distribution_message.clone(),
);
network_handle
.peer_message(
peer,
PeerSet::Validation,
WireMessage::ProtocolMessage(msg.clone()).encode(),
)
.await;
assert_matches!(
virtual_overseer.recv().await,
AllMessages::ApprovalDistribution(
ApprovalDistributionMessage::NetworkBridgeUpdate(
NetworkBridgeEvent::PeerMessage(p, Versioned::V3(m))
)
) => {
assert_eq!(p, peer);
assert_eq!(m, approval_distribution_message);
}
);
let metadata = protocol_v1::StatementMetadata {
relay_parent: Hash::zero(),
candidate_hash: CandidateHash::default(),
signed_by: ValidatorIndex(0),
signature: sp_core::crypto::UncheckedFrom::unchecked_from([1u8; 64]),
};
let statement_distribution_message =
protocol_v2::StatementDistributionMessage::V1Compatibility(
protocol_v1::StatementDistributionMessage::LargeStatement(metadata),
);
let msg = protocol_v2::ValidationProtocol::StatementDistribution(
statement_distribution_message.clone(),
);
network_handle
.peer_message(
peer,
PeerSet::Validation,
WireMessage::ProtocolMessage(msg.clone()).encode(),
)
.await;
assert_matches!(
virtual_overseer.recv().await,
AllMessages::StatementDistribution(
StatementDistributionMessage::NetworkBridgeUpdate(
NetworkBridgeEvent::PeerMessage(p, Versioned::V3(m))
)
) => {
assert_eq!(p, peer);
assert_eq!(m, statement_distribution_message);
}
);
// No more messages.
assert_matches!(futures::poll!(virtual_overseer.recv().boxed()), Poll::Pending);
virtual_overseer
});
}