Rework the event system of sc-network (#1370)

This commit introduces a new concept called `NotificationService` which
allows Polkadot protocols to communicate with the underlying
notification protocol implementation directly, without routing events
through `NetworkWorker`. This implies that each protocol has its own
service which it uses to communicate with remote peers and that each
`NotificationService` is unique with respect to the underlying
notification protocol, meaning `NotificationService` for the transaction
protocol can only be used to send and receive transaction-related
notifications.

The `NotificationService` concept introduces two additional benefits:
  * allow protocols to start using custom handshakes
  * allow protocols to accept/reject inbound peers

Previously the validation of inbound connections was solely the
responsibility of `ProtocolController`. This caused issues with light
peers and `SyncingEngine` as `ProtocolController` would accept more
peers than `SyncingEngine` could accept which caused peers to have
differing views of their own states. `SyncingEngine` would reject excess
peers but these rejections were not properly communicated to those peers
causing them to assume that they were accepted.

With `NotificationService`, the local handshake is not sent to remote
peer if peer is rejected which allows it to detect that it was rejected.

This commit also deprecates the use of `NetworkEventStream` for all
notification-related events and going forward only DHT events are
provided through `NetworkEventStream`. If protocols wish to follow each
other's events, they must introduce additional abtractions, as is done
for GRANDPA and transactions protocols by following the syncing protocol
through `SyncEventStream`.

Fixes https://github.com/paritytech/polkadot-sdk/issues/512
Fixes https://github.com/paritytech/polkadot-sdk/issues/514
Fixes https://github.com/paritytech/polkadot-sdk/issues/515
Fixes https://github.com/paritytech/polkadot-sdk/issues/554
Fixes https://github.com/paritytech/polkadot-sdk/issues/556

---
These changes are transferred from
https://github.com/paritytech/substrate/pull/14197 but there are no
functional changes compared to that PR

---------

Co-authored-by: Dmitry Markin <dmitry@markin.tech>
Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>
This commit is contained in:
Aaro Altonen
2023-11-28 20:18:52 +02:00
committed by GitHub
parent ec3a61ed86
commit e71c484d5b
102 changed files with 5694 additions and 2603 deletions
File diff suppressed because it is too large Load Diff
+261 -72
View File
@@ -15,7 +15,7 @@
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use super::*;
use futures::{channel::oneshot, executor, stream::BoxStream};
use futures::{channel::oneshot, executor};
use overseer::jaeger;
use polkadot_node_network_protocol::{self as net_protocol, OurView};
use polkadot_node_subsystem::messages::NetworkBridgeEvent;
@@ -26,10 +26,13 @@ use parking_lot::Mutex;
use std::{
collections::HashSet,
sync::atomic::{AtomicBool, Ordering},
task::Poll,
};
use sc_network::{Event as NetworkEvent, IfDisconnected, ProtocolName, ReputationChange};
use sc_network::{
service::traits::{Direction, MessageSink, NotificationService},
IfDisconnected, Multiaddr, ObservedRole as SubstrateObservedRole, ProtocolName,
ReputationChange, Roles,
};
use polkadot_node_network_protocol::{
peer_set::PeerSetProtocolNames,
@@ -47,9 +50,8 @@ use polkadot_node_subsystem_test_helpers::{
mock::new_leaf, SingleItemSink, SingleItemStream, TestSubsystemContextHandle,
};
use polkadot_node_subsystem_util::metered;
use polkadot_primitives::{AuthorityDiscoveryId, CandidateHash, Hash};
use polkadot_primitives::{AuthorityDiscoveryId, Hash};
use sc_network::Multiaddr;
use sp_keyring::Sr25519Keyring;
use crate::{network::Network, validator_discovery::AuthorityDiscovery};
@@ -64,10 +66,9 @@ pub enum NetworkAction {
WriteNotification(PeerId, PeerSet, Vec<u8>),
}
// The subsystem's view of the network - only supports a single call to `event_stream`.
// The subsystem's view of the network.
#[derive(Clone)]
struct TestNetwork {
net_events: Arc<Mutex<Option<SingleItemStream<NetworkEvent>>>>,
action_tx: Arc<Mutex<metered::UnboundedMeteredSender<NetworkAction>>>,
protocol_names: Arc<PeerSetProtocolNames>,
}
@@ -79,37 +80,42 @@ struct TestAuthorityDiscovery;
// of `NetworkAction`s.
struct TestNetworkHandle {
action_rx: metered::UnboundedMeteredReceiver<NetworkAction>,
net_tx: SingleItemSink<NetworkEvent>,
protocol_names: PeerSetProtocolNames,
validation_tx: SingleItemSink<NotificationEvent>,
collation_tx: SingleItemSink<NotificationEvent>,
}
fn new_test_network(
protocol_names: PeerSetProtocolNames,
) -> (TestNetwork, TestNetworkHandle, TestAuthorityDiscovery) {
let (net_tx, net_rx) = polkadot_node_subsystem_test_helpers::single_item_sink();
) -> (
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 {
net_events: Arc::new(Mutex::new(Some(net_rx))),
action_tx: Arc::new(Mutex::new(action_tx)),
action_tx: action_tx.clone(),
protocol_names: Arc::new(protocol_names.clone()),
},
TestNetworkHandle { action_rx, net_tx, protocol_names },
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 {
fn event_stream(&mut self) -> BoxStream<'static, NetworkEvent> {
self.net_events
.lock()
.take()
.expect("Subsystem made more than one call to `event_stream`")
.boxed()
}
async fn set_reserved_peers(
&mut self,
_protocol: ProtocolName,
@@ -143,7 +149,8 @@ impl Network for TestNetwork {
}
fn disconnect_peer(&self, who: PeerId, protocol: ProtocolName) {
let (peer_set, _) = self.protocol_names.try_get_protocol(&protocol).unwrap();
let (peer_set, version) = self.protocol_names.try_get_protocol(&protocol).unwrap();
assert_eq!(version, peer_set.get_main_version());
self.action_tx
.lock()
@@ -151,13 +158,10 @@ impl Network for TestNetwork {
.unwrap();
}
fn write_notification(&self, who: PeerId, protocol: ProtocolName, message: Vec<u8>) {
let (peer_set, _) = self.protocol_names.try_get_protocol(&protocol).unwrap();
self.action_tx
.lock()
.unbounded_send(NetworkAction::WriteNotification(who, peer_set, message))
.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)))
}
}
@@ -201,35 +205,85 @@ impl TestNetworkHandle {
peer_set: PeerSet,
role: ObservedRole,
) {
let protocol_version = ProtocolVersion::from(protocol_version);
self.send_network_event(NetworkEvent::NotificationStreamOpened {
remote: peer,
protocol: self.protocol_names.get_name(peer_set, protocol_version),
negotiated_fallback: None,
role: role.into(),
received_handshake: vec![],
})
.await;
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::VStaging => 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) {
self.send_network_event(NetworkEvent::NotificationStreamClosed {
remote: peer,
protocol: self.protocol_names.get_main_name(peer_set),
})
.await;
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>) {
self.send_network_event(NetworkEvent::NotificationsReceived {
remote: peer,
messages: vec![(self.protocol_names.get_main_name(peer_set), message.into())],
})
.await;
}
async fn send_network_event(&mut self, event: NetworkEvent) {
self.net_tx.send(event).await.expect("subsystem concluded early");
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"),
}
}
}
@@ -240,6 +294,121 @@ fn assert_network_actions_contains(actions: &[NetworkAction], action: &NetworkAc
}
}
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(&self, _peer: &PeerId, _notification: Vec<u8>) {
unimplemented!();
}
/// Send asynchronous `notification` to `peer`, allowing sender to exercise backpressure.
async fn send_async_notification(
&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>,
@@ -335,10 +504,11 @@ fn test_harness<T: Future<Output = VirtualOverseer>>(
let peerset_protocol_names = PeerSetProtocolNames::new(genesis_hash, fork_id);
let pool = sp_core::testing::TaskExecutor::new();
let (mut network, network_handle, discovery) = new_test_network(peerset_protocol_names.clone());
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 network_stream = network.event_stream();
let notification_sinks = Arc::new(Mutex::new(HashMap::new()));
let shared = Shared::default();
let bridge = NetworkBridgeRx {
@@ -348,9 +518,12 @@ fn test_harness<T: Future<Output = VirtualOverseer>>(
sync_oracle,
shared: shared.clone(),
peerset_protocol_names,
validation_service,
collation_service,
notification_sinks,
};
let network_bridge = run_network_in(bridge, context, network_stream)
let network_bridge = run_network_in(bridge, context)
.map_err(|_| panic!("subsystem execution failed"))
.map(|_| ());
@@ -942,8 +1115,6 @@ fn relays_collation_protocol_messages() {
.await;
}
// peer A gets reported for sending a collation message.
let collator_protocol_message = protocol_v1::CollatorProtocolMessage::Declare(
Sr25519Keyring::Alice.public().into(),
Default::default(),
@@ -953,19 +1124,23 @@ fn relays_collation_protocol_messages() {
let message_v1 =
protocol_v1::CollationProtocol::CollatorProtocol(collator_protocol_message.clone());
network_handle
.peer_message(
peer_a,
PeerSet::Collation,
WireMessage::ProtocolMessage(message_v1.clone()).encode(),
)
.await;
// 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
let actions = network_handle.next_network_actions(3).await;
assert_network_actions_contains(
&actions,
&NetworkAction::ReputationChange(peer_a, UNCONNECTED_PEERSET_COST.into()),
);
// 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.
@@ -1212,7 +1387,7 @@ fn our_view_updates_decreasing_order_and_limited_to_max() {
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, .. } = test_harness;
let TestHarness { mut network_handle, mut virtual_overseer, shared } = test_harness;
let peer_ids: Vec<_> = (0..4).map(|_| PeerId::random()).collect();
let peers = [
@@ -1231,12 +1406,22 @@ fn network_protocol_versioning_view_update() {
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;
@@ -1264,15 +1449,19 @@ fn network_protocol_versioning_view_update() {
#[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, .. } = test_harness;
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.
{