// This file is part of Substrate.
//
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
//
// This program 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.
//
// This program 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 this program. If not, see .
//
// If you read this, you are very thorough, congratulations.
//! Traits defined by `sc-network`.
use crate::{
config::{IncomingRequest, MultiaddrWithPeerId, NotificationHandshake, Params, SetConfig},
error::{self, Error},
event::Event,
network_state::NetworkState,
request_responses::{IfDisconnected, RequestFailure},
service::{metrics::NotificationMetrics, signature::Signature, PeerStoreProvider},
types::ProtocolName,
ReputationChange,
};
use futures::{channel::oneshot, Stream};
use prometheus_endpoint::Registry;
use sc_client_api::BlockBackend;
use sc_network_common::{role::ObservedRole, ExHashT};
pub use sc_network_types::{
kad::{Key as KademliaKey, Record},
multiaddr::Multiaddr,
PeerId,
};
use sp_runtime::traits::Block as BlockT;
use std::{
collections::HashSet,
fmt::Debug,
future::Future,
pin::Pin,
sync::Arc,
time::{Duration, Instant},
};
pub use libp2p::identity::SigningError;
/// Supertrait defining the services provided by [`NetworkBackend`] service handle.
pub trait NetworkService:
NetworkSigner
+ NetworkDHTProvider
+ NetworkStatusProvider
+ NetworkPeers
+ NetworkEventStream
+ NetworkStateInfo
+ NetworkRequest
+ Send
+ Sync
+ 'static
{
}
impl NetworkService for T where
T: NetworkSigner
+ NetworkDHTProvider
+ NetworkStatusProvider
+ NetworkPeers
+ NetworkEventStream
+ NetworkStateInfo
+ NetworkRequest
+ Send
+ Sync
+ 'static
{
}
/// Trait defining the required functionality from a notification protocol configuration.
pub trait NotificationConfig: Debug {
/// Get access to the `SetConfig` of the notification protocol.
fn set_config(&self) -> &SetConfig;
/// Get protocol name.
fn protocol_name(&self) -> &ProtocolName;
}
/// Trait defining the required functionality from a request-response protocol configuration.
pub trait RequestResponseConfig: Debug {
/// Get protocol name.
fn protocol_name(&self) -> &ProtocolName;
}
/// Trait defining required functionality from `PeerStore`.
#[async_trait::async_trait]
pub trait PeerStore {
/// Get handle to `PeerStore`.
fn handle(&self) -> Arc;
/// Start running `PeerStore` event loop.
async fn run(self);
}
/// Networking backend.
#[async_trait::async_trait]
pub trait NetworkBackend: Send + 'static {
/// Type representing notification protocol-related configuration.
type NotificationProtocolConfig: NotificationConfig;
/// Type representing request-response protocol-related configuration.
type RequestResponseProtocolConfig: RequestResponseConfig;
/// Type implementing `NetworkService` for the networking backend.
///
/// `NetworkService` allows other subsystems of the blockchain to interact with `sc-network`
/// using `NetworkService`.
type NetworkService: NetworkService + Clone;
/// Type implementing [`PeerStore`].
type PeerStore: PeerStore;
/// Bitswap config.
type BitswapConfig;
/// Create new `NetworkBackend`.
fn new(params: Params) -> Result
where
Self: Sized;
/// Get handle to `NetworkService` of the `NetworkBackend`.
fn network_service(&self) -> Arc;
/// Create [`PeerStore`].
fn peer_store(bootnodes: Vec, metrics_registry: Option) -> Self::PeerStore;
/// Register metrics that are used by the notification protocols.
fn register_notification_metrics(registry: Option<&Registry>) -> NotificationMetrics;
/// Create Bitswap server.
fn bitswap_server(
client: Arc + Send + Sync>,
) -> (Pin + Send>>, Self::BitswapConfig);
/// Create notification protocol configuration and an associated `NotificationService`
/// for the protocol.
fn notification_config(
protocol_name: ProtocolName,
fallback_names: Vec,
max_notification_size: u64,
handshake: Option,
set_config: SetConfig,
metrics: NotificationMetrics,
peerstore_handle: Arc,
) -> (Self::NotificationProtocolConfig, Box);
/// Create request-response protocol configuration.
fn request_response_config(
protocol_name: ProtocolName,
fallback_names: Vec,
max_request_size: u64,
max_response_size: u64,
request_timeout: Duration,
inbound_queue: Option>,
) -> Self::RequestResponseProtocolConfig;
/// Start [`NetworkBackend`] event loop.
async fn run(mut self);
}
/// Signer with network identity
pub trait NetworkSigner {
/// Signs the message with the `KeyPair` that defines the local [`PeerId`].
fn sign_with_local_identity(&self, msg: Vec) -> Result;
/// Verify signature using peer's public key.
///
/// `public_key` must be Protobuf-encoded ed25519 public key.
///
/// Returns `Err(())` if public cannot be parsed into a valid ed25519 public key.
fn verify(
&self,
peer_id: sc_network_types::PeerId,
public_key: &Vec,
signature: &Vec,
message: &Vec,
) -> Result;
}
impl NetworkSigner for Arc
where
T: ?Sized,
T: NetworkSigner,
{
fn sign_with_local_identity(&self, msg: Vec) -> Result {
T::sign_with_local_identity(self, msg)
}
fn verify(
&self,
peer_id: sc_network_types::PeerId,
public_key: &Vec,
signature: &Vec,
message: &Vec,
) -> Result {
T::verify(self, peer_id, public_key, signature, message)
}
}
/// Provides access to the networking DHT.
pub trait NetworkDHTProvider {
/// Start finding closest peers to the target.
fn find_closest_peers(&self, target: PeerId);
/// Start getting a value from the DHT.
fn get_value(&self, key: &KademliaKey);
/// Start putting a value in the DHT.
fn put_value(&self, key: KademliaKey, value: Vec);
/// Start putting the record to `peers`.
///
/// If `update_local_storage` is true the local storage is udpated as well.
fn put_record_to(&self, record: Record, peers: HashSet, update_local_storage: bool);
/// Store a record in the DHT memory store.
fn store_record(
&self,
key: KademliaKey,
value: Vec,
publisher: Option,
expires: Option,
);
/// Register this node as a provider for `key` on the DHT.
fn start_providing(&self, key: KademliaKey);
/// Deregister this node as a provider for `key` on the DHT.
fn stop_providing(&self, key: KademliaKey);
/// Start getting the list of providers for `key` on the DHT.
fn get_providers(&self, key: KademliaKey);
}
impl NetworkDHTProvider for Arc
where
T: ?Sized,
T: NetworkDHTProvider,
{
fn find_closest_peers(&self, target: PeerId) {
T::find_closest_peers(self, target)
}
fn get_value(&self, key: &KademliaKey) {
T::get_value(self, key)
}
fn put_value(&self, key: KademliaKey, value: Vec) {
T::put_value(self, key, value)
}
fn put_record_to(&self, record: Record, peers: HashSet, update_local_storage: bool) {
T::put_record_to(self, record, peers, update_local_storage)
}
fn store_record(
&self,
key: KademliaKey,
value: Vec,
publisher: Option,
expires: Option,
) {
T::store_record(self, key, value, publisher, expires)
}
fn start_providing(&self, key: KademliaKey) {
T::start_providing(self, key)
}
fn stop_providing(&self, key: KademliaKey) {
T::stop_providing(self, key)
}
fn get_providers(&self, key: KademliaKey) {
T::get_providers(self, key)
}
}
/// Provides an ability to set a fork sync request for a particular block.
pub trait NetworkSyncForkRequest {
/// Notifies the sync service to try and sync the given block from the given
/// peers.
///
/// If the given vector of peers is empty then the underlying implementation
/// should make a best effort to fetch the block from any peers it is
/// connected to (NOTE: this assumption will change in the future #3629).
fn set_sync_fork_request(&self, peers: Vec, hash: BlockHash, number: BlockNumber);
}
impl NetworkSyncForkRequest for Arc
where
T: ?Sized,
T: NetworkSyncForkRequest,
{
fn set_sync_fork_request(&self, peers: Vec, hash: BlockHash, number: BlockNumber) {
T::set_sync_fork_request(self, peers, hash, number)
}
}
/// Overview status of the network.
#[derive(Clone)]
pub struct NetworkStatus {
/// Total number of connected peers.
pub num_connected_peers: usize,
/// The total number of bytes received.
pub total_bytes_inbound: u64,
/// The total number of bytes sent.
pub total_bytes_outbound: u64,
}
/// Provides high-level status information about network.
#[async_trait::async_trait]
pub trait NetworkStatusProvider {
/// High-level network status information.
///
/// Returns an error if the `NetworkWorker` is no longer running.
async fn status(&self) -> Result;
/// Get the network state.
///
/// Returns an error if the `NetworkWorker` is no longer running.
async fn network_state(&self) -> Result;
}
// Manual implementation to avoid extra boxing here
impl NetworkStatusProvider for Arc
where
T: ?Sized,
T: NetworkStatusProvider,
{
fn status<'life0, 'async_trait>(
&'life0 self,
) -> Pin> + Send + 'async_trait>>
where
'life0: 'async_trait,
Self: 'async_trait,
{
T::status(self)
}
fn network_state<'life0, 'async_trait>(
&'life0 self,
) -> Pin> + Send + 'async_trait>>
where
'life0: 'async_trait,
Self: 'async_trait,
{
T::network_state(self)
}
}
/// Provides low-level API for manipulating network peers.
#[async_trait::async_trait]
pub trait NetworkPeers {
/// Set authorized peers.
///
/// Need a better solution to manage authorized peers, but now just use reserved peers for
/// prototyping.
fn set_authorized_peers(&self, peers: HashSet);
/// Set authorized_only flag.
///
/// Need a better solution to decide authorized_only, but now just use reserved_only flag for
/// prototyping.
fn set_authorized_only(&self, reserved_only: bool);
/// Adds an address known to a node.
fn add_known_address(&self, peer_id: PeerId, addr: Multiaddr);
/// Report a given peer as either beneficial (+) or costly (-) according to the
/// given scalar.
fn report_peer(&self, peer_id: PeerId, cost_benefit: ReputationChange);
/// Get peer reputation.
fn peer_reputation(&self, peer_id: &PeerId) -> i32;
/// Disconnect from a node as soon as possible.
///
/// This triggers the same effects as if the connection had closed itself spontaneously.
fn disconnect_peer(&self, peer_id: PeerId, protocol: ProtocolName);
/// Connect to unreserved peers and allow unreserved peers to connect for syncing purposes.
fn accept_unreserved_peers(&self);
/// Disconnect from unreserved peers and deny new unreserved peers to connect for syncing
/// purposes.
fn deny_unreserved_peers(&self);
/// Adds a `PeerId` and its `Multiaddr` as reserved for a sync protocol (default peer set).
///
/// Returns an `Err` if the given string is not a valid multiaddress
/// or contains an invalid peer ID (which includes the local peer ID).
fn add_reserved_peer(&self, peer: MultiaddrWithPeerId) -> Result<(), String>;
/// Removes a `PeerId` from the list of reserved peers for a sync protocol (default peer set).
fn remove_reserved_peer(&self, peer_id: PeerId);
/// Sets the reserved set of a protocol to the given set of peers.
///
/// Each `Multiaddr` must end with a `/p2p/` component containing the `PeerId`. It can also
/// consist of only `/p2p/`.
///
/// The node will start establishing/accepting connections and substreams to/from peers in this
/// set, if it doesn't have any substream open with them yet.
///
/// Note however, if a call to this function results in less peers on the reserved set, they
/// will not necessarily get disconnected (depending on available free slots in the peer set).
/// If you want to also disconnect those removed peers, you will have to call
/// `remove_from_peers_set` on those in addition to updating the reserved set. You can omit
/// this step if the peer set is in reserved only mode.
///
/// Returns an `Err` if one of the given addresses is invalid or contains an
/// invalid peer ID (which includes the local peer ID), or if `protocol` does not
/// refer to a known protocol.
fn set_reserved_peers(
&self,
protocol: ProtocolName,
peers: HashSet,
) -> Result<(), String>;
/// Add peers to a peer set.
///
/// Each `Multiaddr` must end with a `/p2p/` component containing the `PeerId`. It can also
/// consist of only `/p2p/`.
///
/// Returns an `Err` if one of the given addresses is invalid or contains an
/// invalid peer ID (which includes the local peer ID), or if `protocol` does not
/// refer to a know protocol.
fn add_peers_to_reserved_set(
&self,
protocol: ProtocolName,
peers: HashSet,
) -> Result<(), String>;
/// Remove peers from a peer set.
///
/// Returns `Err` if `protocol` does not refer to a known protocol.
fn remove_peers_from_reserved_set(
&self,
protocol: ProtocolName,
peers: Vec,
) -> Result<(), String>;
/// Returns the number of peers in the sync peer set we're connected to.
fn sync_num_connected(&self) -> usize;
/// Attempt to get peer role.
///
/// Right now the peer role is decoded from the received handshake for all protocols
/// (`/block-announces/1` has other information as well). If the handshake cannot be
/// decoded into a role, the role queried from `PeerStore` and if the role is not stored
/// there either, `None` is returned and the peer should be discarded.
fn peer_role(&self, peer_id: PeerId, handshake: Vec) -> Option;
/// Get the list of reserved peers.
///
/// Returns an error if the `NetworkWorker` is no longer running.
async fn reserved_peers(&self) -> Result, ()>;
}
// Manual implementation to avoid extra boxing here
#[async_trait::async_trait]
impl NetworkPeers for Arc
where
T: ?Sized,
T: NetworkPeers,
{
fn set_authorized_peers(&self, peers: HashSet) {
T::set_authorized_peers(self, peers)
}
fn set_authorized_only(&self, reserved_only: bool) {
T::set_authorized_only(self, reserved_only)
}
fn add_known_address(&self, peer_id: PeerId, addr: Multiaddr) {
T::add_known_address(self, peer_id, addr)
}
fn report_peer(&self, peer_id: PeerId, cost_benefit: ReputationChange) {
T::report_peer(self, peer_id, cost_benefit)
}
fn peer_reputation(&self, peer_id: &PeerId) -> i32 {
T::peer_reputation(self, peer_id)
}
fn disconnect_peer(&self, peer_id: PeerId, protocol: ProtocolName) {
T::disconnect_peer(self, peer_id, protocol)
}
fn accept_unreserved_peers(&self) {
T::accept_unreserved_peers(self)
}
fn deny_unreserved_peers(&self) {
T::deny_unreserved_peers(self)
}
fn add_reserved_peer(&self, peer: MultiaddrWithPeerId) -> Result<(), String> {
T::add_reserved_peer(self, peer)
}
fn remove_reserved_peer(&self, peer_id: PeerId) {
T::remove_reserved_peer(self, peer_id)
}
fn set_reserved_peers(
&self,
protocol: ProtocolName,
peers: HashSet,
) -> Result<(), String> {
T::set_reserved_peers(self, protocol, peers)
}
fn add_peers_to_reserved_set(
&self,
protocol: ProtocolName,
peers: HashSet,
) -> Result<(), String> {
T::add_peers_to_reserved_set(self, protocol, peers)
}
fn remove_peers_from_reserved_set(
&self,
protocol: ProtocolName,
peers: Vec,
) -> Result<(), String> {
T::remove_peers_from_reserved_set(self, protocol, peers)
}
fn sync_num_connected(&self) -> usize {
T::sync_num_connected(self)
}
fn peer_role(&self, peer_id: PeerId, handshake: Vec) -> Option {
T::peer_role(self, peer_id, handshake)
}
fn reserved_peers<'life0, 'async_trait>(
&'life0 self,
) -> Pin, ()>> + Send + 'async_trait>>
where
'life0: 'async_trait,
Self: 'async_trait,
{
T::reserved_peers(self)
}
}
/// Provides access to network-level event stream.
pub trait NetworkEventStream {
/// Returns a stream containing the events that happen on the network.
///
/// If this method is called multiple times, the events are duplicated.
///
/// The stream never ends (unless the `NetworkWorker` gets shut down).
///
/// The name passed is used to identify the channel in the Prometheus metrics. Note that the
/// parameter is a `&'static str`, and not a `String`, in order to avoid accidentally having
/// an unbounded set of Prometheus metrics, which would be quite bad in terms of memory
fn event_stream(&self, name: &'static str) -> Pin + Send>>;
}
impl NetworkEventStream for Arc
where
T: ?Sized,
T: NetworkEventStream,
{
fn event_stream(&self, name: &'static str) -> Pin + Send>> {
T::event_stream(self, name)
}
}
/// Trait for providing information about the local network state
pub trait NetworkStateInfo {
/// Returns the local external addresses.
fn external_addresses(&self) -> Vec;
/// Returns the listening addresses (without trailing `/p2p/` with our `PeerId`).
fn listen_addresses(&self) -> Vec;
/// Returns the local Peer ID.
fn local_peer_id(&self) -> PeerId;
}
impl NetworkStateInfo for Arc
where
T: ?Sized,
T: NetworkStateInfo,
{
fn external_addresses(&self) -> Vec {
T::external_addresses(self)
}
fn listen_addresses(&self) -> Vec {
T::listen_addresses(self)
}
fn local_peer_id(&self) -> PeerId {
T::local_peer_id(self)
}
}
/// Reserved slot in the notifications buffer, ready to accept data.
pub trait NotificationSenderReady {
/// Consumes this slots reservation and actually queues the notification.
///
/// NOTE: Traits can't consume itself, but calling this method second time will return an error.
fn send(&mut self, notification: Vec) -> Result<(), NotificationSenderError>;
}
/// A `NotificationSender` allows for sending notifications to a peer with a chosen protocol.
#[async_trait::async_trait]
pub trait NotificationSender: Send + Sync + 'static {
/// Returns a future that resolves when the `NotificationSender` is ready to send a
/// notification.
async fn ready(&self)
-> Result, NotificationSenderError>;
}
/// Error returned by the notification sink.
#[derive(Debug, thiserror::Error)]
pub enum NotificationSenderError {
/// The notification receiver has been closed, usually because the underlying connection
/// closed.
///
/// Some of the notifications most recently sent may not have been received. However,
/// the peer may still be connected and a new notification sink for the same
/// protocol obtained from [`NotificationService::message_sink()`].
#[error("The notification receiver has been closed")]
Closed,
/// Protocol name hasn't been registered.
#[error("Protocol name hasn't been registered")]
BadProtocol,
}
/// Provides ability to send network requests.
#[async_trait::async_trait]
pub trait NetworkRequest {
/// Sends a single targeted request to a specific peer. On success, returns the response of
/// the peer.
///
/// Request-response protocols are a way to complement notifications protocols, but
/// notifications should remain the default ways of communicating information. For example, a
/// peer can announce something through a notification, after which the recipient can obtain
/// more information by performing a request.
/// As such, call this function with `IfDisconnected::ImmediateError` for `connect`. This way
/// you will get an error immediately for disconnected peers, instead of waiting for a
/// potentially very long connection attempt, which would suggest that something is wrong
/// anyway, as you are supposed to be connected because of the notification protocol.
///
/// No limit or throttling of concurrent outbound requests per peer and protocol are enforced.
/// Such restrictions, if desired, need to be enforced at the call site(s).
///
/// The protocol must have been registered through
/// `NetworkConfiguration::request_response_protocols`.
async fn request(
&self,
target: PeerId,
protocol: ProtocolName,
request: Vec,
fallback_request: Option<(Vec, ProtocolName)>,
connect: IfDisconnected,
) -> Result<(Vec, ProtocolName), RequestFailure>;
/// Variation of `request` which starts a request whose response is delivered on a provided
/// channel.
///
/// Instead of blocking and waiting for a reply, this function returns immediately, sending
/// responses via the passed in sender. This alternative API exists to make it easier to
/// integrate with message passing APIs.
///
/// Keep in mind that the connected receiver might receive a `Canceled` event in case of a
/// closing connection. This is expected behaviour. With `request` you would get a
/// `RequestFailure::Network(OutboundFailure::ConnectionClosed)` in that case.
fn start_request(
&self,
target: PeerId,
protocol: ProtocolName,
request: Vec,
fallback_request: Option<(Vec, ProtocolName)>,
tx: oneshot::Sender, ProtocolName), RequestFailure>>,
connect: IfDisconnected,
);
}
// Manual implementation to avoid extra boxing here
impl NetworkRequest for Arc
where
T: ?Sized,
T: NetworkRequest,
{
fn request<'life0, 'async_trait>(
&'life0 self,
target: PeerId,
protocol: ProtocolName,
request: Vec,
fallback_request: Option<(Vec, ProtocolName)>,
connect: IfDisconnected,
) -> Pin<
Box<
dyn Future