// 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 .
//! `NetworkBackend` implementation for `litep2p`.
use crate::{
config::{
FullNetworkConfiguration, IncomingRequest, NodeKeyConfig, NotificationHandshake, Params,
SetConfig, TransportConfig,
},
error::Error,
event::{DhtEvent, Event},
litep2p::{
discovery::{Discovery, DiscoveryEvent},
peerstore::Peerstore,
service::{Litep2pNetworkService, NetworkServiceCommand},
shim::{
bitswap::BitswapServer,
notification::{
config::{NotificationProtocolConfig, ProtocolControlHandle},
peerset::PeersetCommand,
},
request_response::{RequestResponseConfig, RequestResponseProtocol},
},
},
multiaddr::{Multiaddr, Protocol},
peer_store::PeerStoreProvider,
protocol,
service::{
metrics::{register_without_sources, MetricSources, Metrics, NotificationMetrics},
out_events,
traits::{BandwidthSink, NetworkBackend, NetworkService},
},
NetworkStatus, NotificationService, ProtocolName,
};
use codec::Encode;
use futures::StreamExt;
use libp2p::kad::RecordKey;
use litep2p::{
config::ConfigBuilder,
crypto::ed25519::{Keypair, SecretKey},
executor::Executor,
protocol::{
libp2p::{bitswap::Config as BitswapConfig, kademlia::QueryId},
request_response::ConfigBuilder as RequestResponseConfigBuilder,
},
transport::{
tcp::config::Config as TcpTransportConfig,
websocket::config::Config as WebSocketTransportConfig, Endpoint,
},
types::ConnectionId,
Error as Litep2pError, Litep2p, Litep2pEvent, ProtocolName as Litep2pProtocolName,
};
use parking_lot::RwLock;
use prometheus_endpoint::Registry;
use sc_client_api::BlockBackend;
use sc_network_common::{role::Roles, ExHashT};
use sc_network_types::PeerId;
use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver};
use sp_runtime::traits::Block as BlockT;
use std::{
cmp,
collections::{hash_map::Entry, HashMap, HashSet},
fs,
future::Future,
io, iter,
pin::Pin,
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
},
time::{Duration, Instant},
};
mod discovery;
mod peerstore;
mod service;
mod shim;
/// Litep2p bandwidth sink.
struct Litep2pBandwidthSink {
sink: litep2p::BandwidthSink,
}
impl BandwidthSink for Litep2pBandwidthSink {
fn total_inbound(&self) -> u64 {
self.sink.inbound() as u64
}
fn total_outbound(&self) -> u64 {
self.sink.outbound() as u64
}
}
/// Litep2p task executor.
struct Litep2pExecutor {
/// Executor.
executor: Box + Send>>) + Send + Sync>,
}
impl Executor for Litep2pExecutor {
fn run(&self, future: Pin + Send>>) {
(self.executor)(future)
}
fn run_with_name(&self, _: &'static str, future: Pin + Send>>) {
(self.executor)(future)
}
}
/// Logging target for the file.
const LOG_TARGET: &str = "sub-libp2p";
/// Peer context.
struct ConnectionContext {
/// Peer endpoints.
endpoints: HashMap,
/// Number of active connections.
num_connections: usize,
}
/// Networking backend for `litep2p`.
pub struct Litep2pNetworkBackend {
/// Main `litep2p` object.
litep2p: Litep2p,
/// `NetworkService` implementation for `Litep2pNetworkBackend`.
network_service: Arc,
/// RX channel for receiving commands from `Litep2pNetworkService`.
cmd_rx: TracingUnboundedReceiver,
/// `Peerset` handles to notification protocols.
peerset_handles: HashMap,
/// Pending `GET_VALUE` queries.
pending_get_values: HashMap,
/// Pending `PUT_VALUE` queries.
pending_put_values: HashMap,
/// Discovery.
discovery: Discovery,
/// Number of connected peers.
num_connected: Arc,
/// Connected peers.
peers: HashMap,
/// Peerstore.
peerstore_handle: Arc,
/// Block announce protocol name.
block_announce_protocol: ProtocolName,
/// Sender for DHT events.
event_streams: out_events::OutChannels,
/// Prometheus metrics.
metrics: Option,
/// External addresses.
external_addresses: Arc>>,
}
impl Litep2pNetworkBackend {
/// From an iterator of multiaddress(es), parse and group all addresses of peers
/// so that litep2p can consume the information easily.
fn parse_addresses(
addresses: impl Iterator,
) -> HashMap> {
addresses
.into_iter()
.filter_map(|address| match address.iter().next() {
Some(
Protocol::Dns(_) |
Protocol::Dns4(_) |
Protocol::Dns6(_) |
Protocol::Ip6(_) |
Protocol::Ip4(_),
) => match address.iter().find(|protocol| std::matches!(protocol, Protocol::P2p(_)))
{
Some(Protocol::P2p(multihash)) => PeerId::from_multihash(multihash)
.map_or(None, |peer| Some((peer, Some(address)))),
_ => None,
},
Some(Protocol::P2p(multihash)) =>
PeerId::from_multihash(multihash).map_or(None, |peer| Some((peer, None))),
_ => None,
})
.fold(HashMap::new(), |mut acc, (peer, maybe_address)| {
let entry = acc.entry(peer).or_default();
maybe_address.map(|address| entry.push(address));
acc
})
}
/// Add new known addresses to `litep2p` and return the parsed peer IDs.
fn add_addresses(&mut self, peers: impl Iterator) -> HashSet {
Self::parse_addresses(peers.into_iter())
.into_iter()
.filter_map(|(peer, addresses)| {
// `peers` contained multiaddress in the form `/p2p/`
if addresses.is_empty() {
return Some(peer)
}
if self.litep2p.add_known_address(peer.into(), addresses.clone().into_iter()) == 0 {
log::warn!(
target: LOG_TARGET,
"couldn't add any addresses for {peer:?} and it won't be added as reserved peer",
);
return None
}
self.peerstore_handle.add_known_peer(peer);
Some(peer)
})
.collect()
}
}
impl Litep2pNetworkBackend {
/// Get `litep2p` keypair from `NodeKeyConfig`.
fn get_keypair(node_key: &NodeKeyConfig) -> Result<(Keypair, litep2p::PeerId), Error> {
let secret = libp2p::identity::Keypair::try_into_ed25519(node_key.clone().into_keypair()?)
.map_err(|error| {
log::error!(target: LOG_TARGET, "failed to convert to ed25519: {error:?}");
Error::Io(io::ErrorKind::InvalidInput.into())
})?
.secret();
let mut secret = secret.as_ref().iter().cloned().collect::>();
let secret = SecretKey::from_bytes(&mut secret)
.map_err(|_| Error::Io(io::ErrorKind::InvalidInput.into()))?;
let local_identity = Keypair::from(secret);
let local_public = local_identity.public();
let local_peer_id = local_public.to_peer_id();
Ok((local_identity, local_peer_id))
}
/// Configure transport protocols for `Litep2pNetworkBackend`.
fn configure_transport(
config: &FullNetworkConfiguration,
) -> ConfigBuilder {
let _ = match config.network_config.transport {
TransportConfig::MemoryOnly => panic!("memory transport not supported"),
TransportConfig::Normal { .. } => false,
};
let config_builder = ConfigBuilder::new();
// The yamux buffer size limit is configured to be equal to the maximum frame size
// of all protocols. 10 bytes are added to each limit for the length prefix that
// is not included in the upper layer protocols limit but is still present in the
// yamux buffer. These 10 bytes correspond to the maximum size required to encode
// a variable-length-encoding 64bits number. In other words, we make the
// assumption that no notification larger than 2^64 will ever be sent.
let yamux_maximum_buffer_size = {
let requests_max = config
.request_response_protocols
.iter()
.map(|cfg| usize::try_from(cfg.max_request_size).unwrap_or(usize::MAX));
let responses_max = config
.request_response_protocols
.iter()
.map(|cfg| usize::try_from(cfg.max_response_size).unwrap_or(usize::MAX));
let notifs_max = config
.notification_protocols
.iter()
.map(|cfg| usize::try_from(cfg.max_notification_size()).unwrap_or(usize::MAX));
// A "default" max is added to cover all the other protocols: ping, identify,
// kademlia, block announces, and transactions.
let default_max = cmp::max(
1024 * 1024,
usize::try_from(protocol::BLOCK_ANNOUNCES_TRANSACTIONS_SUBSTREAM_SIZE)
.unwrap_or(usize::MAX),
);
iter::once(default_max)
.chain(requests_max)
.chain(responses_max)
.chain(notifs_max)
.max()
.expect("iterator known to always yield at least one element; qed")
.saturating_add(10)
};
let yamux_config = {
let mut yamux_config = litep2p::yamux::Config::default();
// Enable proper flow-control: window updates are only sent when
// buffered data has been consumed.
yamux_config.set_window_update_mode(litep2p::yamux::WindowUpdateMode::OnRead);
yamux_config.set_max_buffer_size(yamux_maximum_buffer_size);
if let Some(yamux_window_size) = config.network_config.yamux_window_size {
yamux_config.set_receive_window(yamux_window_size);
}
yamux_config
};
let (tcp, websocket): (Vec