mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-14 07:31:08 +00:00
e71c484d5b
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>
1171 lines
33 KiB
Rust
1171 lines
33 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/>.
|
|
|
|
//! The Network Bridge Subsystem - handles _incoming_ messages from the network, forwarded to the
|
|
//! relevant subsystems.
|
|
use super::*;
|
|
|
|
use always_assert::never;
|
|
use bytes::Bytes;
|
|
use net_protocol::filter_by_peer_version;
|
|
use parity_scale_codec::{Decode, DecodeAll};
|
|
use parking_lot::Mutex;
|
|
|
|
use sc_network::{
|
|
service::traits::{NotificationEvent, ValidationResult},
|
|
MessageSink, NotificationService,
|
|
};
|
|
use sp_consensus::SyncOracle;
|
|
|
|
use polkadot_node_network_protocol::{
|
|
self as net_protocol,
|
|
grid_topology::{SessionGridTopology, TopologyPeerInfo},
|
|
peer_set::{
|
|
CollationVersion, PeerSet, PeerSetProtocolNames, PerPeerSet, ProtocolVersion,
|
|
ValidationVersion,
|
|
},
|
|
v1 as protocol_v1, v2 as protocol_v2, vstaging as protocol_vstaging, ObservedRole, OurView,
|
|
PeerId, UnifiedReputationChange as Rep, View,
|
|
};
|
|
|
|
use polkadot_node_subsystem::{
|
|
errors::SubsystemError,
|
|
messages::{
|
|
network_bridge_event::NewGossipTopology, ApprovalDistributionMessage,
|
|
BitfieldDistributionMessage, CollatorProtocolMessage, GossipSupportMessage,
|
|
NetworkBridgeEvent, NetworkBridgeRxMessage, StatementDistributionMessage,
|
|
},
|
|
overseer, ActivatedLeaf, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, SpawnedSubsystem,
|
|
};
|
|
|
|
use polkadot_primitives::{AuthorityDiscoveryId, BlockNumber, Hash, ValidatorIndex};
|
|
|
|
/// Peer set info for network initialization.
|
|
///
|
|
/// To be passed to [`FullNetworkConfiguration::add_notification_protocol`]().
|
|
pub use polkadot_node_network_protocol::peer_set::{peer_sets_info, IsAuthority};
|
|
|
|
use std::{
|
|
collections::{hash_map, HashMap},
|
|
iter::ExactSizeIterator,
|
|
};
|
|
|
|
use super::validator_discovery;
|
|
|
|
/// Actual interfacing to the network based on the `Network` trait.
|
|
///
|
|
/// Defines the `Network` trait with an implementation for an `Arc<NetworkService>`.
|
|
use crate::network::{
|
|
send_collation_message_v1, send_collation_message_v2, send_validation_message_v1,
|
|
send_validation_message_v2, send_validation_message_vstaging, Network,
|
|
};
|
|
use crate::{network::get_peer_id_by_authority_id, WireMessage};
|
|
|
|
use super::metrics::Metrics;
|
|
|
|
#[cfg(test)]
|
|
mod tests;
|
|
|
|
// network bridge log target
|
|
const LOG_TARGET: &'static str = "parachain::network-bridge-rx";
|
|
|
|
/// The network bridge subsystem - network receiving side.
|
|
pub struct NetworkBridgeRx<N, AD> {
|
|
/// `Network` trait implementing type.
|
|
network_service: N,
|
|
authority_discovery_service: AD,
|
|
sync_oracle: Box<dyn SyncOracle + Send>,
|
|
shared: Shared,
|
|
metrics: Metrics,
|
|
peerset_protocol_names: PeerSetProtocolNames,
|
|
validation_service: Box<dyn NotificationService>,
|
|
collation_service: Box<dyn NotificationService>,
|
|
notification_sinks: Arc<Mutex<HashMap<(PeerSet, PeerId), Box<dyn MessageSink>>>>,
|
|
}
|
|
|
|
impl<N, AD> NetworkBridgeRx<N, AD> {
|
|
/// Create a new network bridge subsystem with underlying network service and authority
|
|
/// discovery service.
|
|
///
|
|
/// This assumes that the network service has had the notifications protocol for the network
|
|
/// bridge already registered. See [`peer_sets_info`].
|
|
pub fn new(
|
|
network_service: N,
|
|
authority_discovery_service: AD,
|
|
sync_oracle: Box<dyn SyncOracle + Send>,
|
|
metrics: Metrics,
|
|
peerset_protocol_names: PeerSetProtocolNames,
|
|
mut notification_services: HashMap<PeerSet, Box<dyn NotificationService>>,
|
|
notification_sinks: Arc<Mutex<HashMap<(PeerSet, PeerId), Box<dyn MessageSink>>>>,
|
|
) -> Self {
|
|
let shared = Shared::default();
|
|
|
|
let validation_service = notification_services
|
|
.remove(&PeerSet::Validation)
|
|
.expect("validation protocol was enabled so `NotificationService` must exist; qed");
|
|
let collation_service = notification_services
|
|
.remove(&PeerSet::Collation)
|
|
.expect("collation protocol was enabled so `NotificationService` must exist; qed");
|
|
|
|
Self {
|
|
network_service,
|
|
authority_discovery_service,
|
|
sync_oracle,
|
|
shared,
|
|
metrics,
|
|
peerset_protocol_names,
|
|
validation_service,
|
|
collation_service,
|
|
notification_sinks,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[overseer::subsystem(NetworkBridgeRx, error = SubsystemError, prefix = self::overseer)]
|
|
impl<Net, AD, Context> NetworkBridgeRx<Net, AD>
|
|
where
|
|
Net: Network + Sync,
|
|
AD: validator_discovery::AuthorityDiscovery + Clone + Sync,
|
|
{
|
|
fn start(self, ctx: Context) -> SpawnedSubsystem {
|
|
// Swallow error because failure is fatal to the node and we log with more precision
|
|
// within `run_network`.
|
|
let future = run_network_in(self, ctx)
|
|
.map_err(|e| SubsystemError::with_origin("network-bridge", e))
|
|
.boxed();
|
|
SpawnedSubsystem { name: "network-bridge-rx-subsystem", future }
|
|
}
|
|
}
|
|
|
|
/// Handle notification event received over the validation protocol.
|
|
async fn handle_validation_message<AD>(
|
|
event: NotificationEvent,
|
|
network_service: &mut impl Network,
|
|
sender: &mut impl overseer::NetworkBridgeRxSenderTrait,
|
|
authority_discovery_service: &mut AD,
|
|
metrics: &Metrics,
|
|
shared: &Shared,
|
|
peerset_protocol_names: &PeerSetProtocolNames,
|
|
notification_service: &mut Box<dyn NotificationService>,
|
|
notification_sinks: &mut Arc<Mutex<HashMap<(PeerSet, PeerId), Box<dyn MessageSink>>>>,
|
|
) where
|
|
AD: validator_discovery::AuthorityDiscovery + Send,
|
|
{
|
|
match event {
|
|
NotificationEvent::ValidateInboundSubstream { peer, handshake, result_tx, .. } => {
|
|
// only accept peers whose role can be determined
|
|
let result = network_service
|
|
.peer_role(peer, handshake)
|
|
.map_or(ValidationResult::Reject, |_| ValidationResult::Accept);
|
|
let _ = result_tx.send(result);
|
|
},
|
|
NotificationEvent::NotificationStreamOpened {
|
|
peer,
|
|
handshake,
|
|
negotiated_fallback,
|
|
..
|
|
} => {
|
|
let role = match network_service.peer_role(peer, handshake) {
|
|
Some(role) => ObservedRole::from(role),
|
|
None => {
|
|
gum::debug!(
|
|
target: LOG_TARGET,
|
|
?peer,
|
|
"Failed to determine peer role for validation protocol",
|
|
);
|
|
return
|
|
},
|
|
};
|
|
|
|
let (peer_set, version) = {
|
|
let (peer_set, version) =
|
|
(PeerSet::Validation, PeerSet::Validation.get_main_version());
|
|
|
|
if let Some(fallback) = negotiated_fallback {
|
|
match peerset_protocol_names.try_get_protocol(&fallback) {
|
|
None => {
|
|
gum::debug!(
|
|
target: LOG_TARGET,
|
|
fallback = &*fallback,
|
|
?peer,
|
|
peerset = ?peer_set,
|
|
"Unknown fallback",
|
|
);
|
|
|
|
return
|
|
},
|
|
Some((p2, v2)) => {
|
|
if p2 != peer_set {
|
|
gum::debug!(
|
|
target: LOG_TARGET,
|
|
fallback = &*fallback,
|
|
fallback_peerset = ?p2,
|
|
peerset = ?peer_set,
|
|
"Fallback mismatched peer-set",
|
|
);
|
|
|
|
return
|
|
}
|
|
|
|
(p2, v2)
|
|
},
|
|
}
|
|
} else {
|
|
(peer_set, version)
|
|
}
|
|
};
|
|
// store the notification sink to `notification_sinks` so both `NetworkBridgeRx`
|
|
// and `NetworkBridgeTx` can send messages to the peer.
|
|
match notification_service.message_sink(&peer) {
|
|
Some(sink) => {
|
|
notification_sinks.lock().insert((peer_set, peer), sink);
|
|
},
|
|
None => {
|
|
gum::warn!(
|
|
target: LOG_TARGET,
|
|
peerset = ?peer_set,
|
|
version = %version,
|
|
?peer,
|
|
?role,
|
|
"Message sink not available for peer",
|
|
);
|
|
return
|
|
},
|
|
}
|
|
|
|
gum::debug!(
|
|
target: LOG_TARGET,
|
|
action = "PeerConnected",
|
|
peer_set = ?peer_set,
|
|
version = %version,
|
|
peer = ?peer,
|
|
role = ?role
|
|
);
|
|
|
|
let local_view = {
|
|
let mut shared = shared.0.lock();
|
|
let peer_map = &mut shared.validation_peers;
|
|
|
|
match peer_map.entry(peer) {
|
|
hash_map::Entry::Occupied(_) => return,
|
|
hash_map::Entry::Vacant(vacant) => {
|
|
vacant.insert(PeerData { view: View::default(), version });
|
|
},
|
|
}
|
|
|
|
metrics.on_peer_connected(peer_set, version);
|
|
metrics.note_peer_count(peer_set, version, peer_map.len());
|
|
|
|
shared.local_view.clone().unwrap_or(View::default())
|
|
};
|
|
|
|
let maybe_authority =
|
|
authority_discovery_service.get_authority_ids_by_peer_id(peer).await;
|
|
|
|
dispatch_validation_events_to_all(
|
|
vec![
|
|
NetworkBridgeEvent::PeerConnected(peer, role, version, maybe_authority),
|
|
NetworkBridgeEvent::PeerViewChange(peer, View::default()),
|
|
],
|
|
sender,
|
|
&metrics,
|
|
)
|
|
.await;
|
|
|
|
match ValidationVersion::try_from(version)
|
|
.expect("try_get_protocol has already checked version is known; qed")
|
|
{
|
|
ValidationVersion::V1 => send_validation_message_v1(
|
|
vec![peer],
|
|
WireMessage::<protocol_v1::ValidationProtocol>::ViewUpdate(local_view),
|
|
metrics,
|
|
notification_sinks,
|
|
),
|
|
ValidationVersion::VStaging => send_validation_message_vstaging(
|
|
vec![peer],
|
|
WireMessage::<protocol_vstaging::ValidationProtocol>::ViewUpdate(local_view),
|
|
metrics,
|
|
notification_sinks,
|
|
),
|
|
ValidationVersion::V2 => send_validation_message_v2(
|
|
vec![peer],
|
|
WireMessage::<protocol_v2::ValidationProtocol>::ViewUpdate(local_view),
|
|
metrics,
|
|
notification_sinks,
|
|
),
|
|
}
|
|
},
|
|
NotificationEvent::NotificationStreamClosed { peer } => {
|
|
let (peer_set, version) = (PeerSet::Validation, PeerSet::Validation.get_main_version());
|
|
|
|
gum::debug!(
|
|
target: LOG_TARGET,
|
|
action = "PeerDisconnected",
|
|
?peer_set,
|
|
?peer
|
|
);
|
|
|
|
let was_connected = {
|
|
let mut shared = shared.0.lock();
|
|
let peer_map = &mut shared.validation_peers;
|
|
|
|
let w = peer_map.remove(&peer).is_some();
|
|
|
|
metrics.on_peer_disconnected(peer_set, version);
|
|
metrics.note_peer_count(peer_set, version, peer_map.len());
|
|
|
|
w
|
|
};
|
|
|
|
notification_sinks.lock().remove(&(peer_set, peer));
|
|
|
|
if was_connected && version == peer_set.get_main_version() {
|
|
dispatch_validation_event_to_all(
|
|
NetworkBridgeEvent::PeerDisconnected(peer),
|
|
sender,
|
|
&metrics,
|
|
)
|
|
.await;
|
|
}
|
|
},
|
|
NotificationEvent::NotificationReceived { peer, notification } => {
|
|
let expected_versions = {
|
|
let mut versions = PerPeerSet::<Option<ProtocolVersion>>::default();
|
|
let shared = shared.0.lock();
|
|
|
|
if let Some(peer_data) = shared.validation_peers.get(&peer) {
|
|
versions[PeerSet::Validation] = Some(peer_data.version);
|
|
}
|
|
|
|
versions
|
|
};
|
|
|
|
gum::trace!(
|
|
target: LOG_TARGET,
|
|
action = "PeerMessage",
|
|
peerset = ?PeerSet::Validation,
|
|
?peer,
|
|
);
|
|
|
|
let (events, reports) =
|
|
if expected_versions[PeerSet::Validation] == Some(ValidationVersion::V1.into()) {
|
|
handle_peer_messages::<protocol_v1::ValidationProtocol, _>(
|
|
peer,
|
|
PeerSet::Validation,
|
|
&mut shared.0.lock().validation_peers,
|
|
vec![notification.into()],
|
|
metrics,
|
|
)
|
|
} else if expected_versions[PeerSet::Validation] ==
|
|
Some(ValidationVersion::V2.into())
|
|
{
|
|
handle_peer_messages::<protocol_v2::ValidationProtocol, _>(
|
|
peer,
|
|
PeerSet::Validation,
|
|
&mut shared.0.lock().validation_peers,
|
|
vec![notification.into()],
|
|
metrics,
|
|
)
|
|
} else if expected_versions[PeerSet::Validation] ==
|
|
Some(ValidationVersion::VStaging.into())
|
|
{
|
|
handle_peer_messages::<protocol_vstaging::ValidationProtocol, _>(
|
|
peer,
|
|
PeerSet::Validation,
|
|
&mut shared.0.lock().validation_peers,
|
|
vec![notification.into()],
|
|
metrics,
|
|
)
|
|
} else {
|
|
gum::warn!(
|
|
target: LOG_TARGET,
|
|
version = ?expected_versions[PeerSet::Validation],
|
|
"Major logic bug. Peer somehow has unsupported validation protocol version."
|
|
);
|
|
|
|
never!("Only versions 1 and 2 are supported; peer set connection checked above; qed");
|
|
|
|
// If a peer somehow triggers this, we'll disconnect them
|
|
// eventually.
|
|
(Vec::new(), vec![UNCONNECTED_PEERSET_COST])
|
|
};
|
|
|
|
for report in reports {
|
|
network_service.report_peer(peer, report.into());
|
|
}
|
|
|
|
dispatch_validation_events_to_all(events, sender, &metrics).await;
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Handle notification event received over the collation protocol.
|
|
async fn handle_collation_message<AD>(
|
|
event: NotificationEvent,
|
|
network_service: &mut impl Network,
|
|
sender: &mut impl overseer::NetworkBridgeRxSenderTrait,
|
|
authority_discovery_service: &mut AD,
|
|
metrics: &Metrics,
|
|
shared: &Shared,
|
|
peerset_protocol_names: &PeerSetProtocolNames,
|
|
notification_service: &mut Box<dyn NotificationService>,
|
|
notification_sinks: &mut Arc<Mutex<HashMap<(PeerSet, PeerId), Box<dyn MessageSink>>>>,
|
|
) where
|
|
AD: validator_discovery::AuthorityDiscovery + Send,
|
|
{
|
|
match event {
|
|
NotificationEvent::ValidateInboundSubstream { peer, handshake, result_tx, .. } => {
|
|
// only accept peers whose role can be determined
|
|
let result = network_service
|
|
.peer_role(peer, handshake)
|
|
.map_or(ValidationResult::Reject, |_| ValidationResult::Accept);
|
|
let _ = result_tx.send(result);
|
|
},
|
|
NotificationEvent::NotificationStreamOpened {
|
|
peer,
|
|
handshake,
|
|
negotiated_fallback,
|
|
..
|
|
} => {
|
|
let role = match network_service.peer_role(peer, handshake) {
|
|
Some(role) => ObservedRole::from(role),
|
|
None => {
|
|
gum::debug!(
|
|
target: LOG_TARGET,
|
|
?peer,
|
|
"Failed to determine peer role for validation protocol",
|
|
);
|
|
return
|
|
},
|
|
};
|
|
|
|
let (peer_set, version) = {
|
|
let (peer_set, version) =
|
|
(PeerSet::Collation, PeerSet::Collation.get_main_version());
|
|
|
|
if let Some(fallback) = negotiated_fallback {
|
|
match peerset_protocol_names.try_get_protocol(&fallback) {
|
|
None => {
|
|
gum::debug!(
|
|
target: LOG_TARGET,
|
|
fallback = &*fallback,
|
|
?peer,
|
|
?peer_set,
|
|
"Unknown fallback",
|
|
);
|
|
|
|
return
|
|
},
|
|
Some((p2, v2)) => {
|
|
if p2 != peer_set {
|
|
gum::debug!(
|
|
target: LOG_TARGET,
|
|
fallback = &*fallback,
|
|
fallback_peerset = ?p2,
|
|
peerset = ?peer_set,
|
|
"Fallback mismatched peer-set",
|
|
);
|
|
|
|
return
|
|
}
|
|
|
|
(p2, v2)
|
|
},
|
|
}
|
|
} else {
|
|
(peer_set, version)
|
|
}
|
|
};
|
|
|
|
// store the notification sink to `notification_sinks` so both `NetworkBridgeRx`
|
|
// and `NetworkBridgeTx` can send messages to the peer.
|
|
match notification_service.message_sink(&peer) {
|
|
Some(sink) => {
|
|
notification_sinks.lock().insert((peer_set, peer), sink);
|
|
},
|
|
None => {
|
|
gum::warn!(
|
|
target: LOG_TARGET,
|
|
peer_set = ?peer_set,
|
|
version = %version,
|
|
peer = ?peer,
|
|
role = ?role,
|
|
"Message sink not available for peer",
|
|
);
|
|
return
|
|
},
|
|
}
|
|
|
|
gum::debug!(
|
|
target: LOG_TARGET,
|
|
action = "PeerConnected",
|
|
peer_set = ?peer_set,
|
|
version = %version,
|
|
peer = ?peer,
|
|
role = ?role
|
|
);
|
|
|
|
let local_view = {
|
|
let mut shared = shared.0.lock();
|
|
let peer_map = &mut shared.collation_peers;
|
|
|
|
match peer_map.entry(peer) {
|
|
hash_map::Entry::Occupied(_) => return,
|
|
hash_map::Entry::Vacant(vacant) => {
|
|
vacant.insert(PeerData { view: View::default(), version });
|
|
},
|
|
}
|
|
|
|
metrics.on_peer_connected(peer_set, version);
|
|
metrics.note_peer_count(peer_set, version, peer_map.len());
|
|
|
|
shared.local_view.clone().unwrap_or(View::default())
|
|
};
|
|
|
|
let maybe_authority =
|
|
authority_discovery_service.get_authority_ids_by_peer_id(peer).await;
|
|
|
|
dispatch_collation_events_to_all(
|
|
vec![
|
|
NetworkBridgeEvent::PeerConnected(peer, role, version, maybe_authority),
|
|
NetworkBridgeEvent::PeerViewChange(peer, View::default()),
|
|
],
|
|
sender,
|
|
)
|
|
.await;
|
|
|
|
match CollationVersion::try_from(version)
|
|
.expect("try_get_protocol has already checked version is known; qed")
|
|
{
|
|
CollationVersion::V1 => send_collation_message_v1(
|
|
vec![peer],
|
|
WireMessage::<protocol_v1::CollationProtocol>::ViewUpdate(local_view),
|
|
metrics,
|
|
notification_sinks,
|
|
),
|
|
CollationVersion::V2 => send_collation_message_v2(
|
|
vec![peer],
|
|
WireMessage::<protocol_v2::CollationProtocol>::ViewUpdate(local_view),
|
|
metrics,
|
|
notification_sinks,
|
|
),
|
|
}
|
|
},
|
|
NotificationEvent::NotificationStreamClosed { peer } => {
|
|
let (peer_set, version) = (PeerSet::Collation, PeerSet::Collation.get_main_version());
|
|
|
|
gum::debug!(
|
|
target: LOG_TARGET,
|
|
action = "PeerDisconnected",
|
|
?peer_set,
|
|
?peer
|
|
);
|
|
|
|
let was_connected = {
|
|
let mut shared = shared.0.lock();
|
|
let peer_map = &mut shared.collation_peers;
|
|
|
|
let w = peer_map.remove(&peer).is_some();
|
|
|
|
metrics.on_peer_disconnected(peer_set, version);
|
|
metrics.note_peer_count(peer_set, version, peer_map.len());
|
|
|
|
w
|
|
};
|
|
|
|
notification_sinks.lock().remove(&(peer_set, peer));
|
|
|
|
if was_connected && version == peer_set.get_main_version() {
|
|
dispatch_collation_event_to_all(NetworkBridgeEvent::PeerDisconnected(peer), sender)
|
|
.await;
|
|
}
|
|
},
|
|
NotificationEvent::NotificationReceived { peer, notification } => {
|
|
let expected_versions = {
|
|
let mut versions = PerPeerSet::<Option<ProtocolVersion>>::default();
|
|
let shared = shared.0.lock();
|
|
|
|
if let Some(peer_data) = shared.collation_peers.get(&peer) {
|
|
versions[PeerSet::Collation] = Some(peer_data.version);
|
|
}
|
|
|
|
versions
|
|
};
|
|
|
|
gum::trace!(
|
|
target: LOG_TARGET,
|
|
action = "PeerMessage",
|
|
peerset = ?PeerSet::Collation,
|
|
?peer,
|
|
);
|
|
|
|
let (events, reports) =
|
|
if expected_versions[PeerSet::Collation] == Some(CollationVersion::V1.into()) {
|
|
handle_peer_messages::<protocol_v1::CollationProtocol, _>(
|
|
peer,
|
|
PeerSet::Collation,
|
|
&mut shared.0.lock().collation_peers,
|
|
vec![notification.into()],
|
|
metrics,
|
|
)
|
|
} else if expected_versions[PeerSet::Collation] == Some(CollationVersion::V2.into())
|
|
{
|
|
handle_peer_messages::<protocol_v2::CollationProtocol, _>(
|
|
peer,
|
|
PeerSet::Collation,
|
|
&mut shared.0.lock().collation_peers,
|
|
vec![notification.into()],
|
|
metrics,
|
|
)
|
|
} else {
|
|
gum::warn!(
|
|
target: LOG_TARGET,
|
|
version = ?expected_versions[PeerSet::Collation],
|
|
"Major logic bug. Peer somehow has unsupported collation protocol version."
|
|
);
|
|
|
|
never!("Only versions 1 and 2 are supported; peer set connection checked above; qed");
|
|
|
|
// If a peer somehow triggers this, we'll disconnect them
|
|
// eventually.
|
|
(Vec::new(), vec![UNCONNECTED_PEERSET_COST])
|
|
};
|
|
|
|
for report in reports {
|
|
network_service.report_peer(peer, report.into());
|
|
}
|
|
|
|
dispatch_collation_events_to_all(events, sender).await;
|
|
},
|
|
}
|
|
}
|
|
|
|
async fn handle_network_messages<AD>(
|
|
mut sender: impl overseer::NetworkBridgeRxSenderTrait,
|
|
mut network_service: impl Network,
|
|
mut authority_discovery_service: AD,
|
|
metrics: Metrics,
|
|
shared: Shared,
|
|
peerset_protocol_names: PeerSetProtocolNames,
|
|
mut validation_service: Box<dyn NotificationService>,
|
|
mut collation_service: Box<dyn NotificationService>,
|
|
mut notification_sinks: Arc<Mutex<HashMap<(PeerSet, PeerId), Box<dyn MessageSink>>>>,
|
|
) -> Result<(), Error>
|
|
where
|
|
AD: validator_discovery::AuthorityDiscovery + Send,
|
|
{
|
|
loop {
|
|
futures::select! {
|
|
event = validation_service.next_event().fuse() => match event {
|
|
Some(event) => handle_validation_message(
|
|
event,
|
|
&mut network_service,
|
|
&mut sender,
|
|
&mut authority_discovery_service,
|
|
&metrics,
|
|
&shared,
|
|
&peerset_protocol_names,
|
|
&mut validation_service,
|
|
&mut notification_sinks,
|
|
).await,
|
|
None => return Err(Error::EventStreamConcluded),
|
|
},
|
|
event = collation_service.next_event().fuse() => match event {
|
|
Some(event) => handle_collation_message(
|
|
event,
|
|
&mut network_service,
|
|
&mut sender,
|
|
&mut authority_discovery_service,
|
|
&metrics,
|
|
&shared,
|
|
&peerset_protocol_names,
|
|
&mut collation_service,
|
|
&mut notification_sinks,
|
|
).await,
|
|
None => return Err(Error::EventStreamConcluded),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn flesh_out_topology_peers<AD, N>(ads: &mut AD, neighbors: N) -> Vec<TopologyPeerInfo>
|
|
where
|
|
AD: validator_discovery::AuthorityDiscovery,
|
|
N: IntoIterator<Item = (AuthorityDiscoveryId, ValidatorIndex)>,
|
|
N::IntoIter: std::iter::ExactSizeIterator,
|
|
{
|
|
let neighbors = neighbors.into_iter();
|
|
let mut peers = Vec::with_capacity(neighbors.len());
|
|
for (discovery_id, validator_index) in neighbors {
|
|
let addr = get_peer_id_by_authority_id(ads, discovery_id.clone()).await;
|
|
if addr.is_none() {
|
|
// See on why is not good in https://github.com/paritytech/polkadot-sdk/issues/2138
|
|
gum::debug!(
|
|
target: LOG_TARGET,
|
|
?validator_index,
|
|
"Could not determine peer_id for validator, let the team know in \n
|
|
https://github.com/paritytech/polkadot-sdk/issues/2138"
|
|
)
|
|
}
|
|
peers.push(TopologyPeerInfo {
|
|
peer_ids: addr.into_iter().collect(),
|
|
validator_index,
|
|
discovery_id,
|
|
});
|
|
}
|
|
|
|
peers
|
|
}
|
|
|
|
#[overseer::contextbounds(NetworkBridgeRx, prefix = self::overseer)]
|
|
async fn run_incoming_orchestra_signals<Context, AD>(
|
|
mut ctx: Context,
|
|
mut authority_discovery_service: AD,
|
|
shared: Shared,
|
|
sync_oracle: Box<dyn SyncOracle + Send>,
|
|
metrics: Metrics,
|
|
notification_sinks: Arc<Mutex<HashMap<(PeerSet, PeerId), Box<dyn MessageSink>>>>,
|
|
) -> Result<(), Error>
|
|
where
|
|
AD: validator_discovery::AuthorityDiscovery + Clone,
|
|
{
|
|
// This is kept sorted, descending, by block number.
|
|
let mut live_heads: Vec<ActivatedLeaf> = Vec::with_capacity(MAX_VIEW_HEADS);
|
|
let mut finalized_number = 0;
|
|
|
|
let mut mode = Mode::Syncing(sync_oracle);
|
|
loop {
|
|
match ctx.recv().fuse().await? {
|
|
FromOrchestra::Communication {
|
|
msg:
|
|
NetworkBridgeRxMessage::NewGossipTopology {
|
|
session,
|
|
local_index,
|
|
canonical_shuffling,
|
|
shuffled_indices,
|
|
},
|
|
} => {
|
|
gum::debug!(
|
|
target: LOG_TARGET,
|
|
action = "NewGossipTopology",
|
|
?session,
|
|
?local_index,
|
|
"Gossip topology has changed",
|
|
);
|
|
|
|
let topology_peers =
|
|
flesh_out_topology_peers(&mut authority_discovery_service, canonical_shuffling)
|
|
.await;
|
|
|
|
dispatch_validation_event_to_all_unbounded(
|
|
NetworkBridgeEvent::NewGossipTopology(NewGossipTopology {
|
|
session,
|
|
topology: SessionGridTopology::new(shuffled_indices, topology_peers),
|
|
local_index,
|
|
}),
|
|
ctx.sender(),
|
|
);
|
|
},
|
|
FromOrchestra::Communication {
|
|
msg: NetworkBridgeRxMessage::UpdatedAuthorityIds { peer_id, authority_ids },
|
|
} => {
|
|
gum::debug!(
|
|
target: LOG_TARGET,
|
|
action = "UpdatedAuthorityIds",
|
|
?peer_id,
|
|
?authority_ids,
|
|
"`AuthorityDiscoveryId`s have changed",
|
|
);
|
|
// using unbounded send to avoid cycles
|
|
// the messages are sent only once per session up to one per peer
|
|
dispatch_collation_event_to_all_unbounded(
|
|
NetworkBridgeEvent::UpdatedAuthorityIds(peer_id, authority_ids.clone()),
|
|
ctx.sender(),
|
|
);
|
|
dispatch_validation_event_to_all_unbounded(
|
|
NetworkBridgeEvent::UpdatedAuthorityIds(peer_id, authority_ids),
|
|
ctx.sender(),
|
|
);
|
|
},
|
|
FromOrchestra::Signal(OverseerSignal::Conclude) => return Ok(()),
|
|
FromOrchestra::Signal(OverseerSignal::ActiveLeaves(active_leaves)) => {
|
|
let ActiveLeavesUpdate { activated, deactivated } = active_leaves;
|
|
gum::trace!(
|
|
target: LOG_TARGET,
|
|
action = "ActiveLeaves",
|
|
has_activated = activated.is_some(),
|
|
num_deactivated = %deactivated.len(),
|
|
);
|
|
|
|
if let Some(activated) = activated {
|
|
let pos = live_heads
|
|
.binary_search_by(|probe| probe.number.cmp(&activated.number).reverse())
|
|
.unwrap_or_else(|i| i);
|
|
|
|
live_heads.insert(pos, activated);
|
|
}
|
|
live_heads.retain(|h| !deactivated.contains(&h.hash));
|
|
|
|
// if we're done syncing, set the mode to `Mode::Active`.
|
|
// Otherwise, we don't need to send view updates.
|
|
{
|
|
let is_done_syncing = match mode {
|
|
Mode::Active => true,
|
|
Mode::Syncing(ref mut sync_oracle) => !sync_oracle.is_major_syncing(),
|
|
};
|
|
|
|
if is_done_syncing {
|
|
mode = Mode::Active;
|
|
|
|
update_our_view(
|
|
&mut ctx,
|
|
&live_heads,
|
|
&shared,
|
|
finalized_number,
|
|
&metrics,
|
|
¬ification_sinks,
|
|
);
|
|
}
|
|
}
|
|
},
|
|
FromOrchestra::Signal(OverseerSignal::BlockFinalized(_hash, number)) => {
|
|
gum::trace!(target: LOG_TARGET, action = "BlockFinalized");
|
|
|
|
debug_assert!(finalized_number < number);
|
|
|
|
// we don't send the view updates here, but delay them until the next `ActiveLeaves`
|
|
// otherwise it might break assumptions of some of the subsystems
|
|
// that we never send the same `ActiveLeavesUpdate`
|
|
finalized_number = number;
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Main driver, processing network events and overseer signals.
|
|
///
|
|
/// THIS IS A HACK. We need to ensure we never hold the mutex across an `.await` boundary
|
|
/// and `parking_lot` currently does not provide `Send`, which helps us enforce that.
|
|
/// If this breaks, we need to find another way to protect ourselves.
|
|
///
|
|
/// ```compile_fail
|
|
/// #use parking_lot::MutexGuard;
|
|
/// #fn is_send<T: Send>();
|
|
/// #is_send::<parking_lot::MutexGuard<'static, ()>();
|
|
/// ```
|
|
#[overseer::contextbounds(NetworkBridgeRx, prefix = self::overseer)]
|
|
async fn run_network_in<N, AD, Context>(
|
|
bridge: NetworkBridgeRx<N, AD>,
|
|
mut ctx: Context,
|
|
) -> Result<(), Error>
|
|
where
|
|
N: Network,
|
|
AD: validator_discovery::AuthorityDiscovery + Clone,
|
|
{
|
|
let NetworkBridgeRx {
|
|
network_service,
|
|
authority_discovery_service,
|
|
metrics,
|
|
sync_oracle,
|
|
shared,
|
|
peerset_protocol_names,
|
|
validation_service,
|
|
collation_service,
|
|
notification_sinks,
|
|
} = bridge;
|
|
|
|
let (task, network_event_handler) = handle_network_messages(
|
|
ctx.sender().clone(),
|
|
network_service.clone(),
|
|
authority_discovery_service.clone(),
|
|
metrics.clone(),
|
|
shared.clone(),
|
|
peerset_protocol_names.clone(),
|
|
validation_service,
|
|
collation_service,
|
|
notification_sinks.clone(),
|
|
)
|
|
.remote_handle();
|
|
|
|
ctx.spawn_blocking("network-bridge-in-network-worker", Box::pin(task))?;
|
|
futures::pin_mut!(network_event_handler);
|
|
|
|
let orchestra_signal_handler = run_incoming_orchestra_signals(
|
|
ctx,
|
|
authority_discovery_service,
|
|
shared,
|
|
sync_oracle,
|
|
metrics,
|
|
notification_sinks,
|
|
);
|
|
|
|
futures::pin_mut!(orchestra_signal_handler);
|
|
|
|
futures::future::select(orchestra_signal_handler, network_event_handler)
|
|
.await
|
|
.factor_first()
|
|
.0?;
|
|
Ok(())
|
|
}
|
|
|
|
fn construct_view(
|
|
live_heads: impl DoubleEndedIterator<Item = Hash>,
|
|
finalized_number: BlockNumber,
|
|
) -> View {
|
|
View::new(live_heads.take(MAX_VIEW_HEADS), finalized_number)
|
|
}
|
|
|
|
#[overseer::contextbounds(NetworkBridgeRx, prefix = self::overseer)]
|
|
fn update_our_view<Context>(
|
|
ctx: &mut Context,
|
|
live_heads: &[ActivatedLeaf],
|
|
shared: &Shared,
|
|
finalized_number: BlockNumber,
|
|
metrics: &Metrics,
|
|
notification_sinks: &Arc<Mutex<HashMap<(PeerSet, PeerId), Box<dyn MessageSink>>>>,
|
|
) {
|
|
let new_view = construct_view(live_heads.iter().map(|v| v.hash), finalized_number);
|
|
|
|
let (validation_peers, collation_peers) = {
|
|
let mut shared = shared.0.lock();
|
|
|
|
// We only want to send a view update when the heads changed.
|
|
// A change in finalized block number only is _not_ sufficient.
|
|
//
|
|
// If this is the first view update since becoming active, but our view is empty,
|
|
// there is no need to send anything.
|
|
match shared.local_view {
|
|
Some(ref v) if v.check_heads_eq(&new_view) => return,
|
|
None if live_heads.is_empty() => {
|
|
shared.local_view = Some(new_view);
|
|
return
|
|
},
|
|
_ => {
|
|
shared.local_view = Some(new_view.clone());
|
|
},
|
|
}
|
|
|
|
(
|
|
shared
|
|
.validation_peers
|
|
.iter()
|
|
.map(|(peer_id, data)| (*peer_id, data.version))
|
|
.collect::<Vec<_>>(),
|
|
shared
|
|
.collation_peers
|
|
.iter()
|
|
.map(|(peer_id, data)| (*peer_id, data.version))
|
|
.collect::<Vec<_>>(),
|
|
)
|
|
};
|
|
|
|
let v1_validation_peers =
|
|
filter_by_peer_version(&validation_peers, ValidationVersion::V1.into());
|
|
let v1_collation_peers = filter_by_peer_version(&collation_peers, CollationVersion::V1.into());
|
|
|
|
let v2_validation_peers =
|
|
filter_by_peer_version(&validation_peers, ValidationVersion::V2.into());
|
|
let v2_collation_peers = filter_by_peer_version(&collation_peers, CollationVersion::V2.into());
|
|
|
|
let vstaging_validation_peers =
|
|
filter_by_peer_version(&validation_peers, ValidationVersion::VStaging.into());
|
|
|
|
send_validation_message_v1(
|
|
v1_validation_peers,
|
|
WireMessage::ViewUpdate(new_view.clone()),
|
|
metrics,
|
|
notification_sinks,
|
|
);
|
|
|
|
send_collation_message_v1(
|
|
v1_collation_peers,
|
|
WireMessage::ViewUpdate(new_view.clone()),
|
|
metrics,
|
|
notification_sinks,
|
|
);
|
|
|
|
send_validation_message_v2(
|
|
v2_validation_peers,
|
|
WireMessage::ViewUpdate(new_view.clone()),
|
|
metrics,
|
|
notification_sinks,
|
|
);
|
|
|
|
send_collation_message_v2(
|
|
v2_collation_peers,
|
|
WireMessage::ViewUpdate(new_view.clone()),
|
|
metrics,
|
|
notification_sinks,
|
|
);
|
|
|
|
send_validation_message_vstaging(
|
|
vstaging_validation_peers,
|
|
WireMessage::ViewUpdate(new_view.clone()),
|
|
metrics,
|
|
notification_sinks,
|
|
);
|
|
|
|
let our_view = OurView::new(
|
|
live_heads.iter().take(MAX_VIEW_HEADS).cloned().map(|a| (a.hash, a.span)),
|
|
finalized_number,
|
|
);
|
|
|
|
dispatch_validation_event_to_all_unbounded(
|
|
NetworkBridgeEvent::OurViewChange(our_view.clone()),
|
|
ctx.sender(),
|
|
);
|
|
|
|
dispatch_collation_event_to_all_unbounded(
|
|
NetworkBridgeEvent::OurViewChange(our_view),
|
|
ctx.sender(),
|
|
);
|
|
}
|
|
|
|
// Handle messages on a specific v1 peer-set. The peer is expected to be connected on that
|
|
// peer-set.
|
|
fn handle_peer_messages<RawMessage: Decode, OutMessage: From<RawMessage>>(
|
|
peer: PeerId,
|
|
peer_set: PeerSet,
|
|
peers: &mut HashMap<PeerId, PeerData>,
|
|
messages: Vec<Bytes>,
|
|
metrics: &Metrics,
|
|
) -> (Vec<NetworkBridgeEvent<OutMessage>>, Vec<Rep>) {
|
|
let peer_data = match peers.get_mut(&peer) {
|
|
None => return (Vec::new(), vec![UNCONNECTED_PEERSET_COST]),
|
|
Some(d) => d,
|
|
};
|
|
|
|
let mut outgoing_events = Vec::with_capacity(messages.len());
|
|
let mut reports = Vec::new();
|
|
|
|
for message in messages {
|
|
metrics.on_notification_received(peer_set, peer_data.version, message.len());
|
|
let message = match WireMessage::<RawMessage>::decode_all(&mut message.as_ref()) {
|
|
Err(_) => {
|
|
reports.push(MALFORMED_MESSAGE_COST);
|
|
continue
|
|
},
|
|
Ok(m) => m,
|
|
};
|
|
|
|
outgoing_events.push(match message {
|
|
WireMessage::ViewUpdate(new_view) => {
|
|
if new_view.len() > MAX_VIEW_HEADS ||
|
|
new_view.finalized_number < peer_data.view.finalized_number
|
|
{
|
|
reports.push(MALFORMED_VIEW_COST);
|
|
continue
|
|
} else if new_view.is_empty() {
|
|
reports.push(EMPTY_VIEW_COST);
|
|
continue
|
|
} else if new_view == peer_data.view {
|
|
continue
|
|
} else {
|
|
peer_data.view = new_view;
|
|
|
|
NetworkBridgeEvent::PeerViewChange(peer, peer_data.view.clone())
|
|
}
|
|
},
|
|
WireMessage::ProtocolMessage(message) =>
|
|
NetworkBridgeEvent::PeerMessage(peer, message.into()),
|
|
})
|
|
}
|
|
|
|
(outgoing_events, reports)
|
|
}
|
|
|
|
async fn dispatch_validation_event_to_all(
|
|
event: NetworkBridgeEvent<net_protocol::VersionedValidationProtocol>,
|
|
ctx: &mut impl overseer::NetworkBridgeRxSenderTrait,
|
|
metrics: &Metrics,
|
|
) {
|
|
dispatch_validation_events_to_all(std::iter::once(event), ctx, metrics).await
|
|
}
|
|
|
|
async fn dispatch_collation_event_to_all(
|
|
event: NetworkBridgeEvent<net_protocol::VersionedCollationProtocol>,
|
|
ctx: &mut impl overseer::NetworkBridgeRxSenderTrait,
|
|
) {
|
|
dispatch_collation_events_to_all(std::iter::once(event), ctx).await
|
|
}
|
|
|
|
fn dispatch_validation_event_to_all_unbounded(
|
|
event: NetworkBridgeEvent<net_protocol::VersionedValidationProtocol>,
|
|
sender: &mut impl overseer::NetworkBridgeRxSenderTrait,
|
|
) {
|
|
event
|
|
.focus()
|
|
.ok()
|
|
.map(StatementDistributionMessage::from)
|
|
.and_then(|msg| Some(sender.send_unbounded_message(msg)));
|
|
event
|
|
.focus()
|
|
.ok()
|
|
.map(BitfieldDistributionMessage::from)
|
|
.and_then(|msg| Some(sender.send_unbounded_message(msg)));
|
|
event
|
|
.focus()
|
|
.ok()
|
|
.map(ApprovalDistributionMessage::from)
|
|
.and_then(|msg| Some(sender.send_unbounded_message(msg)));
|
|
event
|
|
.focus()
|
|
.ok()
|
|
.map(GossipSupportMessage::from)
|
|
.and_then(|msg| Some(sender.send_unbounded_message(msg)));
|
|
}
|
|
|
|
fn dispatch_collation_event_to_all_unbounded(
|
|
event: NetworkBridgeEvent<net_protocol::VersionedCollationProtocol>,
|
|
sender: &mut impl overseer::NetworkBridgeRxSenderTrait,
|
|
) {
|
|
if let Ok(msg) = event.focus() {
|
|
sender.send_unbounded_message(CollatorProtocolMessage::NetworkBridgeUpdate(msg))
|
|
}
|
|
}
|
|
|
|
async fn dispatch_validation_events_to_all<I>(
|
|
events: I,
|
|
sender: &mut impl overseer::NetworkBridgeRxSenderTrait,
|
|
_metrics: &Metrics,
|
|
) where
|
|
I: IntoIterator<Item = NetworkBridgeEvent<net_protocol::VersionedValidationProtocol>>,
|
|
I::IntoIter: Send,
|
|
{
|
|
for event in events {
|
|
sender
|
|
.send_messages(event.focus().map(StatementDistributionMessage::from))
|
|
.await;
|
|
sender.send_messages(event.focus().map(BitfieldDistributionMessage::from)).await;
|
|
sender.send_messages(event.focus().map(ApprovalDistributionMessage::from)).await;
|
|
sender.send_messages(event.focus().map(GossipSupportMessage::from)).await;
|
|
}
|
|
}
|
|
|
|
async fn dispatch_collation_events_to_all<I>(
|
|
events: I,
|
|
ctx: &mut impl overseer::NetworkBridgeRxSenderTrait,
|
|
) where
|
|
I: IntoIterator<Item = NetworkBridgeEvent<net_protocol::VersionedCollationProtocol>>,
|
|
I::IntoIter: Send,
|
|
{
|
|
let messages_for = |event: NetworkBridgeEvent<net_protocol::VersionedCollationProtocol>| {
|
|
event.focus().ok().map(|m| CollatorProtocolMessage::NetworkBridgeUpdate(m))
|
|
};
|
|
|
|
ctx.send_messages(events.into_iter().flat_map(messages_for)).await
|
|
}
|