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
+60 -63
View File
@@ -21,12 +21,13 @@
//! Usage:
//!
//! - Use [`StatementHandlerPrototype::new`] to create a prototype.
//! - Pass the return value of [`StatementHandlerPrototype::set_config`] to the network
//! configuration as an extra peers set.
//! - Pass the `NonDefaultSetConfig` returned from [`StatementHandlerPrototype::new`] to the network
//! configuration as an extra peers set.
//! - Use [`StatementHandlerPrototype::build`] then [`StatementHandler::run`] to obtain a
//! `Future` that processes statements.
use crate::config::*;
use codec::{Decode, Encode};
use futures::{channel::oneshot, prelude::*, stream::FuturesUnordered, FutureExt};
use libp2p::{multiaddr, PeerId};
@@ -34,7 +35,7 @@ use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64};
use sc_network::{
config::{NonDefaultSetConfig, NonReservedPeerMode, SetConfig},
error,
event::Event,
service::traits::{NotificationEvent, NotificationService, ValidationResult},
types::ProtocolName,
utils::{interval, LruHashSet},
NetworkEventStream, NetworkNotification, NetworkPeers,
@@ -101,35 +102,35 @@ impl Metrics {
/// Prototype for a [`StatementHandler`].
pub struct StatementHandlerPrototype {
protocol_name: ProtocolName,
notification_service: Box<dyn NotificationService>,
}
impl StatementHandlerPrototype {
/// Create a new instance.
pub fn new<Hash: AsRef<[u8]>>(genesis_hash: Hash, fork_id: Option<&str>) -> Self {
pub fn new<Hash: AsRef<[u8]>>(
genesis_hash: Hash,
fork_id: Option<&str>,
) -> (Self, NonDefaultSetConfig) {
let genesis_hash = genesis_hash.as_ref();
let protocol_name = if let Some(fork_id) = fork_id {
format!("/{}/{}/statement/1", array_bytes::bytes2hex("", genesis_hash), fork_id)
} else {
format!("/{}/statement/1", array_bytes::bytes2hex("", genesis_hash))
};
Self { protocol_name: protocol_name.into() }
}
/// Returns the configuration of the set to put in the network configuration.
pub fn set_config(&self) -> NonDefaultSetConfig {
NonDefaultSetConfig {
notifications_protocol: self.protocol_name.clone(),
fallback_names: Vec::new(),
max_notification_size: MAX_STATEMENT_SIZE,
handshake: None,
set_config: SetConfig {
let (config, notification_service) = NonDefaultSetConfig::new(
protocol_name.clone().into(),
Vec::new(),
MAX_STATEMENT_SIZE,
None,
SetConfig {
in_peers: 0,
out_peers: 0,
reserved_nodes: Vec::new(),
non_reserved_mode: NonReservedPeerMode::Deny,
},
}
);
(Self { protocol_name: protocol_name.into(), notification_service }, config)
}
/// Turns the prototype into the actual handler.
@@ -147,7 +148,6 @@ impl StatementHandlerPrototype {
metrics_registry: Option<&Registry>,
executor: impl Fn(Pin<Box<dyn Future<Output = ()> + Send>>) + Send,
) -> error::Result<StatementHandler<N, S>> {
let net_event_stream = network.event_stream("statement-handler-net");
let sync_event_stream = sync.event_stream("statement-handler-sync");
let (queue_sender, mut queue_receiver) = async_channel::bounded(100_000);
@@ -176,6 +176,7 @@ impl StatementHandlerPrototype {
let handler = StatementHandler {
protocol_name: self.protocol_name,
notification_service: self.notification_service,
propagate_timeout: (Box::pin(interval(PROPAGATE_TIMEOUT))
as Pin<Box<dyn Stream<Item = ()> + Send>>)
.fuse(),
@@ -183,7 +184,6 @@ impl StatementHandlerPrototype {
pending_statements_peers: HashMap::new(),
network,
sync,
net_event_stream: net_event_stream.fuse(),
sync_event_stream: sync_event_stream.fuse(),
peers: HashMap::new(),
statement_store,
@@ -219,10 +219,10 @@ pub struct StatementHandler<
network: N,
/// Syncing service.
sync: S,
/// Stream of networking events.
net_event_stream: stream::Fuse<Pin<Box<dyn Stream<Item = Event> + Send>>>,
/// Receiver for syncing-related events.
sync_event_stream: stream::Fuse<Pin<Box<dyn Stream<Item = SyncEvent> + Send>>>,
/// Notification service.
notification_service: Box<dyn NotificationService>,
// All connected peers
peers: HashMap<PeerId, Peer>,
statement_store: Arc<dyn StatementStore>,
@@ -261,14 +261,6 @@ where
log::warn!(target: LOG_TARGET, "Inconsistent state, no peers for pending statement!");
}
},
network_event = self.net_event_stream.next() => {
if let Some(network_event) = network_event {
self.handle_network_event(network_event).await;
} else {
// Networking has seemingly closed. Closing as well.
return;
}
},
sync_event = self.sync_event_stream.next() => {
if let Some(sync_event) = sync_event {
self.handle_sync_event(sync_event);
@@ -277,6 +269,14 @@ where
return;
}
}
event = self.notification_service.next_event().fuse() => {
if let Some(event) = event {
self.handle_notification_event(event)
} else {
// `Notifications` has seemingly closed. Closing as well.
return
}
}
}
}
}
@@ -306,14 +306,24 @@ where
}
}
async fn handle_network_event(&mut self, event: Event) {
fn handle_notification_event(&mut self, event: NotificationEvent) {
match event {
Event::Dht(_) => {},
Event::NotificationStreamOpened { remote, protocol, role, .. }
if protocol == self.protocol_name =>
{
NotificationEvent::ValidateInboundSubstream { peer, handshake, result_tx, .. } => {
// only accept peers whose role can be determined
let result = self
.network
.peer_role(peer, handshake)
.map_or(ValidationResult::Reject, |_| ValidationResult::Accept);
let _ = result_tx.send(result);
},
NotificationEvent::NotificationStreamOpened { peer, handshake, .. } => {
let Some(role) = self.network.peer_role(peer, handshake) else {
log::debug!(target: LOG_TARGET, "role for {peer} couldn't be determined");
return
};
let _was_in = self.peers.insert(
remote,
peer,
Peer {
known_statements: LruHashSet::new(
NonZeroUsize::new(MAX_KNOWN_STATEMENTS).expect("Constant is nonzero"),
@@ -323,39 +333,26 @@ where
);
debug_assert!(_was_in.is_none());
},
Event::NotificationStreamClosed { remote, protocol }
if protocol == self.protocol_name =>
{
let _peer = self.peers.remove(&remote);
NotificationEvent::NotificationStreamClosed { peer } => {
let _peer = self.peers.remove(&peer);
debug_assert!(_peer.is_some());
},
NotificationEvent::NotificationReceived { peer, notification } => {
// Accept statements only when node is not major syncing
if self.sync.is_major_syncing() {
log::trace!(
target: LOG_TARGET,
"{peer}: Ignoring statements while major syncing or offline"
);
return
}
Event::NotificationsReceived { remote, messages } => {
for (protocol, message) in messages {
if protocol != self.protocol_name {
continue
}
// Accept statements only when node is not major syncing
if self.sync.is_major_syncing() {
log::trace!(
target: LOG_TARGET,
"{remote}: Ignoring statements while major syncing or offline"
);
continue
}
if let Ok(statements) = <Statements as Decode>::decode(&mut message.as_ref()) {
self.on_statements(remote, statements);
} else {
log::debug!(
target: LOG_TARGET,
"Failed to decode statement list from {remote}"
);
}
if let Ok(statements) = <Statements as Decode>::decode(&mut notification.as_ref()) {
self.on_statements(peer, statements);
} else {
log::debug!(target: LOG_TARGET, "Failed to decode statement list from {peer}");
}
},
// Not our concern.
Event::NotificationStreamOpened { .. } | Event::NotificationStreamClosed { .. } => {},
}
}