Merge network-libp2p into network (#2843)

* Move network-libp2p into network

* Merge libp2p_service into service

* Don't expose RegisteredProtocol in the API

* Extract DiscoveryBehaviour from Behaviour

* Restore libp2p tests

* Add a test for discovery

* Line width

* Remove bandwidth check

* Fix gitlab
This commit is contained in:
Pierre Krieger
2019-06-13 18:21:31 +02:00
committed by Gavin Wood
parent f4afdd2f0b
commit 12bbc2ffd9
28 changed files with 1072 additions and 1326 deletions
-1
View File
@@ -153,7 +153,6 @@ check-web-wasm:
- time cargo web build -p substrate-keystore
- time cargo web build -p substrate-executor
- time cargo web build -p substrate-network
- time cargo web build -p substrate-network-libp2p
- time cargo web build -p substrate-panic-handler
- time cargo web build -p substrate-peerset
- time cargo web build -p substrate-primitives
+10 -27
View File
@@ -4304,10 +4304,14 @@ name = "substrate-network"
version = "2.0.0"
dependencies = [
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
"derive_more 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"erased-serde 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"fork-tree 2.0.0",
"futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)",
"libp2p 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
"linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"linked_hash_set 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -4316,39 +4320,18 @@ dependencies = [
"parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-hex 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"sr-primitives 2.0.0",
"substrate-client 2.0.0",
"substrate-consensus-common 2.0.0",
"substrate-keyring 2.0.0",
"substrate-network-libp2p 2.0.0",
"substrate-peerset 2.0.0",
"substrate-primitives 2.0.0",
"substrate-test-runtime-client 2.0.0",
"tokio 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "substrate-network-libp2p"
version = "2.0.0"
dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
"erased-serde 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libp2p 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
"slog 2.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"slog_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
"sr-primitives 2.0.0",
"substrate-client 2.0.0",
"substrate-consensus-common 2.0.0",
"substrate-keyring 2.0.0",
"substrate-peerset 2.0.0",
"substrate-primitives 2.0.0",
"substrate-test-runtime-client 2.0.0",
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
-36
View File
@@ -1,36 +0,0 @@
[package]
description = "libp2p implementation of the ethcore network library"
homepage = "http://parity.io"
license = "GPL-3.0"
name = "substrate-network-libp2p"
version = "2.0.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
[dependencies]
byteorder = "1.3"
bytes = "0.4"
fnv = "1.0"
futures = "0.1"
libp2p = { version = "0.9.1", default-features = false, features = ["secp256k1", "libp2p-websocket"] }
parking_lot = "0.8.0"
lazy_static = "1.2"
log = "0.4"
rand = "0.6"
serde = { version = "1.0.70", features = ["derive"] }
serde_json = "1.0.24"
smallvec = "0.6"
substrate-peerset = { path = "../peerset" }
tokio-io = "0.1"
tokio-timer = "0.2"
unsigned-varint = { version = "0.2.1", features = ["codec"] }
void = "1.0"
zeroize = "0.6.0"
slog = { version = "^2", features = ["nested-values"] }
slog_derive = "0.1.1"
erased-serde = "0.3.9"
[dev-dependencies]
tempdir = "0.3"
tokio = "0.1"
-304
View File
@@ -1,304 +0,0 @@
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Libp2p network configuration.
use libp2p::identity::{Keypair, secp256k1, ed25519};
use libp2p::wasm_ext;
use libp2p::{Multiaddr, multiaddr::Protocol};
use std::error::Error;
use std::{io::{self, Write}, iter, fs, net::Ipv4Addr, path::{Path, PathBuf}};
use zeroize::Zeroize;
/// Network service configuration.
#[derive(Clone)]
pub struct NetworkConfiguration {
/// Directory path to store general network configuration. None means nothing will be saved.
pub config_path: Option<String>,
/// Directory path to store network-specific configuration. None means nothing will be saved.
pub net_config_path: Option<String>,
/// Multiaddresses to listen for incoming connections.
pub listen_addresses: Vec<Multiaddr>,
/// Multiaddresses to advertise. Detected automatically if empty.
pub public_addresses: Vec<Multiaddr>,
/// List of initial node addresses
pub boot_nodes: Vec<String>,
/// The node key configuration, which determines the node's network identity keypair.
pub node_key: NodeKeyConfig,
/// Maximum allowed number of incoming connections.
pub in_peers: u32,
/// Number of outgoing connections we're trying to maintain.
pub out_peers: u32,
/// List of reserved node addresses.
pub reserved_nodes: Vec<String>,
/// The non-reserved peer mode.
pub non_reserved_mode: NonReservedPeerMode,
/// Client identifier. Sent over the wire for debugging purposes.
pub client_version: String,
/// Name of the node. Sent over the wire for debugging purposes.
pub node_name: String,
/// If true, the network will use mDNS to discover other libp2p nodes on the local network
/// and connect to them if they support the same chain.
pub enable_mdns: bool,
/// Optional external implementation of a libp2p transport. Used in WASM contexts where we need
/// some binding between the networking provided by the operating system or environment and
/// libp2p.
///
/// This parameter exists whatever the target platform is, but it is expected to be set to
/// `Some` only when compiling for WASM.
pub wasm_external_transport: Option<wasm_ext::ExtTransport>,
}
impl Default for NetworkConfiguration {
fn default() -> Self {
NetworkConfiguration {
config_path: None,
net_config_path: None,
listen_addresses: Vec::new(),
public_addresses: Vec::new(),
boot_nodes: Vec::new(),
node_key: NodeKeyConfig::Ed25519(Secret::New),
in_peers: 25,
out_peers: 75,
reserved_nodes: Vec::new(),
non_reserved_mode: NonReservedPeerMode::Accept,
client_version: "unknown".into(),
node_name: "unknown".into(),
enable_mdns: false,
wasm_external_transport: None,
}
}
}
impl NetworkConfiguration {
/// Create a new instance of default settings.
pub fn new() -> Self {
Self::default()
}
/// Create new default configuration for localhost-only connection with random port (useful for testing)
pub fn new_local() -> NetworkConfiguration {
let mut config = NetworkConfiguration::new();
config.listen_addresses = vec![
iter::once(Protocol::Ip4(Ipv4Addr::new(127, 0, 0, 1)))
.chain(iter::once(Protocol::Tcp(0)))
.collect()
];
config
}
}
/// The policy for connections to non-reserved peers.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum NonReservedPeerMode {
/// Accept them. This is the default.
Accept,
/// Deny them.
Deny,
}
impl NonReservedPeerMode {
/// Attempt to parse the peer mode from a string.
pub fn parse(s: &str) -> Option<Self> {
match s {
"accept" => Some(NonReservedPeerMode::Accept),
"deny" => Some(NonReservedPeerMode::Deny),
_ => None,
}
}
}
/// The configuration of a node's secret key, describing the type of key
/// and how it is obtained. A node's identity keypair is the result of
/// the evaluation of the node key configuration.
#[derive(Clone)]
pub enum NodeKeyConfig {
/// A Secp256k1 secret key configuration.
Secp256k1(Secret<secp256k1::SecretKey>),
/// A Ed25519 secret key configuration.
Ed25519(Secret<ed25519::SecretKey>)
}
/// The options for obtaining a Secp256k1 secret key.
pub type Secp256k1Secret = Secret<secp256k1::SecretKey>;
/// The options for obtaining a Ed25519 secret key.
pub type Ed25519Secret = Secret<ed25519::SecretKey>;
/// The configuration options for obtaining a secret key `K`.
#[derive(Clone)]
pub enum Secret<K> {
/// Use the given secret key `K`.
Input(K),
/// Read the secret key from a file. If the file does not exist,
/// it is created with a newly generated secret key `K`. The format
/// of the file is determined by `K`:
///
/// * `secp256k1::SecretKey`: An unencoded 32 bytes Secp256k1 secret key.
/// * `ed25519::SecretKey`: An unencoded 32 bytes Ed25519 secret key.
File(PathBuf),
/// Always generate a new secret key `K`.
New
}
impl NodeKeyConfig {
/// Evaluate a `NodeKeyConfig` to obtain an identity `Keypair`:
///
/// * If the secret is configured as input, the corresponding keypair is returned.
///
/// * If the secret is configured as a file, it is read from that file, if it exists.
/// Otherwise a new secret is generated and stored. In either case, the
/// keypair obtained from the secret is returned.
///
/// * If the secret is configured to be new, it is generated and the corresponding
/// keypair is returned.
pub fn into_keypair(self) -> io::Result<Keypair> {
use NodeKeyConfig::*;
match self {
Secp256k1(Secret::New) =>
Ok(Keypair::generate_secp256k1()),
Secp256k1(Secret::Input(k)) =>
Ok(Keypair::Secp256k1(k.into())),
Secp256k1(Secret::File(f)) =>
get_secret(f,
|mut b| secp256k1::SecretKey::from_bytes(&mut b),
secp256k1::SecretKey::generate,
|b| b.to_bytes().to_vec())
.map(secp256k1::Keypair::from)
.map(Keypair::Secp256k1),
Ed25519(Secret::New) =>
Ok(Keypair::generate_ed25519()),
Ed25519(Secret::Input(k)) =>
Ok(Keypair::Ed25519(k.into())),
Ed25519(Secret::File(f)) =>
get_secret(f,
|mut b| ed25519::SecretKey::from_bytes(&mut b),
ed25519::SecretKey::generate,
|b| b.as_ref().to_vec())
.map(ed25519::Keypair::from)
.map(Keypair::Ed25519),
}
}
}
/// Load a secret key from a file, if it exists, or generate a
/// new secret key and write it to that file. In either case,
/// the secret key is returned.
fn get_secret<P, F, G, E, W, K>(file: P, parse: F, generate: G, serialize: W) -> io::Result<K>
where
P: AsRef<Path>,
F: for<'r> FnOnce(&'r mut [u8]) -> Result<K, E>,
G: FnOnce() -> K,
E: Error + Send + Sync + 'static,
W: Fn(&K) -> Vec<u8>,
{
std::fs::read(&file)
.and_then(|mut sk_bytes|
parse(&mut sk_bytes)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)))
.or_else(|e| {
if e.kind() == io::ErrorKind::NotFound {
file.as_ref().parent().map_or(Ok(()), fs::create_dir_all)?;
let sk = generate();
let mut sk_vec = serialize(&sk);
write_secret_file(file, &sk_vec)?;
sk_vec.zeroize();
Ok(sk)
} else {
Err(e)
}
})
}
/// Write secret bytes to a file.
fn write_secret_file<P>(path: P, sk_bytes: &[u8]) -> io::Result<()>
where
P: AsRef<Path>
{
let mut file = open_secret_file(&path)?;
file.write_all(sk_bytes)
}
/// Opens a file containing a secret key in write mode.
#[cfg(unix)]
fn open_secret_file<P>(path: P) -> io::Result<fs::File>
where
P: AsRef<Path>
{
use std::os::unix::fs::OpenOptionsExt;
fs::OpenOptions::new()
.write(true)
.create_new(true)
.mode(0o600)
.open(path)
}
/// Opens a file containing a secret key in write mode.
#[cfg(not(unix))]
fn open_secret_file<P>(path: P) -> Result<fs::File, io::Error>
where
P: AsRef<Path>
{
fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(path)
}
#[cfg(test)]
mod tests {
use super::*;
use tempdir::TempDir;
fn secret_bytes(kp: &Keypair) -> Vec<u8> {
match kp {
Keypair::Ed25519(p) => p.secret().as_ref().iter().cloned().collect(),
Keypair::Secp256k1(p) => p.secret().to_bytes().to_vec(),
_ => panic!("Unexpected keypair.")
}
}
#[test]
fn test_secret_file() {
let tmp = TempDir::new("x").unwrap();
std::fs::remove_dir(tmp.path()).unwrap(); // should be recreated
let file = tmp.path().join("x").to_path_buf();
let kp1 = NodeKeyConfig::Ed25519(Secret::File(file.clone())).into_keypair().unwrap();
let kp2 = NodeKeyConfig::Ed25519(Secret::File(file.clone())).into_keypair().unwrap();
assert!(file.is_file() && secret_bytes(&kp1) == secret_bytes(&kp2))
}
#[test]
fn test_secret_input() {
let sk = secp256k1::SecretKey::generate();
let kp1 = NodeKeyConfig::Secp256k1(Secret::Input(sk.clone())).into_keypair().unwrap();
let kp2 = NodeKeyConfig::Secp256k1(Secret::Input(sk)).into_keypair().unwrap();
assert!(secret_bytes(&kp1) == secret_bytes(&kp2));
}
#[test]
fn test_secret_new() {
let kp1 = NodeKeyConfig::Ed25519(Secret::New).into_keypair().unwrap();
let kp2 = NodeKeyConfig::Ed25519(Secret::New).into_keypair().unwrap();
assert!(secret_bytes(&kp1) != secret_bytes(&kp2));
}
}
-282
View File
@@ -1,282 +0,0 @@
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Networking layer of Substrate.
//!
//! # Overview
//!
//! This crate handles the following network mechanisms:
//!
//! - Discovering nodes that are part of the network.
//! - Connecting to said nodes and accepting incoming connections.
//! - Encrypting communications between nodes.
//! - Ensure that nodes are really the `PeerId` that they pretend to be.
//! - Ensuring that the nodes belong to the same chain as us before reporting a new connection.
//!
//! From the point of view of our node, each other node on the network has a reputation value in
//! the form of an `i32`. We try to establish connections towards nodes with a higher reputation
//! over nodes with a lower reputation.
//!
//! Establishing connections to other nodes is automatically performed by this crate, and there is
//! no way to influence this, except by adjusting reputations.
//!
//! ## About the term "connecting"
//!
//! The documentation of this crate uses the words "connected" and "disconnected". It is important
//! to note that this doesn't correspond to actual TCP/IP connections and disconnections. Libp2p
//! will maintain connections that aren't reported by the API of this crate, and TCP/IP connections
//! can be kept alive even after we have reported a disconnect in order to potentially reuse them.
//!
//! # Usage
//!
//! > **Important**: This crate is unstable and the API and usage may change.
//!
//! The first step is to crate a [`RegisteredProtocol`] describing the protocol, and a
//! [`NetworkConfiguration`] describing the network. Then call [`start_service`] with them, which
//! returns a [`Service`] object and a [`substrate_peerset::PeersetHandle`].
//!
//! The former allows you to know what happens on the network and to send messages, while the
//! latter can be used to adjust the reputations of nodes.
//!
//! You must call the `poll` method of [`Service`] in order to make the network progress and in
//! order to update the internal state of the [`Service`]. Calling `poll` will produce
//! [`ServiceEvent`]s, which inform you of what happened on the network.
//!
//! Please keep in mind that the state of the [`Service`] only updates itself in a way
//! corresponding to the [`ServiceEvent`] that `poll` returns.
//!
//! Illustration:
//!
//! - You call [`Service::connected_peers`] to get the list of nodes we are connected to.
//! - If you then call [`Service::connected_peers`] again, the returned list will always be the
//! same, no matter what happened on the wire.
//! - If you then call [`Service::poll`] and a [`ServiceEvent::OpenedCustomProtocol`] event is
//! returned, then the concerned node, and only the concerned node, will be added to the list of
//! nodes we are connected to.
//! - Similarly, if [`Service::poll`] produces a [`ServiceEvent::ClosedCustomProtocol`] event, then
//! only the concerned node will disappear from the list.
//! - And if [`Service::poll`] returns neither [`ServiceEvent::OpenedCustomProtocol`] nor
//! [`ServiceEvent::ClosedCustomProtocol`], then the list of connected nodes doesn't change.
//!
//! ## Example
//!
//! ```no_run
//! # use futures::prelude::*;
//! use substrate_network_libp2p::ServiceEvent;
//!
//! let proto = substrate_network_libp2p::RegisteredProtocol::new(&b"hello"[..], &[0]);
//! let config = substrate_network_libp2p::NetworkConfiguration::default();
//! let (mut service, _peerset) = substrate_network_libp2p::start_service(config, proto).unwrap();
//!
//! tokio::run(futures::future::poll_fn(move || {
//! loop {
//! match service.poll().unwrap() {
//! Async::NotReady => return Ok(Async::NotReady),
//! Async::Ready(Some(ServiceEvent::OpenedCustomProtocol { peer_id, .. })) => {
//! println!("now connected to {:?}", peer_id);
//! service.send_custom_message(&peer_id, b"hello world!".to_vec());
//! }
//! Async::Ready(Some(ServiceEvent::ClosedCustomProtocol { peer_id, .. })) =>
//! println!("now disconnected from {:?}", peer_id),
//! Async::Ready(Some(ServiceEvent::CustomMessage { peer_id, message })) =>
//! println!("received message from {:?}: {:?}", peer_id, message),
//! Async::Ready(None) => return Ok(Async::Ready(())),
//! _ => {}
//! }
//! }
//! }));
//! ```
//!
mod behaviour;
mod config;
mod custom_proto;
mod service_task;
mod transport;
pub use crate::config::*;
pub use crate::custom_proto::{CustomMessage, RegisteredProtocol};
pub use crate::config::{NetworkConfiguration, NodeKeyConfig, Secret, NonReservedPeerMode};
pub use crate::service_task::{start_service, Service, ServiceEvent};
pub use libp2p::{Multiaddr, multiaddr, build_multiaddr};
pub use libp2p::{identity, PeerId, core::PublicKey};
use libp2p::core::nodes::ConnectedPoint;
use serde::{Deserialize, Serialize};
use slog_derive::SerdeValue;
use std::{collections::{HashMap, HashSet}, error, fmt, time::Duration};
/// Extension trait for `NetworkBehaviour` that also accepts discovering nodes.
pub trait DiscoveryNetBehaviour {
/// Notify the protocol that we have learned about the existence of nodes.
///
/// Can (or most likely will) be called multiple times with the same `PeerId`s.
///
/// Also note that there is no notification for expired nodes. The implementer must add a TTL
/// system, or remove nodes that will fail to reach.
fn add_discovered_nodes(&mut self, nodes: impl Iterator<Item = PeerId>);
}
/// Name of a protocol, transmitted on the wire. Should be unique for each chain.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ProtocolId(smallvec::SmallVec<[u8; 6]>);
impl<'a> From<&'a [u8]> for ProtocolId {
fn from(bytes: &'a [u8]) -> ProtocolId {
ProtocolId(bytes.into())
}
}
impl ProtocolId {
/// Exposes the `ProtocolId` as bytes.
pub fn as_bytes(&self) -> &[u8] {
self.0.as_ref()
}
}
/// Parses a string address and returns the component, if valid.
pub fn parse_str_addr(addr_str: &str) -> Result<(PeerId, Multiaddr), ParseErr> {
let mut addr: Multiaddr = addr_str.parse()?;
let who = match addr.pop() {
Some(multiaddr::Protocol::P2p(key)) => PeerId::from_multihash(key)
.map_err(|_| ParseErr::InvalidPeerId)?,
_ => return Err(ParseErr::PeerIdMissing),
};
Ok((who, addr))
}
/// Error that can be generated by `parse_str_addr`.
#[derive(Debug)]
pub enum ParseErr {
/// Error while parsing the multiaddress.
MultiaddrParse(multiaddr::Error),
/// Multihash of the peer ID is invalid.
InvalidPeerId,
/// The peer ID is missing from the address.
PeerIdMissing,
}
impl fmt::Display for ParseErr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseErr::MultiaddrParse(err) => write!(f, "{}", err),
ParseErr::InvalidPeerId => write!(f, "Peer id at the end of the address is invalid"),
ParseErr::PeerIdMissing => write!(f, "Peer id is missing from the address"),
}
}
}
impl error::Error for ParseErr {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
ParseErr::MultiaddrParse(err) => Some(err),
ParseErr::InvalidPeerId => None,
ParseErr::PeerIdMissing => None,
}
}
}
impl From<multiaddr::Error> for ParseErr {
fn from(err: multiaddr::Error) -> ParseErr {
ParseErr::MultiaddrParse(err)
}
}
/// Returns general information about the networking.
///
/// Meant for general diagnostic purposes.
///
/// **Warning**: This API is not stable.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, SerdeValue)]
#[serde(rename_all = "camelCase")]
pub struct NetworkState {
/// PeerId of the local node.
pub peer_id: String,
/// List of addresses the node is currently listening on.
pub listened_addresses: HashSet<Multiaddr>,
/// List of addresses the node knows it can be reached as.
pub external_addresses: HashSet<Multiaddr>,
/// List of node we're connected to.
pub connected_peers: HashMap<String, NetworkStatePeer>,
/// List of node that we know of but that we're not connected to.
pub not_connected_peers: HashMap<String, NetworkStateNotConnectedPeer>,
/// Downloaded bytes per second averaged over the past few seconds.
pub average_download_per_sec: u64,
/// Uploaded bytes per second averaged over the past few seconds.
pub average_upload_per_sec: u64,
/// State of the peerset manager.
pub peerset: serde_json::Value,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NetworkStatePeer {
/// How we are connected to the node.
pub endpoint: NetworkStatePeerEndpoint,
/// Node information, as provided by the node itself. Can be empty if not known yet.
pub version_string: Option<String>,
/// Latest ping duration with this node.
pub latest_ping_time: Option<Duration>,
/// If true, the peer is "enabled", which means that we try to open Substrate-related protocols
/// with this peer. If false, we stick to Kademlia and/or other network-only protocols.
pub enabled: bool,
/// If true, the peer is "open", which means that we have a Substrate-related protocol
/// with this peer.
pub open: bool,
/// List of addresses known for this node.
pub known_addresses: HashSet<Multiaddr>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NetworkStateNotConnectedPeer {
/// List of addresses known for this node.
pub known_addresses: HashSet<Multiaddr>,
/// Node information, as provided by the node itself, if we were ever connected to this node.
pub version_string: Option<String>,
/// Latest ping duration with this node, if we were ever connected to this node.
pub latest_ping_time: Option<Duration>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum NetworkStatePeerEndpoint {
/// We are dialing the given address.
Dialing(Multiaddr),
/// We are listening.
Listening {
/// Address we're listening on that received the connection.
listen_addr: Multiaddr,
/// Address data is sent back to.
send_back_addr: Multiaddr,
},
}
impl From<ConnectedPoint> for NetworkStatePeerEndpoint {
fn from(endpoint: ConnectedPoint) -> Self {
match endpoint {
ConnectedPoint::Dialer { address } =>
NetworkStatePeerEndpoint::Dialing(address),
ConnectedPoint::Listener { listen_addr, send_back_addr } =>
NetworkStatePeerEndpoint::Listening {
listen_addr,
send_back_addr
}
}
}
}
@@ -1,368 +0,0 @@
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
use crate::{
behaviour::Behaviour,
config::NodeKeyConfig,
transport, NetworkState, NetworkStatePeer, NetworkStateNotConnectedPeer
};
use crate::custom_proto::{CustomProto, CustomProtoOut, CustomMessage, RegisteredProtocol};
use crate::{NetworkConfiguration, NonReservedPeerMode, parse_str_addr};
use futures::{prelude::*, Stream};
use libp2p::{Multiaddr, core::swarm::NetworkBehaviour, PeerId};
use libp2p::core::{Swarm, nodes::Substream, transport::boxed::Boxed, muxing::StreamMuxerBox};
use libp2p::core::nodes::ConnectedPoint;
use log::{info, error, warn};
use std::fs;
use std::io::Error as IoError;
use std::path::Path;
use std::sync::Arc;
/// Starts the substrate libp2p service.
///
/// Returns a stream that must be polled regularly in order for the networking to function.
pub fn start_service<TMessage>(
config: NetworkConfiguration,
registered_custom: RegisteredProtocol<TMessage>,
) -> Result<(Service<TMessage>, substrate_peerset::PeersetHandle), IoError>
where TMessage: CustomMessage + Send + 'static {
if let Some(ref path) = config.net_config_path {
fs::create_dir_all(Path::new(path))?;
}
// List of multiaddresses that we know in the network.
let mut known_addresses = Vec::new();
let mut bootnodes = Vec::new();
let mut reserved_nodes = Vec::new();
// Process the bootnodes.
for bootnode in config.boot_nodes.iter() {
match parse_str_addr(bootnode) {
Ok((peer_id, addr)) => {
bootnodes.push(peer_id.clone());
known_addresses.push((peer_id, addr));
},
Err(_) => warn!(target: "sub-libp2p", "Not a valid bootnode address: {}", bootnode),
}
}
// Initialize the reserved peers.
for reserved in config.reserved_nodes.iter() {
if let Ok((peer_id, addr)) = parse_str_addr(reserved) {
reserved_nodes.push(peer_id.clone());
known_addresses.push((peer_id, addr));
} else {
warn!(target: "sub-libp2p", "Not a valid reserved node address: {}", reserved);
}
}
// Build the peerset.
let (peerset, peerset_handle) = substrate_peerset::Peerset::from_config(substrate_peerset::PeersetConfig {
in_peers: config.in_peers,
out_peers: config.out_peers,
bootnodes,
reserved_only: config.non_reserved_mode == NonReservedPeerMode::Deny,
reserved_nodes,
});
// Private and public keys configuration.
if let NodeKeyConfig::Secp256k1(_) = config.node_key {
warn!(target: "sub-libp2p", "Secp256k1 keys are deprecated in favour of ed25519");
}
let local_identity = config.node_key.clone().into_keypair()?;
let local_public = local_identity.public();
let local_peer_id = local_public.clone().into_peer_id();
info!(target: "sub-libp2p", "Local node identity is: {}", local_peer_id.to_base58());
// Build the swarm.
let (mut swarm, bandwidth) = {
let user_agent = format!("{} ({})", config.client_version, config.node_name);
let proto = CustomProto::new(registered_custom, peerset);
let behaviour = Behaviour::new(proto, user_agent, local_public, known_addresses, config.enable_mdns);
let (transport, bandwidth) = transport::build_transport(
local_identity,
config.wasm_external_transport
);
(Swarm::new(transport, behaviour, local_peer_id.clone()), bandwidth)
};
// Listen on multiaddresses.
for addr in &config.listen_addresses {
if let Err(err) = Swarm::listen_on(&mut swarm, addr.clone()) {
warn!(target: "sub-libp2p", "Can't listen on {} because: {:?}", addr, err)
}
}
// Add external addresses.
for addr in &config.public_addresses {
Swarm::add_external_address(&mut swarm, addr.clone());
}
let service = Service {
swarm,
bandwidth,
injected_events: Vec::new(),
};
Ok((service, peerset_handle))
}
/// Event produced by the service.
#[derive(Debug)]
pub enum ServiceEvent<TMessage> {
/// A custom protocol substream has been opened with a node.
OpenedCustomProtocol {
/// Identity of the node.
peer_id: PeerId,
/// Version of the protocol that was opened.
version: u8,
/// Node debug info
debug_info: String,
},
/// A custom protocol substream has been closed.
ClosedCustomProtocol {
/// Identity of the node.
peer_id: PeerId,
/// Node debug info
debug_info: String,
},
/// Receives a message on a custom protocol stream.
CustomMessage {
/// Identity of the node.
peer_id: PeerId,
/// Message that has been received.
message: TMessage,
},
/// The substream with a node is clogged. We should avoid sending data to it if possible.
Clogged {
/// Index of the node.
peer_id: PeerId,
/// Copy of the messages that are within the buffer, for further diagnostic.
messages: Vec<TMessage>,
},
}
/// Network service. Must be polled regularly in order for the networking to work.
pub struct Service<TMessage> where TMessage: CustomMessage {
/// Stream of events of the swarm.
swarm: Swarm<
Boxed<(PeerId, StreamMuxerBox), IoError>,
Behaviour<CustomProto<TMessage, Substream<StreamMuxerBox>>, CustomProtoOut<TMessage>, Substream<StreamMuxerBox>>
>,
/// Bandwidth logging system. Can be queried to know the average bandwidth consumed.
bandwidth: Arc<transport::BandwidthSinks>,
/// Events to produce on the Stream.
injected_events: Vec<ServiceEvent<TMessage>>,
}
impl<TMessage> Service<TMessage>
where TMessage: CustomMessage + Send + 'static {
/// Returns a struct containing tons of useful information about the network.
pub fn state(&mut self) -> NetworkState {
let open = self.swarm.user_protocol().open_peers().cloned().collect::<Vec<_>>();
let connected_peers = {
let swarm = &mut self.swarm;
open.iter().filter_map(move |peer_id| {
let known_addresses = NetworkBehaviour::addresses_of_peer(&mut **swarm, peer_id)
.into_iter().collect();
let endpoint = if let Some(e) = swarm.node(peer_id).map(|i| i.endpoint()) {
e.clone().into()
} else {
error!(target: "sub-libp2p", "Found state inconsistency between custom protocol \
and debug information about {:?}", peer_id);
return None
};
Some((peer_id.to_base58(), NetworkStatePeer {
endpoint,
version_string: swarm.node(peer_id).and_then(|i| i.client_version().map(|s| s.to_owned())).clone(),
latest_ping_time: swarm.node(peer_id).and_then(|i| i.latest_ping()),
enabled: swarm.user_protocol().is_enabled(&peer_id),
open: swarm.user_protocol().is_open(&peer_id),
known_addresses,
}))
}).collect()
};
let not_connected_peers = {
let swarm = &mut self.swarm;
let list = swarm.known_peers().filter(|p| open.iter().all(|n| n != *p))
.cloned().collect::<Vec<_>>();
list.into_iter().map(move |peer_id| {
(peer_id.to_base58(), NetworkStateNotConnectedPeer {
version_string: swarm.node(&peer_id).and_then(|i| i.client_version().map(|s| s.to_owned())).clone(),
latest_ping_time: swarm.node(&peer_id).and_then(|i| i.latest_ping()),
known_addresses: NetworkBehaviour::addresses_of_peer(&mut **swarm, &peer_id)
.into_iter().collect(),
})
}).collect()
};
NetworkState {
peer_id: Swarm::local_peer_id(&self.swarm).to_base58(),
listened_addresses: Swarm::listeners(&self.swarm).cloned().collect(),
external_addresses: Swarm::external_addresses(&self.swarm).cloned().collect(),
average_download_per_sec: self.bandwidth.average_download_per_sec(),
average_upload_per_sec: self.bandwidth.average_upload_per_sec(),
connected_peers,
not_connected_peers,
peerset: self.swarm.user_protocol_mut().peerset_debug_info(),
}
}
/// Returns an iterator that produces the list of addresses we're listening on.
#[inline]
pub fn listeners(&self) -> impl Iterator<Item = &Multiaddr> {
Swarm::listeners(&self.swarm)
}
/// Returns the downloaded bytes per second averaged over the past few seconds.
#[inline]
pub fn average_download_per_sec(&self) -> u64 {
self.bandwidth.average_download_per_sec()
}
/// Returns the uploaded bytes per second averaged over the past few seconds.
#[inline]
pub fn average_upload_per_sec(&self) -> u64 {
self.bandwidth.average_upload_per_sec()
}
/// Returns the peer id of the local node.
pub fn peer_id(&self) -> &PeerId {
Swarm::local_peer_id(&self.swarm)
}
/// Returns the list of all the peers we are connected to.
pub fn connected_peers<'a>(&'a self) -> impl Iterator<Item = &'a PeerId> + 'a {
self.swarm.user_protocol().open_peers()
}
/// Returns the way we are connected to a node. Returns `None` if we are not connected to it.
pub fn node_endpoint(&self, peer_id: &PeerId) -> Option<&ConnectedPoint> {
if self.swarm.user_protocol().is_open(peer_id) {
self.swarm.node(peer_id).map(|n| n.endpoint())
} else {
None
}
}
/// Returns the latest client version reported by a node. Can return `Some` even for nodes
/// we're not connected to.
pub fn node_client_version(&self, peer_id: &PeerId) -> Option<&str> {
self.swarm.node(peer_id).and_then(|n| n.client_version())
}
/// Sends a message to a peer using the custom protocol.
///
/// Has no effect if the connection to the node has been closed, or if the node index is
/// invalid.
pub fn send_custom_message(
&mut self,
peer_id: &PeerId,
message: TMessage
) {
self.swarm.user_protocol_mut().send_packet(peer_id, message);
}
/// Disconnects a peer.
///
/// This is asynchronous and will not immediately close the peer.
/// Corresponding closing events will be generated once the closing actually happens.
pub fn drop_node(&mut self, peer_id: &PeerId) {
self.swarm.user_protocol_mut().disconnect_peer(peer_id);
}
/// Adds a hard-coded address for the given peer, that never expires.
pub fn add_known_address(&mut self, peer_id: PeerId, addr: Multiaddr) {
self.swarm.add_known_address(peer_id, addr)
}
/// Get debug info for a given peer.
pub fn peer_debug_info(&self, who: &PeerId) -> String {
if let Some(node) = self.swarm.node(who) {
format!("{:?} {}", who, node.debug_info())
} else {
format!("{:?} (unknown)", who)
}
}
/// Polls for what happened on the network.
fn poll_swarm(&mut self) -> Poll<Option<ServiceEvent<TMessage>>, IoError> {
loop {
match self.swarm.poll() {
Ok(Async::Ready(Some(CustomProtoOut::CustomProtocolOpen { peer_id, version, .. }))) => {
let debug_info = self.peer_debug_info(&peer_id);
break Ok(Async::Ready(Some(ServiceEvent::OpenedCustomProtocol {
peer_id,
version,
debug_info,
})))
}
Ok(Async::Ready(Some(CustomProtoOut::CustomProtocolClosed { peer_id, .. }))) => {
let debug_info = self.peer_debug_info(&peer_id);
break Ok(Async::Ready(Some(ServiceEvent::ClosedCustomProtocol {
peer_id,
debug_info,
})))
}
Ok(Async::Ready(Some(CustomProtoOut::CustomMessage { peer_id, message }))) => {
break Ok(Async::Ready(Some(ServiceEvent::CustomMessage {
peer_id,
message,
})))
}
Ok(Async::Ready(Some(CustomProtoOut::Clogged { peer_id, messages }))) => {
break Ok(Async::Ready(Some(ServiceEvent::Clogged {
peer_id,
messages,
})))
}
Ok(Async::NotReady) => break Ok(Async::NotReady),
Ok(Async::Ready(None)) => unreachable!("The Swarm stream never ends"),
Err(_) => unreachable!("The Swarm never errors"),
}
}
}
}
impl<TMessage> Stream for Service<TMessage> where TMessage: CustomMessage + Send + 'static {
type Item = ServiceEvent<TMessage>;
type Error = IoError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
if !self.injected_events.is_empty() {
return Ok(Async::Ready(Some(self.injected_events.remove(0))));
}
match self.poll_swarm()? {
Async::Ready(value) => return Ok(Async::Ready(value)),
Async::NotReady => (),
}
// The only way we reach this is if we went through all the `NotReady` paths above,
// ensuring the current task is registered everywhere.
Ok(Async::NotReady)
}
}
+13 -3
View File
@@ -6,38 +6,48 @@ license = "GPL-3.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
[lib]
[dependencies]
bytes = "0.4"
derive_more = "0.14.0"
log = "0.4"
parking_lot = "0.8.0"
bitflags = "1.0"
fnv = "1.0"
futures = "0.1.17"
linked-hash-map = "0.5"
linked_hash_set = "0.1.3"
lru-cache = "0.1.1"
rustc-hex = "2.0"
rand = "0.6"
libp2p = { version = "0.9.1", default-features = false, features = ["secp256k1", "libp2p-websocket"] }
fork-tree = { path = "../../core/util/fork-tree" }
primitives = { package = "substrate-primitives", path = "../../core/primitives" }
consensus = { package = "substrate-consensus-common", path = "../../core/consensus/common" }
client = { package = "substrate-client", path = "../../core/client" }
runtime_primitives = { package = "sr-primitives", path = "../../core/sr-primitives" }
parity-codec = { version = "3.3", features = ["derive"] }
network_libp2p = { package = "substrate-network-libp2p", path = "../../core/network-libp2p" }
peerset = { package = "substrate-peerset", path = "../../core/peerset" }
serde = { version = "1.0.70", features = ["derive"] }
serde_json = "1.0.24"
slog = { version = "^2", features = ["nested-values"] }
slog_derive = "0.1.1"
smallvec = "0.6"
tokio-io = "0.1"
tokio-timer = "0.2.11"
tokio = { version = "0.1.11", optional = true }
unsigned-varint = { version = "0.2.1", features = ["codec"] }
keyring = { package = "substrate-keyring", path = "../../core/keyring", optional = true }
test-client = { package = "substrate-test-runtime-client", path = "../../core/test-runtime/client", optional = true }
erased-serde = "0.3.9"
void = "1.0"
zeroize = "0.6.0"
[dev-dependencies]
env_logger = { version = "0.6" }
keyring = { package = "substrate-keyring", path = "../../core/keyring" }
test-client = { package = "substrate-test-runtime-client", path = "../../core/test-runtime/client" }
consensus = { package = "substrate-consensus-common", path = "../../core/consensus/common", features = ["test-helpers"] }
tempdir = "0.3"
tokio = "0.1.11"
[features]
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
use crate::DiscoveryNetBehaviour;
use crate::{debug_info, discovery::DiscoveryBehaviour, discovery::DiscoveryOut, DiscoveryNetBehaviour};
use futures::prelude::*;
use libp2p::NetworkBehaviour;
use libp2p::core::{Multiaddr, PeerId, ProtocolsHandler, protocols_handler::IntoProtocolsHandler, PublicKey};
@@ -22,18 +22,12 @@ use libp2p::core::swarm::{ConnectedPoint, NetworkBehaviour, NetworkBehaviourActi
use libp2p::core::swarm::{NetworkBehaviourEventProcess, PollParameters};
#[cfg(not(target_os = "unknown"))]
use libp2p::core::swarm::toggle::Toggle;
use libp2p::kad::{Kademlia, KademliaOut};
#[cfg(not(target_os = "unknown"))]
use libp2p::mdns::{Mdns, MdnsEvent};
use libp2p::multiaddr::Protocol;
use log::{debug, info, trace, warn};
use std::{cmp, iter, time::Duration};
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_timer::{Delay, clock::Clock};
use log::warn;
use std::iter;
use void;
mod debug_info;
/// General behaviour of the network.
#[derive(NetworkBehaviour)]
#[behaviour(out_event = "TBehaviourEv", poll_method = "poll")]
@@ -65,28 +59,15 @@ impl<TBehaviour, TBehaviourEv, TSubstream> Behaviour<TBehaviour, TBehaviourEv, T
) -> Self {
let debug_info = debug_info::DebugInfoBehaviour::new(user_agent, local_public_key.clone());
let mut kademlia = Kademlia::new(local_public_key.clone().into_peer_id());
for (peer_id, addr) in &known_addresses {
kademlia.add_address(peer_id, addr.clone());
}
if enable_mdns {
#[cfg(target_os = "unknown")]
warn!(target: "sub-libp2p", "mDNS is not available on this platform");
}
let clock = Clock::new();
Behaviour {
user_protocol: UserBehaviourWrap(user_protocol),
debug_info,
discovery: DiscoveryBehaviour {
user_defined: known_addresses,
kademlia,
next_kad_random_query: Delay::new(clock.now()),
duration_to_next_kad: Duration::from_secs(1),
clock,
local_peer_id: local_public_key.into_peer_id(),
},
discovery: DiscoveryBehaviour::new(local_public_key, known_addresses),
#[cfg(not(target_os = "unknown"))]
mdns: if enable_mdns {
match Mdns::new() {
@@ -105,14 +86,12 @@ impl<TBehaviour, TBehaviourEv, TSubstream> Behaviour<TBehaviour, TBehaviourEv, T
/// Returns the list of nodes that we know exist in the network.
pub fn known_peers(&mut self) -> impl Iterator<Item = &PeerId> {
self.discovery.kademlia.kbuckets_entries()
self.discovery.known_peers()
}
/// Adds a hard-coded address for the given peer, that never expires.
pub fn add_known_address(&mut self, peer_id: PeerId, addr: Multiaddr) {
if self.discovery.user_defined.iter().all(|(p, a)| *p != peer_id && *a != addr) {
self.discovery.user_defined.push((peer_id, addr));
}
self.discovery.add_known_address(peer_id, addr)
}
/// Borrows `self` and returns a struct giving access to the information about a node.
@@ -165,33 +144,20 @@ impl<TBehaviour, TBehaviourEv, TSubstream> NetworkBehaviourEventProcess<debug_in
info.listen_addrs.truncate(30);
}
for addr in &info.listen_addrs {
self.discovery.kademlia.add_address(&peer_id, addr.clone());
self.discovery.add_self_reported_address(&peer_id, addr.clone());
}
self.user_protocol.0.add_discovered_nodes(iter::once(peer_id.clone()));
}
}
impl<TBehaviour, TBehaviourEv, TSubstream> NetworkBehaviourEventProcess<KademliaOut>
impl<TBehaviour, TBehaviourEv, TSubstream> NetworkBehaviourEventProcess<DiscoveryOut>
for Behaviour<TBehaviour, TBehaviourEv, TSubstream>
where TBehaviour: DiscoveryNetBehaviour {
fn inject_event(&mut self, out: KademliaOut) {
fn inject_event(&mut self, out: DiscoveryOut) {
match out {
KademliaOut::Discovered { .. } => {}
KademliaOut::KBucketAdded { peer_id, .. } => {
DiscoveryOut::Discovered(peer_id) => {
self.user_protocol.0.add_discovered_nodes(iter::once(peer_id));
}
KademliaOut::FindNodeResult { key, closer_peers } => {
trace!(target: "sub-libp2p", "Libp2p => Query for {:?} yielded {:?} results",
key, closer_peers.len());
if closer_peers.is_empty() {
warn!(target: "sub-libp2p", "Libp2p => Random Kademlia query has yielded empty \
results");
}
}
// We never start any other type of query.
KademliaOut::GetProvidersResult { .. } => {}
KademliaOut::GetValueResult(_) => {}
KademliaOut::PutValueResult(_) => {}
}
}
}
@@ -287,120 +253,3 @@ impl<TInner: NetworkBehaviour> NetworkBehaviour for UserBehaviourWrap<TInner> {
self.0.inject_new_external_addr(addr)
}
}
/// Implementation of `NetworkBehaviour` that discovers the nodes on the network.
pub struct DiscoveryBehaviour<TSubstream> {
/// User-defined list of nodes and their addresses. Typically includes bootstrap nodes and
/// reserved nodes.
user_defined: Vec<(PeerId, Multiaddr)>,
/// Kademlia requests and answers.
kademlia: Kademlia<TSubstream>,
/// Stream that fires when we need to perform the next random Kademlia query.
next_kad_random_query: Delay,
/// After `next_kad_random_query` triggers, the next one triggers after this duration.
duration_to_next_kad: Duration,
/// `Clock` instance that uses the current execution context's source of time.
clock: Clock,
/// Identity of our local node.
local_peer_id: PeerId,
}
impl<TSubstream> NetworkBehaviour for DiscoveryBehaviour<TSubstream>
where
TSubstream: AsyncRead + AsyncWrite,
{
type ProtocolsHandler = <Kademlia<TSubstream> as NetworkBehaviour>::ProtocolsHandler;
type OutEvent = <Kademlia<TSubstream> as NetworkBehaviour>::OutEvent;
fn new_handler(&mut self) -> Self::ProtocolsHandler {
NetworkBehaviour::new_handler(&mut self.kademlia)
}
fn addresses_of_peer(&mut self, peer_id: &PeerId) -> Vec<Multiaddr> {
let mut list = self.user_defined.iter()
.filter_map(|(p, a)| if p == peer_id { Some(a.clone()) } else { None })
.collect::<Vec<_>>();
list.extend(self.kademlia.addresses_of_peer(peer_id));
trace!(target: "sub-libp2p", "Addresses of {:?} are {:?}", peer_id, list);
if list.is_empty() {
if self.kademlia.kbuckets_entries().any(|p| p == peer_id) {
debug!(target: "sub-libp2p", "Requested dialing to {:?} (peer in k-buckets), \
and no address was found", peer_id);
} else {
debug!(target: "sub-libp2p", "Requested dialing to {:?} (peer not in k-buckets), \
and no address was found", peer_id);
}
}
list
}
fn inject_connected(&mut self, peer_id: PeerId, endpoint: ConnectedPoint) {
NetworkBehaviour::inject_connected(&mut self.kademlia, peer_id, endpoint)
}
fn inject_disconnected(&mut self, peer_id: &PeerId, endpoint: ConnectedPoint) {
NetworkBehaviour::inject_disconnected(&mut self.kademlia, peer_id, endpoint)
}
fn inject_replaced(&mut self, peer_id: PeerId, closed: ConnectedPoint, opened: ConnectedPoint) {
NetworkBehaviour::inject_replaced(&mut self.kademlia, peer_id, closed, opened)
}
fn inject_node_event(
&mut self,
peer_id: PeerId,
event: <Self::ProtocolsHandler as ProtocolsHandler>::OutEvent,
) {
NetworkBehaviour::inject_node_event(&mut self.kademlia, peer_id, event)
}
fn inject_new_external_addr(&mut self, addr: &Multiaddr) {
let new_addr = addr.clone()
.with(Protocol::P2p(self.local_peer_id.clone().into()));
info!(target: "sub-libp2p", "Discovered new external address for our node: {}", new_addr);
}
fn inject_expired_listen_addr(&mut self, addr: &Multiaddr) {
info!(target: "sub-libp2p", "No longer listening on {}", addr);
}
fn poll(
&mut self,
params: &mut PollParameters,
) -> Async<
NetworkBehaviourAction<
<Self::ProtocolsHandler as ProtocolsHandler>::InEvent,
Self::OutEvent,
>,
> {
// Poll Kademlia.
match self.kademlia.poll(params) {
Async::Ready(action) => return Async::Ready(action),
Async::NotReady => (),
}
// Poll the stream that fires when we need to start a random Kademlia query.
loop {
match self.next_kad_random_query.poll() {
Ok(Async::NotReady) => break,
Ok(Async::Ready(_)) => {
let random_peer_id = PeerId::random();
debug!(target: "sub-libp2p", "Libp2p <= Starting random Kademlia request for \
{:?}", random_peer_id);
self.kademlia.find_node(random_peer_id);
// Reset the `Delay` to the next random.
self.next_kad_random_query.reset(self.clock.now() + self.duration_to_next_kad);
self.duration_to_next_kad = cmp::min(self.duration_to_next_kad * 2,
Duration::from_secs(60));
},
Err(err) => {
warn!(target: "sub-libp2p", "Kademlia query timer errored: {:?}", err);
break
}
}
}
Async::NotReady
}
}
+289 -4
View File
@@ -17,16 +17,22 @@
//! Configuration for the networking layer of Substrate.
pub use crate::protocol::ProtocolConfig;
pub use network_libp2p::{NonReservedPeerMode, NetworkConfiguration, NodeKeyConfig, ProtocolId, Secret};
use crate::ProtocolId;
use crate::chain::{Client, FinalityProofProvider};
use crate::on_demand_layer::OnDemand;
use crate::service::{ExHashT, TransactionPool};
use bitflags::bitflags;
use consensus::import_queue::ImportQueue;
use crate::chain::{Client, FinalityProofProvider};
use parity_codec;
use crate::on_demand_layer::OnDemand;
use runtime_primitives::traits::{Block as BlockT};
use crate::service::{ExHashT, TransactionPool};
use std::sync::Arc;
use libp2p::identity::{Keypair, secp256k1, ed25519};
use libp2p::wasm_ext;
use libp2p::{Multiaddr, multiaddr::Protocol};
use std::error::Error;
use std::{io::{self, Write}, iter, fs, net::Ipv4Addr, path::{Path, PathBuf}};
use zeroize::Zeroize;
/// Service initialization parameters.
pub struct Params<B: BlockT, S, H: ExHashT> {
@@ -87,3 +93,282 @@ impl parity_codec::Decode for Roles {
Self::from_bits(input.read_byte()?)
}
}
/// Network service configuration.
#[derive(Clone)]
pub struct NetworkConfiguration {
/// Directory path to store general network configuration. None means nothing will be saved.
pub config_path: Option<String>,
/// Directory path to store network-specific configuration. None means nothing will be saved.
pub net_config_path: Option<String>,
/// Multiaddresses to listen for incoming connections.
pub listen_addresses: Vec<Multiaddr>,
/// Multiaddresses to advertise. Detected automatically if empty.
pub public_addresses: Vec<Multiaddr>,
/// List of initial node addresses
pub boot_nodes: Vec<String>,
/// The node key configuration, which determines the node's network identity keypair.
pub node_key: NodeKeyConfig,
/// Maximum allowed number of incoming connections.
pub in_peers: u32,
/// Number of outgoing connections we're trying to maintain.
pub out_peers: u32,
/// List of reserved node addresses.
pub reserved_nodes: Vec<String>,
/// The non-reserved peer mode.
pub non_reserved_mode: NonReservedPeerMode,
/// Client identifier. Sent over the wire for debugging purposes.
pub client_version: String,
/// Name of the node. Sent over the wire for debugging purposes.
pub node_name: String,
/// If true, the network will use mDNS to discover other libp2p nodes on the local network
/// and connect to them if they support the same chain.
pub enable_mdns: bool,
/// Optional external implementation of a libp2p transport. Used in WASM contexts where we need
/// some binding between the networking provided by the operating system or environment and
/// libp2p.
///
/// This parameter exists whatever the target platform is, but it is expected to be set to
/// `Some` only when compiling for WASM.
pub wasm_external_transport: Option<wasm_ext::ExtTransport>,
}
impl Default for NetworkConfiguration {
fn default() -> Self {
NetworkConfiguration {
config_path: None,
net_config_path: None,
listen_addresses: Vec::new(),
public_addresses: Vec::new(),
boot_nodes: Vec::new(),
node_key: NodeKeyConfig::Ed25519(Secret::New),
in_peers: 25,
out_peers: 75,
reserved_nodes: Vec::new(),
non_reserved_mode: NonReservedPeerMode::Accept,
client_version: "unknown".into(),
node_name: "unknown".into(),
enable_mdns: false,
wasm_external_transport: None,
}
}
}
impl NetworkConfiguration {
/// Create a new instance of default settings.
pub fn new() -> Self {
Self::default()
}
/// Create new default configuration for localhost-only connection with random port (useful for testing)
pub fn new_local() -> NetworkConfiguration {
let mut config = NetworkConfiguration::new();
config.listen_addresses = vec![
iter::once(Protocol::Ip4(Ipv4Addr::new(127, 0, 0, 1)))
.chain(iter::once(Protocol::Tcp(0)))
.collect()
];
config
}
}
/// The policy for connections to non-reserved peers.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum NonReservedPeerMode {
/// Accept them. This is the default.
Accept,
/// Deny them.
Deny,
}
impl NonReservedPeerMode {
/// Attempt to parse the peer mode from a string.
pub fn parse(s: &str) -> Option<Self> {
match s {
"accept" => Some(NonReservedPeerMode::Accept),
"deny" => Some(NonReservedPeerMode::Deny),
_ => None,
}
}
}
/// The configuration of a node's secret key, describing the type of key
/// and how it is obtained. A node's identity keypair is the result of
/// the evaluation of the node key configuration.
#[derive(Clone)]
pub enum NodeKeyConfig {
/// A Secp256k1 secret key configuration.
Secp256k1(Secret<secp256k1::SecretKey>),
/// A Ed25519 secret key configuration.
Ed25519(Secret<ed25519::SecretKey>)
}
/// The options for obtaining a Secp256k1 secret key.
pub type Secp256k1Secret = Secret<secp256k1::SecretKey>;
/// The options for obtaining a Ed25519 secret key.
pub type Ed25519Secret = Secret<ed25519::SecretKey>;
/// The configuration options for obtaining a secret key `K`.
#[derive(Clone)]
pub enum Secret<K> {
/// Use the given secret key `K`.
Input(K),
/// Read the secret key from a file. If the file does not exist,
/// it is created with a newly generated secret key `K`. The format
/// of the file is determined by `K`:
///
/// * `secp256k1::SecretKey`: An unencoded 32 bytes Secp256k1 secret key.
/// * `ed25519::SecretKey`: An unencoded 32 bytes Ed25519 secret key.
File(PathBuf),
/// Always generate a new secret key `K`.
New
}
impl NodeKeyConfig {
/// Evaluate a `NodeKeyConfig` to obtain an identity `Keypair`:
///
/// * If the secret is configured as input, the corresponding keypair is returned.
///
/// * If the secret is configured as a file, it is read from that file, if it exists.
/// Otherwise a new secret is generated and stored. In either case, the
/// keypair obtained from the secret is returned.
///
/// * If the secret is configured to be new, it is generated and the corresponding
/// keypair is returned.
pub fn into_keypair(self) -> io::Result<Keypair> {
use NodeKeyConfig::*;
match self {
Secp256k1(Secret::New) =>
Ok(Keypair::generate_secp256k1()),
Secp256k1(Secret::Input(k)) =>
Ok(Keypair::Secp256k1(k.into())),
Secp256k1(Secret::File(f)) =>
get_secret(f,
|mut b| secp256k1::SecretKey::from_bytes(&mut b),
secp256k1::SecretKey::generate,
|b| b.to_bytes().to_vec())
.map(secp256k1::Keypair::from)
.map(Keypair::Secp256k1),
Ed25519(Secret::New) =>
Ok(Keypair::generate_ed25519()),
Ed25519(Secret::Input(k)) =>
Ok(Keypair::Ed25519(k.into())),
Ed25519(Secret::File(f)) =>
get_secret(f,
|mut b| ed25519::SecretKey::from_bytes(&mut b),
ed25519::SecretKey::generate,
|b| b.as_ref().to_vec())
.map(ed25519::Keypair::from)
.map(Keypair::Ed25519),
}
}
}
/// Load a secret key from a file, if it exists, or generate a
/// new secret key and write it to that file. In either case,
/// the secret key is returned.
fn get_secret<P, F, G, E, W, K>(file: P, parse: F, generate: G, serialize: W) -> io::Result<K>
where
P: AsRef<Path>,
F: for<'r> FnOnce(&'r mut [u8]) -> Result<K, E>,
G: FnOnce() -> K,
E: Error + Send + Sync + 'static,
W: Fn(&K) -> Vec<u8>,
{
std::fs::read(&file)
.and_then(|mut sk_bytes|
parse(&mut sk_bytes)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)))
.or_else(|e| {
if e.kind() == io::ErrorKind::NotFound {
file.as_ref().parent().map_or(Ok(()), fs::create_dir_all)?;
let sk = generate();
let mut sk_vec = serialize(&sk);
write_secret_file(file, &sk_vec)?;
sk_vec.zeroize();
Ok(sk)
} else {
Err(e)
}
})
}
/// Write secret bytes to a file.
fn write_secret_file<P>(path: P, sk_bytes: &[u8]) -> io::Result<()>
where
P: AsRef<Path>
{
let mut file = open_secret_file(&path)?;
file.write_all(sk_bytes)
}
/// Opens a file containing a secret key in write mode.
#[cfg(unix)]
fn open_secret_file<P>(path: P) -> io::Result<fs::File>
where
P: AsRef<Path>
{
use std::os::unix::fs::OpenOptionsExt;
fs::OpenOptions::new()
.write(true)
.create_new(true)
.mode(0o600)
.open(path)
}
/// Opens a file containing a secret key in write mode.
#[cfg(not(unix))]
fn open_secret_file<P>(path: P) -> Result<fs::File, io::Error>
where
P: AsRef<Path>
{
fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(path)
}
#[cfg(test)]
mod tests {
use super::*;
use tempdir::TempDir;
fn secret_bytes(kp: &Keypair) -> Vec<u8> {
match kp {
Keypair::Ed25519(p) => p.secret().as_ref().iter().cloned().collect(),
Keypair::Secp256k1(p) => p.secret().to_bytes().to_vec(),
_ => panic!("Unexpected keypair.")
}
}
#[test]
fn test_secret_file() {
let tmp = TempDir::new("x").unwrap();
std::fs::remove_dir(tmp.path()).unwrap(); // should be recreated
let file = tmp.path().join("x").to_path_buf();
let kp1 = NodeKeyConfig::Ed25519(Secret::File(file.clone())).into_keypair().unwrap();
let kp2 = NodeKeyConfig::Ed25519(Secret::File(file.clone())).into_keypair().unwrap();
assert!(file.is_file() && secret_bytes(&kp1) == secret_bytes(&kp2))
}
#[test]
fn test_secret_input() {
let sk = secp256k1::SecretKey::generate();
let kp1 = NodeKeyConfig::Secp256k1(Secret::Input(sk.clone())).into_keypair().unwrap();
let kp2 = NodeKeyConfig::Secp256k1(Secret::Input(sk)).into_keypair().unwrap();
assert!(secret_bytes(&kp1) == secret_bytes(&kp2));
}
#[test]
fn test_secret_new() {
let kp1 = NodeKeyConfig::Ed25519(Secret::New).into_keypair().unwrap();
let kp2 = NodeKeyConfig::Ed25519(Secret::New).into_keypair().unwrap();
assert!(secret_bytes(&kp1) != secret_bytes(&kp2));
}
}
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
use crate::DiscoveryNetBehaviour;
use crate::{DiscoveryNetBehaviour, ProtocolId};
use crate::custom_proto::handler::{CustomProtoHandlerProto, CustomProtoHandlerOut, CustomProtoHandlerIn};
use crate::custom_proto::upgrade::{CustomMessage, RegisteredProtocol};
use fnv::FnvHashMap;
@@ -23,7 +23,8 @@ use libp2p::core::swarm::{ConnectedPoint, NetworkBehaviour, NetworkBehaviourActi
use libp2p::core::{Multiaddr, PeerId};
use log::{debug, error, trace, warn};
use smallvec::SmallVec;
use std::{borrow::Cow, collections::hash_map::Entry, cmp, error, marker::PhantomData, mem, time::Duration, time::Instant};
use std::{borrow::Cow, collections::hash_map::Entry, cmp, error, marker::PhantomData, mem};
use std::time::{Duration, Instant};
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_timer::clock::Clock;
@@ -62,7 +63,7 @@ pub struct CustomProto<TMessage, TSubstream> {
protocol: RegisteredProtocol<TMessage>,
/// Receiver for instructions about who to connect to or disconnect from.
peerset: substrate_peerset::Peerset,
peerset: peerset::Peerset,
/// List of peers in our state.
peers: FnvHashMap<PeerId, PeerState>,
@@ -73,7 +74,7 @@ pub struct CustomProto<TMessage, TSubstream> {
/// We generate indices to identify incoming connections. This is the next value for the index
/// to use when a connection is incoming.
next_incoming_index: substrate_peerset::IncomingIndex,
next_incoming_index: peerset::IncomingIndex,
/// Events to produce from `poll()`.
events: SmallVec<[NetworkBehaviourAction<CustomProtoHandlerIn<TMessage>, CustomProtoOut<TMessage>>; 4]>,
@@ -179,7 +180,7 @@ struct IncomingPeer {
/// connection corresponding to it has been closed or replaced already.
alive: bool,
/// Id that the we sent to the peerset.
incoming_id: substrate_peerset::IncomingIndex,
incoming_id: peerset::IncomingIndex,
}
/// Event that can be emitted by the `CustomProto`.
@@ -224,15 +225,18 @@ pub enum CustomProtoOut<TMessage> {
impl<TMessage, TSubstream> CustomProto<TMessage, TSubstream> {
/// Creates a `CustomProtos`.
pub fn new(
protocol: RegisteredProtocol<TMessage>,
peerset: substrate_peerset::Peerset,
protocol: impl Into<ProtocolId>,
versions: &[u8],
peerset: peerset::Peerset,
) -> Self {
let protocol = RegisteredProtocol::new(protocol, versions);
CustomProto {
protocol,
peerset,
peers: FnvHashMap::default(),
incoming: SmallVec::new(),
next_incoming_index: substrate_peerset::IncomingIndex(0),
next_incoming_index: peerset::IncomingIndex(0),
events: SmallVec::new(),
marker: PhantomData,
clock: Clock::new(),
@@ -514,7 +518,7 @@ impl<TMessage, TSubstream> CustomProto<TMessage, TSubstream> {
}
/// Function that is called when the peerset wants us to accept an incoming node.
fn peerset_report_accept(&mut self, index: substrate_peerset::IncomingIndex) {
fn peerset_report_accept(&mut self, index: peerset::IncomingIndex) {
let incoming = if let Some(pos) = self.incoming.iter().position(|i| i.incoming_id == index) {
self.incoming.remove(pos)
} else {
@@ -558,7 +562,7 @@ impl<TMessage, TSubstream> CustomProto<TMessage, TSubstream> {
}
/// Function that is called when the peerset wants us to reject an incoming node.
fn peerset_report_reject(&mut self, index: substrate_peerset::IncomingIndex) {
fn peerset_report_reject(&mut self, index: peerset::IncomingIndex) {
let incoming = if let Some(pos) = self.incoming.iter().position(|i| i.incoming_id == index) {
self.incoming.remove(pos)
} else {
@@ -939,16 +943,16 @@ where
// Note that the peerset is a *best effort* crate, and we have to use defensive programming.
loop {
match self.peerset.poll() {
Ok(Async::Ready(Some(substrate_peerset::Message::Accept(index)))) => {
Ok(Async::Ready(Some(peerset::Message::Accept(index)))) => {
self.peerset_report_accept(index);
}
Ok(Async::Ready(Some(substrate_peerset::Message::Reject(index)))) => {
Ok(Async::Ready(Some(peerset::Message::Reject(index)))) => {
self.peerset_report_reject(index);
}
Ok(Async::Ready(Some(substrate_peerset::Message::Connect(id)))) => {
Ok(Async::Ready(Some(peerset::Message::Connect(id)))) => {
self.peerset_report_connect(id);
}
Ok(Async::Ready(Some(substrate_peerset::Message::Drop(id)))) => {
Ok(Async::Ready(Some(peerset::Message::Drop(id)))) => {
self.peerset_report_disconnect(id);
}
Ok(Async::Ready(None)) => {
@@ -15,7 +15,7 @@
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
pub use self::behaviour::{CustomProto, CustomProtoOut};
pub use self::upgrade::{CustomMessage, RegisteredProtocol};
pub use self::upgrade::CustomMessage;
mod behaviour;
mod handler;
@@ -62,11 +62,6 @@ impl<TMessage> RegisteredProtocol<TMessage> {
marker: PhantomData,
}
}
/// Returns the ID of the protocol.
pub fn id(&self) -> &ProtocolId {
&self.id
}
}
impl<TMessage> Clone for RegisteredProtocol<TMessage> {
@@ -93,8 +88,6 @@ pub struct RegisteredProtocolSubstream<TMessage, TSubstream> {
requires_poll_complete: bool,
/// The underlying substream.
inner: stream::Fuse<Framed<Negotiated<TSubstream>, UviBytes<Vec<u8>>>>,
/// Id of the protocol.
protocol_id: ProtocolId,
/// Version of the protocol that was negotiated.
protocol_version: u8,
/// If true, we have sent a "remote is clogged" event recently and shouldn't send another one
@@ -105,14 +98,7 @@ pub struct RegisteredProtocolSubstream<TMessage, TSubstream> {
}
impl<TMessage, TSubstream> RegisteredProtocolSubstream<TMessage, TSubstream> {
/// Returns the protocol id.
#[inline]
pub fn protocol_id(&self) -> &ProtocolId {
&self.protocol_id
}
/// Returns the version of the protocol that was negotiated.
#[inline]
pub fn protocol_version(&self) -> u8 {
self.protocol_version
}
@@ -310,7 +296,6 @@ where TSubstream: AsyncRead + AsyncWrite,
send_queue: VecDeque::new(),
requires_poll_complete: false,
inner: framed.fuse(),
protocol_id: self.id,
protocol_version: info.version,
clogged_fuse: false,
marker: PhantomData,
@@ -338,7 +323,6 @@ where TSubstream: AsyncRead + AsyncWrite,
send_queue: VecDeque::new(),
requires_poll_complete: false,
inner: framed.fuse(),
protocol_id: self.id,
protocol_version: info.version,
clogged_fuse: false,
marker: PhantomData,
@@ -133,11 +133,6 @@ impl<'a> Node<'a> {
pub fn latest_ping(&self) -> Option<Duration> {
self.0.latest_ping
}
/// Generates an arbitrary string containing debug information about the node.
pub fn debug_info(&self) -> String {
format!("(version: {:?}) through {:?}", self.0.client_version, self.0.endpoint)
}
}
/// Event that can be emitted by the behaviour.
+302
View File
@@ -0,0 +1,302 @@
// Copyright 2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
use futures::prelude::*;
use libp2p::core::{Multiaddr, PeerId, ProtocolsHandler, PublicKey};
use libp2p::core::swarm::{ConnectedPoint, NetworkBehaviour, NetworkBehaviourAction};
use libp2p::core::swarm::PollParameters;
use libp2p::kad::{Kademlia, KademliaOut};
use libp2p::multiaddr::Protocol;
use log::{debug, info, trace, warn};
use std::{cmp, time::Duration};
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_timer::{Delay, clock::Clock};
/// Implementation of `NetworkBehaviour` that discovers the nodes on the network.
pub struct DiscoveryBehaviour<TSubstream> {
/// User-defined list of nodes and their addresses. Typically includes bootstrap nodes and
/// reserved nodes.
user_defined: Vec<(PeerId, Multiaddr)>,
/// Kademlia requests and answers.
kademlia: Kademlia<TSubstream>,
/// Stream that fires when we need to perform the next random Kademlia query.
next_kad_random_query: Delay,
/// After `next_kad_random_query` triggers, the next one triggers after this duration.
duration_to_next_kad: Duration,
/// `Clock` instance that uses the current execution context's source of time.
clock: Clock,
/// Identity of our local node.
local_peer_id: PeerId,
}
impl<TSubstream> DiscoveryBehaviour<TSubstream> {
/// Builds a new `DiscoveryBehaviour`.
///
/// `user_defined` is a list of known address for nodes that never expire.
pub fn new(local_public_key: PublicKey, user_defined: Vec<(PeerId, Multiaddr)>) -> Self {
let mut kademlia = Kademlia::new(local_public_key.clone().into_peer_id());
for (peer_id, addr) in &user_defined {
kademlia.add_address(peer_id, addr.clone());
}
let clock = Clock::new();
DiscoveryBehaviour {
user_defined,
kademlia,
next_kad_random_query: Delay::new(clock.now()),
duration_to_next_kad: Duration::from_secs(1),
clock,
local_peer_id: local_public_key.into_peer_id(),
}
}
/// Returns the list of nodes that we know exist in the network.
pub fn known_peers(&mut self) -> impl Iterator<Item = &PeerId> {
self.kademlia.kbuckets_entries()
}
/// Adds a hard-coded address for the given peer, that never expires.
///
/// This adds an entry to the parameter that was passed to `new`.
pub fn add_known_address(&mut self, peer_id: PeerId, addr: Multiaddr) {
if self.user_defined.iter().all(|(p, a)| *p != peer_id && *a != addr) {
self.user_defined.push((peer_id, addr));
}
}
/// Call this method when a node reports an address for itself.
pub fn add_self_reported_address(&mut self, peer_id: &PeerId, addr: Multiaddr) {
self.kademlia.add_address(peer_id, addr);
}
}
/// Event generated by the `DiscoveryBehaviour`.
pub enum DiscoveryOut {
/// We have discovered a node. Can be called multiple times with the same identity.
Discovered(PeerId),
}
impl<TSubstream> NetworkBehaviour for DiscoveryBehaviour<TSubstream>
where
TSubstream: AsyncRead + AsyncWrite,
{
type ProtocolsHandler = <Kademlia<TSubstream> as NetworkBehaviour>::ProtocolsHandler;
type OutEvent = DiscoveryOut;
fn new_handler(&mut self) -> Self::ProtocolsHandler {
NetworkBehaviour::new_handler(&mut self.kademlia)
}
fn addresses_of_peer(&mut self, peer_id: &PeerId) -> Vec<Multiaddr> {
let mut list = self.user_defined.iter()
.filter_map(|(p, a)| if p == peer_id { Some(a.clone()) } else { None })
.collect::<Vec<_>>();
list.extend(self.kademlia.addresses_of_peer(peer_id));
trace!(target: "sub-libp2p", "Addresses of {:?} are {:?}", peer_id, list);
if list.is_empty() {
if self.kademlia.kbuckets_entries().any(|p| p == peer_id) {
debug!(target: "sub-libp2p", "Requested dialing to {:?} (peer in k-buckets), \
and no address was found", peer_id);
} else {
debug!(target: "sub-libp2p", "Requested dialing to {:?} (peer not in k-buckets), \
and no address was found", peer_id);
}
}
list
}
fn inject_connected(&mut self, peer_id: PeerId, endpoint: ConnectedPoint) {
NetworkBehaviour::inject_connected(&mut self.kademlia, peer_id, endpoint)
}
fn inject_disconnected(&mut self, peer_id: &PeerId, endpoint: ConnectedPoint) {
NetworkBehaviour::inject_disconnected(&mut self.kademlia, peer_id, endpoint)
}
fn inject_replaced(&mut self, peer_id: PeerId, closed: ConnectedPoint, opened: ConnectedPoint) {
NetworkBehaviour::inject_replaced(&mut self.kademlia, peer_id, closed, opened)
}
fn inject_node_event(
&mut self,
peer_id: PeerId,
event: <Self::ProtocolsHandler as ProtocolsHandler>::OutEvent,
) {
NetworkBehaviour::inject_node_event(&mut self.kademlia, peer_id, event)
}
fn inject_new_external_addr(&mut self, addr: &Multiaddr) {
let new_addr = addr.clone()
.with(Protocol::P2p(self.local_peer_id.clone().into()));
info!(target: "sub-libp2p", "Discovered new external address for our node: {}", new_addr);
}
fn inject_expired_listen_addr(&mut self, addr: &Multiaddr) {
info!(target: "sub-libp2p", "No longer listening on {}", addr);
}
fn poll(
&mut self,
params: &mut PollParameters,
) -> Async<
NetworkBehaviourAction<
<Self::ProtocolsHandler as ProtocolsHandler>::InEvent,
Self::OutEvent,
>,
> {
// Poll Kademlia.
match self.kademlia.poll(params) {
Async::NotReady => (),
Async::Ready(NetworkBehaviourAction::GenerateEvent(ev)) => {
match ev {
KademliaOut::Discovered { .. } => {}
KademliaOut::KBucketAdded { peer_id, .. } => {
let ev = DiscoveryOut::Discovered(peer_id);
return Async::Ready(NetworkBehaviourAction::GenerateEvent(ev));
}
KademliaOut::FindNodeResult { key, closer_peers } => {
trace!(target: "sub-libp2p", "Libp2p => Query for {:?} yielded {:?} results",
key, closer_peers.len());
if closer_peers.is_empty() {
warn!(target: "sub-libp2p", "Libp2p => Random Kademlia query has yielded empty \
results");
}
}
// We never start any other type of query.
KademliaOut::GetProvidersResult { .. } => {}
KademliaOut::GetValueResult(_) => {}
KademliaOut::PutValueResult(_) => {}
}
},
Async::Ready(NetworkBehaviourAction::DialAddress { address }) =>
return Async::Ready(NetworkBehaviourAction::DialAddress { address }),
Async::Ready(NetworkBehaviourAction::DialPeer { peer_id }) =>
return Async::Ready(NetworkBehaviourAction::DialPeer { peer_id }),
Async::Ready(NetworkBehaviourAction::SendEvent { peer_id, event }) =>
return Async::Ready(NetworkBehaviourAction::SendEvent { peer_id, event }),
Async::Ready(NetworkBehaviourAction::ReportObservedAddr { address }) =>
return Async::Ready(NetworkBehaviourAction::ReportObservedAddr { address }),
}
// Poll the stream that fires when we need to start a random Kademlia query.
loop {
match self.next_kad_random_query.poll() {
Ok(Async::NotReady) => break,
Ok(Async::Ready(_)) => {
let random_peer_id = PeerId::random();
debug!(target: "sub-libp2p", "Libp2p <= Starting random Kademlia request for \
{:?}", random_peer_id);
self.kademlia.find_node(random_peer_id);
// Reset the `Delay` to the next random.
self.next_kad_random_query.reset(self.clock.now() + self.duration_to_next_kad);
self.duration_to_next_kad = cmp::min(self.duration_to_next_kad * 2,
Duration::from_secs(60));
},
Err(err) => {
warn!(target: "sub-libp2p", "Kademlia query timer errored: {:?}", err);
break
}
}
}
Async::NotReady
}
}
#[cfg(test)]
mod tests {
use futures::prelude::*;
use libp2p::identity::Keypair;
use libp2p::Multiaddr;
use libp2p::core::{upgrade, Swarm};
use libp2p::core::transport::{Transport, MemoryTransport};
use libp2p::core::upgrade::{InboundUpgradeExt, OutboundUpgradeExt};
use std::collections::HashSet;
use super::{DiscoveryBehaviour, DiscoveryOut};
#[test]
fn discovery_working() {
let mut user_defined = Vec::new();
// Build swarms whose behaviour is `DiscoveryBehaviour`.
let mut swarms = (0..25).map(|_| {
let keypair = Keypair::generate_ed25519();
let transport = MemoryTransport
.with_upgrade(libp2p::secio::SecioConfig::new(keypair.clone()))
.and_then(move |out, endpoint| {
let peer_id = out.remote_key.into_peer_id();
let peer_id2 = peer_id.clone();
let upgrade = libp2p::yamux::Config::default()
.map_inbound(move |muxer| (peer_id, muxer))
.map_outbound(move |muxer| (peer_id2, muxer));
upgrade::apply(out.stream, upgrade, endpoint)
});
let behaviour = DiscoveryBehaviour::new(keypair.public(), user_defined.clone());
let mut swarm = Swarm::new(transport, behaviour, keypair.public().into_peer_id());
let listen_addr: Multiaddr = format!("/memory/{}", rand::random::<u64>()).parse().unwrap();
if user_defined.is_empty() {
user_defined.push((keypair.public().into_peer_id(), listen_addr.clone()));
}
Swarm::listen_on(&mut swarm, listen_addr.clone()).unwrap();
(swarm, listen_addr)
}).collect::<Vec<_>>();
// Build a `Vec<HashSet<PeerId>>` with the list of nodes remaining to be discovered.
let mut to_discover = (0..swarms.len()).map(|n| {
(0..swarms.len()).filter(|p| *p != n)
.map(|p| Swarm::local_peer_id(&swarms[p].0).clone())
.collect::<HashSet<_>>()
}).collect::<Vec<_>>();
let fut = futures::future::poll_fn(move || -> Result<_, ()> {
loop {
let mut keep_polling = false;
for swarm_n in 0..swarms.len() {
if let Async::Ready(Some(DiscoveryOut::Discovered(other))) =
swarms[swarm_n].0.poll().unwrap() {
if to_discover[swarm_n].remove(&other) {
keep_polling = true;
// Call `add_self_reported_address` to simulate identify happening.
let addr = swarms.iter()
.find(|s| *Swarm::local_peer_id(&s.0) == other)
.unwrap()
.1.clone();
swarms[swarm_n].0.add_self_reported_address(&other, addr);
}
}
}
if !keep_polling {
break;
}
}
if to_discover.iter().all(|l| l.is_empty()) {
Ok(Async::Ready(()))
} else {
Ok(Async::NotReady)
}
});
tokio::runtime::Runtime::new().unwrap().block_on(fut).unwrap();
}
}
+183 -10
View File
@@ -23,11 +23,17 @@
//! **Important**: This crate is unstable and the API and usage may change.
//!
mod service;
mod behaviour;
mod chain;
mod custom_proto;
mod debug_info;
mod discovery;
mod on_demand_layer;
#[macro_use]
mod protocol;
mod chain;
mod on_demand_layer;
mod service;
mod transport;
pub mod config;
pub mod error;
@@ -39,18 +45,185 @@ pub use service::{
NetworkService, NetworkWorker, FetchFuture, TransactionPool, ManageNetwork,
NetworkMsg, SyncProvider, ExHashT, ReportHandle,
};
pub use config::{NodeKeyConfig, Secret, Secp256k1Secret, Ed25519Secret};
pub use protocol::{ProtocolStatus, PeerInfo, Context, consensus_gossip, message, specialization};
pub use protocol::sync::{Status as SyncStatus, SyncState};
pub use network_libp2p::{
identity, multiaddr,
ProtocolId, Multiaddr,
NetworkState, NetworkStatePeer, NetworkStateNotConnectedPeer, NetworkStatePeerEndpoint,
NodeKeyConfig, Secret, Secp256k1Secret, Ed25519Secret,
build_multiaddr, PeerId, PublicKey
};
pub use libp2p::{Multiaddr, multiaddr, build_multiaddr};
pub use libp2p::{identity, PeerId, core::PublicKey};
pub use message::{generic as generic_message, RequestId, Status as StatusMessage};
pub use error::Error;
pub use protocol::on_demand::AlwaysBadChecker;
pub use on_demand_layer::{OnDemand, RemoteResponse};
#[doc(hidden)]
pub use runtime_primitives::traits::Block as BlockT;
use libp2p::core::nodes::ConnectedPoint;
use serde::{Deserialize, Serialize};
use slog_derive::SerdeValue;
use std::{collections::{HashMap, HashSet}, fmt, time::Duration};
/// Extension trait for `NetworkBehaviour` that also accepts discovering nodes.
pub trait DiscoveryNetBehaviour {
/// Notify the protocol that we have learned about the existence of nodes.
///
/// Can (or most likely will) be called multiple times with the same `PeerId`s.
///
/// Also note that there is no notification for expired nodes. The implementer must add a TTL
/// system, or remove nodes that will fail to reach.
fn add_discovered_nodes(&mut self, nodes: impl Iterator<Item = PeerId>);
}
/// Name of a protocol, transmitted on the wire. Should be unique for each chain.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ProtocolId(smallvec::SmallVec<[u8; 6]>);
impl<'a> From<&'a [u8]> for ProtocolId {
fn from(bytes: &'a [u8]) -> ProtocolId {
ProtocolId(bytes.into())
}
}
impl ProtocolId {
/// Exposes the `ProtocolId` as bytes.
pub fn as_bytes(&self) -> &[u8] {
self.0.as_ref()
}
}
/// Parses a string address and returns the component, if valid.
pub fn parse_str_addr(addr_str: &str) -> Result<(PeerId, Multiaddr), ParseErr> {
let mut addr: Multiaddr = addr_str.parse()?;
let who = match addr.pop() {
Some(multiaddr::Protocol::P2p(key)) => PeerId::from_multihash(key)
.map_err(|_| ParseErr::InvalidPeerId)?,
_ => return Err(ParseErr::PeerIdMissing),
};
Ok((who, addr))
}
/// Error that can be generated by `parse_str_addr`.
#[derive(Debug)]
pub enum ParseErr {
/// Error while parsing the multiaddress.
MultiaddrParse(multiaddr::Error),
/// Multihash of the peer ID is invalid.
InvalidPeerId,
/// The peer ID is missing from the address.
PeerIdMissing,
}
impl fmt::Display for ParseErr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseErr::MultiaddrParse(err) => write!(f, "{}", err),
ParseErr::InvalidPeerId => write!(f, "Peer id at the end of the address is invalid"),
ParseErr::PeerIdMissing => write!(f, "Peer id is missing from the address"),
}
}
}
impl std::error::Error for ParseErr {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
ParseErr::MultiaddrParse(err) => Some(err),
ParseErr::InvalidPeerId => None,
ParseErr::PeerIdMissing => None,
}
}
}
impl From<multiaddr::Error> for ParseErr {
fn from(err: multiaddr::Error) -> ParseErr {
ParseErr::MultiaddrParse(err)
}
}
/// Returns general information about the networking.
///
/// Meant for general diagnostic purposes.
///
/// **Warning**: This API is not stable.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, SerdeValue)]
#[serde(rename_all = "camelCase")]
pub struct NetworkState {
/// PeerId of the local node.
pub peer_id: String,
/// List of addresses the node is currently listening on.
pub listened_addresses: HashSet<Multiaddr>,
/// List of addresses the node knows it can be reached as.
pub external_addresses: HashSet<Multiaddr>,
/// List of node we're connected to.
pub connected_peers: HashMap<String, NetworkStatePeer>,
/// List of node that we know of but that we're not connected to.
pub not_connected_peers: HashMap<String, NetworkStateNotConnectedPeer>,
/// Downloaded bytes per second averaged over the past few seconds.
pub average_download_per_sec: u64,
/// Uploaded bytes per second averaged over the past few seconds.
pub average_upload_per_sec: u64,
/// State of the peerset manager.
pub peerset: serde_json::Value,
}
/// Part of the `NetworkState` struct. Unstable.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NetworkStatePeer {
/// How we are connected to the node.
pub endpoint: NetworkStatePeerEndpoint,
/// Node information, as provided by the node itself. Can be empty if not known yet.
pub version_string: Option<String>,
/// Latest ping duration with this node.
pub latest_ping_time: Option<Duration>,
/// If true, the peer is "enabled", which means that we try to open Substrate-related protocols
/// with this peer. If false, we stick to Kademlia and/or other network-only protocols.
pub enabled: bool,
/// If true, the peer is "open", which means that we have a Substrate-related protocol
/// with this peer.
pub open: bool,
/// List of addresses known for this node.
pub known_addresses: HashSet<Multiaddr>,
}
/// Part of the `NetworkState` struct. Unstable.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NetworkStateNotConnectedPeer {
/// List of addresses known for this node.
pub known_addresses: HashSet<Multiaddr>,
/// Node information, as provided by the node itself, if we were ever connected to this node.
pub version_string: Option<String>,
/// Latest ping duration with this node, if we were ever connected to this node.
pub latest_ping_time: Option<Duration>,
}
/// Part of the `NetworkState` struct. Unstable.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum NetworkStatePeerEndpoint {
/// We are dialing the given address.
Dialing(Multiaddr),
/// We are listening.
Listening {
/// Address we're listening on that received the connection.
listen_addr: Multiaddr,
/// Address data is sent back to.
send_back_addr: Multiaddr,
},
}
impl From<ConnectedPoint> for NetworkStatePeerEndpoint {
fn from(endpoint: ConnectedPoint) -> Self {
match endpoint {
ConnectedPoint::Dialer { address } =>
NetworkStatePeerEndpoint::Dialing(address),
ConnectedPoint::Listener { listen_addr, send_back_addr } =>
NetworkStatePeerEndpoint::Listening {
listen_addr,
send_back_addr
}
}
}
}
+5 -5
View File
@@ -15,7 +15,7 @@
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
use futures::prelude::*;
use network_libp2p::PeerId;
use libp2p::PeerId;
use primitives::storage::StorageKey;
use consensus::{import_queue::IncomingBlock, import_queue::Origin, BlockOrigin};
use runtime_primitives::{generic::BlockId, ConsensusEngineId, Justification};
@@ -618,15 +618,15 @@ impl<B: BlockT, S: NetworkSpecialization<B>, H: ExHashT> Protocol<B, S, H> {
}
/// Called when a new peer is connected
pub fn on_peer_connected(&mut self, network_out: &mut dyn NetworkOut<B>, who: PeerId, debug_info: String) {
trace!(target: "sync", "Connecting {}: {}", who, debug_info);
pub fn on_peer_connected(&mut self, network_out: &mut dyn NetworkOut<B>, who: PeerId) {
trace!(target: "sync", "Connecting {}", who);
self.handshaking_peers.insert(who.clone(), HandshakingPeer { timestamp: time::Instant::now() });
self.send_status(network_out, who);
}
/// Called by peer when it is disconnecting
pub fn on_peer_disconnected(&mut self, mut network_out: &mut dyn NetworkOut<B>, peer: PeerId, debug_info: String) {
trace!(target: "sync", "Disconnecting {}: {}", peer, debug_info);
pub fn on_peer_disconnected(&mut self, mut network_out: &mut dyn NetworkOut<B>, peer: PeerId) {
trace!(target: "sync", "Disconnecting {}", peer);
// lock all the the peer lists so that add/remove peer events are in order
let removed = {
self.handshaking_peers.remove(&peer);
@@ -24,7 +24,7 @@ use std::time;
use log::{trace, debug};
use futures::sync::mpsc;
use lru_cache::LruCache;
use network_libp2p::PeerId;
use libp2p::PeerId;
use runtime_primitives::traits::{Block as BlockT, Hash, HashFor};
use runtime_primitives::ConsensusEngineId;
pub use crate::message::generic::{Message, ConsensusMessage};
@@ -125,8 +125,8 @@ pub struct RemoteReadResponse {
/// Generic types.
pub mod generic {
use crate::custom_proto::CustomMessage;
use parity_codec::{Encode, Decode};
use network_libp2p::CustomMessage;
use runtime_primitives::Justification;
use crate::config::Roles;
use super::{
@@ -27,7 +27,7 @@ use client::light::fetcher::{FetchChecker, RemoteHeaderRequest,
RemoteCallRequest, RemoteReadRequest, RemoteChangesRequest, ChangesProof,
RemoteReadChildRequest, RemoteBodyRequest};
use crate::message::{self, BlockAttributes, Direction, FromBlock, RequestId};
use network_libp2p::PeerId;
use libp2p::PeerId;
use crate::config::Roles;
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, NumberFor};
@@ -644,7 +644,7 @@ pub mod tests {
RemoteReadChildRequest, RemoteChangesRequest, RemoteBodyRequest};
use crate::config::Roles;
use crate::message::{self, BlockAttributes, Direction, FromBlock, RequestId};
use network_libp2p::PeerId;
use libp2p::PeerId;
use super::{REQUEST_TIMEOUT, OnDemandCore, OnDemandNetwork, RequestData};
use test_client::runtime::{changes_trie_config, Block, Extrinsic, Header};
@@ -16,9 +16,9 @@
//! Specializations of the substrate network protocol to allow more complex forms of communication.
use crate::PeerId;
use runtime_primitives::traits::Block as BlockT;
use crate::protocol::Context;
use libp2p::PeerId;
use runtime_primitives::traits::Block as BlockT;
/// A specialization of the substrate network protocol. Handles events and sends messages.
pub trait NetworkSpecialization<B: BlockT>: Send + Sync + 'static {
+1 -1
View File
@@ -35,7 +35,7 @@ use std::ops::Range;
use std::collections::{HashMap, VecDeque};
use log::{debug, trace, warn, info, error};
use crate::protocol::PeerInfo as ProtocolPeerInfo;
use network_libp2p::PeerId;
use libp2p::PeerId;
use client::{BlockStatus, ClientInfo};
use consensus::{BlockOrigin, import_queue::{IncomingBlock, SharedFinalityProofRequestBuilder}};
use client::error::Error as ClientError;
@@ -20,7 +20,7 @@ use std::ops::Range;
use std::collections::{HashMap, BTreeMap};
use std::collections::hash_map::Entry;
use log::trace;
use network_libp2p::PeerId;
use libp2p::PeerId;
use runtime_primitives::traits::{Block as BlockT, NumberFor, One};
use crate::message;
@@ -20,7 +20,7 @@ use log::{trace, warn};
use client::error::Error as ClientError;
use consensus::import_queue::SharedFinalityProofRequestBuilder;
use fork_tree::ForkTree;
use network_libp2p::PeerId;
use libp2p::PeerId;
use runtime_primitives::Justification;
use runtime_primitives::traits::{Block as BlockT, NumberFor};
use crate::protocol::message;
+179 -31
View File
@@ -15,16 +15,20 @@
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
use std::collections::HashMap;
use std::io;
use std::{fs, io, path::Path};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use log::{warn, debug, error, info};
use libp2p::core::swarm::NetworkBehaviour;
use libp2p::core::{nodes::Substream, transport::boxed::Boxed, muxing::StreamMuxerBox};
use futures::{prelude::*, sync::oneshot, sync::mpsc};
use parking_lot::{Mutex, RwLock};
use network_libp2p::{start_service, parse_str_addr, Service as Libp2pNetService, ServiceEvent as Libp2pNetServiceEvent};
use network_libp2p::{RegisteredProtocol, NetworkState};
use crate::custom_proto::{CustomProto, CustomProtoOut};
use crate::{behaviour::Behaviour, parse_str_addr, ProtocolId};
use crate::{NetworkState, NetworkStateNotConnectedPeer, NetworkStatePeer};
use crate::{transport, config::NodeKeyConfig, config::NonReservedPeerMode, config::NetworkConfiguration};
use peerset::PeersetHandle;
use consensus::import_queue::{ImportQueue, Link, SharedFinalityProofRequestBuilder};
use runtime_primitives::{traits::{Block as BlockT, NumberFor}, ConsensusEngineId};
@@ -40,12 +44,14 @@ use crate::config::Params;
use crate::error::Error;
use crate::protocol::specialization::NetworkSpecialization;
mod tests;
/// Interval at which we send status updates on the SyncProvider status stream.
const STATUS_INTERVAL: Duration = Duration::from_millis(5000);
/// Interval at which we update the `peers` field on the main thread.
const CONNECTED_PEERS_INTERVAL: Duration = Duration::from_millis(500);
pub use network_libp2p::PeerId;
pub use libp2p::PeerId;
/// Type that represents fetch completion future.
pub type FetchFuture = oneshot::Receiver<Vec<u8>>;
@@ -187,7 +193,9 @@ pub struct NetworkService<B: BlockT + 'static, S: NetworkSpecialization<B>> {
/// Channel for networking messages processed by the background thread.
network_chan: mpsc::UnboundedSender<NetworkMsg<B>>,
/// Network service
network: Arc<Mutex<Libp2pNetService<Message<B>>>>,
network: Arc<Mutex<Swarm<B>>>,
/// Bandwidth logging system. Can be queried to know the average bandwidth consumed.
bandwidth: Arc<transport::BandwidthSinks>,
/// Peerset manager (PSM); manages the reputation of nodes and indicates the network which
/// nodes it should be connected to or not.
peerset: PeersetHandle,
@@ -227,19 +235,20 @@ impl<B: BlockT + 'static, S: NetworkSpecialization<B>, H: ExHashT> NetworkWorker
params.specialization,
)?;
let versions: Vec<_> = ((protocol::MIN_VERSION as u8)..=(protocol::CURRENT_VERSION as u8)).collect();
let registered = RegisteredProtocol::new(params.protocol_id, &versions);
// Start the main service.
let (network, peerset) = match start_service(params.network_config, registered) {
Ok((network, peerset)) => (Arc::new(Mutex::new(network)), peerset),
Err(err) => {
warn!("Error starting network: {}", err);
return Err(err.into())
},
};
let (network, bandwidth, peerset) =
match start_service::<B, _>(params.network_config, params.protocol_id, &versions) {
Ok((network, bandwidth, peerset)) => (Arc::new(Mutex::new(network)), bandwidth, peerset),
Err(err) => {
warn!("Error starting network: {}", err);
return Err(err.into())
},
};
let service = Arc::new(NetworkService {
status_sinks: status_sinks.clone(),
bandwidth,
is_offline: is_offline.clone(),
is_major_syncing: is_major_syncing.clone(),
network_chan,
@@ -278,20 +287,18 @@ impl<B: BlockT + 'static, S: NetworkSpecialization<B>, H: ExHashT> NetworkWorker
impl<B: BlockT + 'static, S: NetworkSpecialization<B>> NetworkService<B, S> {
/// Returns the downloaded bytes per second averaged over the past few seconds.
#[inline]
pub fn average_download_per_sec(&self) -> u64 {
self.network.lock().average_download_per_sec()
self.bandwidth.average_download_per_sec()
}
/// Returns the uploaded bytes per second averaged over the past few seconds.
#[inline]
pub fn average_upload_per_sec(&self) -> u64 {
self.network.lock().average_upload_per_sec()
self.bandwidth.average_upload_per_sec()
}
/// Returns the network identity of the node.
pub fn local_peer_id(&self) -> PeerId {
self.network.lock().peer_id().clone()
Swarm::<B>::local_peer_id(&*self.network.lock()).clone()
}
/// Called when a new block is imported by the client.
@@ -404,7 +411,58 @@ impl<B: BlockT + 'static, S: NetworkSpecialization<B>> SyncProvider<B> for Netwo
}
fn network_state(&self) -> NetworkState {
self.network.lock().state()
let mut swarm = self.network.lock();
let open = swarm.user_protocol().open_peers().cloned().collect::<Vec<_>>();
let connected_peers = {
let swarm = &mut *swarm;
open.iter().filter_map(move |peer_id| {
let known_addresses = NetworkBehaviour::addresses_of_peer(&mut **swarm, peer_id)
.into_iter().collect();
let endpoint = if let Some(e) = swarm.node(peer_id).map(|i| i.endpoint()) {
e.clone().into()
} else {
error!(target: "sub-libp2p", "Found state inconsistency between custom protocol \
and debug information about {:?}", peer_id);
return None
};
Some((peer_id.to_base58(), NetworkStatePeer {
endpoint,
version_string: swarm.node(peer_id).and_then(|i| i.client_version().map(|s| s.to_owned())).clone(),
latest_ping_time: swarm.node(peer_id).and_then(|i| i.latest_ping()),
enabled: swarm.user_protocol().is_enabled(&peer_id),
open: swarm.user_protocol().is_open(&peer_id),
known_addresses,
}))
}).collect()
};
let not_connected_peers = {
let swarm = &mut *swarm;
let list = swarm.known_peers().filter(|p| open.iter().all(|n| n != *p))
.cloned().collect::<Vec<_>>();
list.into_iter().map(move |peer_id| {
(peer_id.to_base58(), NetworkStateNotConnectedPeer {
version_string: swarm.node(&peer_id).and_then(|i| i.client_version().map(|s| s.to_owned())).clone(),
latest_ping_time: swarm.node(&peer_id).and_then(|i| i.latest_ping()),
known_addresses: NetworkBehaviour::addresses_of_peer(&mut **swarm, &peer_id)
.into_iter().collect(),
})
}).collect()
};
NetworkState {
peer_id: Swarm::<B>::local_peer_id(&swarm).to_base58(),
listened_addresses: Swarm::<B>::listeners(&swarm).cloned().collect(),
external_addresses: Swarm::<B>::external_addresses(&swarm).cloned().collect(),
average_download_per_sec: self.bandwidth.average_download_per_sec(),
average_upload_per_sec: self.bandwidth.average_upload_per_sec(),
connected_peers,
not_connected_peers,
peerset: swarm.user_protocol_mut().peerset_debug_info(),
}
}
fn peers_debug_info(&self) -> Vec<(PeerId, PeerInfo<B>)> {
@@ -533,7 +591,7 @@ pub struct NetworkWorker<B: BlockT + 'static, S: NetworkSpecialization<B>, H: Ex
protocol: Protocol<B, S, H>,
/// The network service that can be extracted and shared through the codebase.
service: Arc<NetworkService<B, S>>,
network_service: Arc<Mutex<Libp2pNetService<Message<B>>>>,
network_service: Arc<Mutex<Swarm<B>>>,
peers: Arc<RwLock<HashMap<PeerId, ConnectedPeer<B>>>>,
import_queue: Box<dyn ImportQueue<B>>,
transaction_pool: Arc<dyn TransactionPool<H, B>>,
@@ -556,16 +614,16 @@ impl<B: BlockT + 'static, S: NetworkSpecialization<B>, H: ExHashT> Future for Ne
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
// Implementation of `protocol::NetworkOut` using the available local variables.
struct Context<'a, B: BlockT>(&'a mut Libp2pNetService<Message<B>>, &'a PeersetHandle);
struct Context<'a, B: BlockT>(&'a mut Swarm<B>, &'a PeersetHandle);
impl<'a, B: BlockT> NetworkOut<B> for Context<'a, B> {
fn report_peer(&mut self, who: PeerId, reputation: i32) {
self.1.report_peer(who, reputation)
}
fn disconnect_peer(&mut self, who: PeerId) {
self.0.drop_node(&who)
self.0.user_protocol_mut().disconnect_peer(&who)
}
fn send_message(&mut self, who: PeerId, message: Message<B>) {
self.0.send_custom_message(&who, message)
self.0.user_protocol_mut().send_packet(&who, message)
}
}
@@ -598,11 +656,11 @@ impl<B: BlockT + 'static, S: NetworkSpecialization<B>, H: ExHashT> Future for Ne
match self.network_port.poll() {
Ok(Async::NotReady) => break,
Ok(Async::Ready(Some(NetworkMsg::Outgoing(who, outgoing_message)))) =>
self.network_service.lock().send_custom_message(&who, outgoing_message),
self.network_service.lock().user_protocol_mut().send_packet(&who, outgoing_message),
Ok(Async::Ready(Some(NetworkMsg::ReportPeer(who, reputation)))) =>
self.peerset.report_peer(who, reputation),
Ok(Async::Ready(Some(NetworkMsg::DisconnectPeer(who)))) =>
self.network_service.lock().drop_node(&who),
self.network_service.lock().user_protocol_mut().disconnect_peer(&who),
#[cfg(any(test, feature = "test-helpers"))]
Ok(Async::Ready(Some(NetworkMsg::Synchronized))) => {}
@@ -672,19 +730,19 @@ impl<B: BlockT + 'static, S: NetworkSpecialization<B>, H: ExHashT> Future for Ne
let outcome = match poll_value {
Ok(Async::NotReady) => break,
Ok(Async::Ready(Some(Libp2pNetServiceEvent::OpenedCustomProtocol { peer_id, version, debug_info, .. }))) => {
Ok(Async::Ready(Some(CustomProtoOut::CustomProtocolOpen { peer_id, version, .. }))) => {
debug_assert!(
version <= protocol::CURRENT_VERSION as u8
&& version >= protocol::MIN_VERSION as u8
);
self.protocol.on_peer_connected(&mut network_out, peer_id, debug_info);
self.protocol.on_peer_connected(&mut network_out, peer_id);
CustomMessageOutcome::None
}
Ok(Async::Ready(Some(Libp2pNetServiceEvent::ClosedCustomProtocol { peer_id, debug_info, .. }))) => {
self.protocol.on_peer_disconnected(&mut network_out, peer_id, debug_info);
Ok(Async::Ready(Some(CustomProtoOut::CustomProtocolClosed { peer_id, .. }))) => {
self.protocol.on_peer_disconnected(&mut network_out, peer_id);
CustomMessageOutcome::None
},
Ok(Async::Ready(Some(Libp2pNetServiceEvent::CustomMessage { peer_id, message, .. }))) =>
Ok(Async::Ready(Some(CustomProtoOut::CustomMessage { peer_id, message }))) =>
self.protocol.on_custom_message(
&mut network_out,
&*self.transaction_pool,
@@ -692,7 +750,7 @@ impl<B: BlockT + 'static, S: NetworkSpecialization<B>, H: ExHashT> Future for Ne
message,
self.finality_proof_provider.as_ref().map(|p| &**p)
),
Ok(Async::Ready(Some(Libp2pNetServiceEvent::Clogged { peer_id, messages, .. }))) => {
Ok(Async::Ready(Some(CustomProtoOut::Clogged { peer_id, messages, .. }))) => {
debug!(target: "sync", "{} clogging messages:", messages.len());
for msg in messages.into_iter().take(5) {
debug!(target: "sync", "{:?}", msg);
@@ -724,3 +782,93 @@ impl<B: BlockT + 'static, S: NetworkSpecialization<B>, H: ExHashT> Future for Ne
Ok(Async::NotReady)
}
}
/// The libp2p swarm, customized for our needs.
type Swarm<B> = libp2p::core::Swarm<
Boxed<(PeerId, StreamMuxerBox), io::Error>,
Behaviour<CustomProto<Message<B>, Substream<StreamMuxerBox>>, CustomProtoOut<Message<B>>, Substream<StreamMuxerBox>>
>;
/// Starts the substrate libp2p service.
///
/// Returns a stream that must be polled regularly in order for the networking to function.
fn start_service<B: BlockT, Pid: Into<ProtocolId>>(
config: NetworkConfiguration,
protocol_id: Pid,
versions: &[u8],
) -> Result<(Swarm<B>, Arc<transport::BandwidthSinks>, peerset::PeersetHandle), io::Error> {
if let Some(ref path) = config.net_config_path {
fs::create_dir_all(Path::new(path))?;
}
// List of multiaddresses that we know in the network.
let mut known_addresses = Vec::new();
let mut bootnodes = Vec::new();
let mut reserved_nodes = Vec::new();
// Process the bootnodes.
for bootnode in config.boot_nodes.iter() {
match parse_str_addr(bootnode) {
Ok((peer_id, addr)) => {
bootnodes.push(peer_id.clone());
known_addresses.push((peer_id, addr));
},
Err(_) => warn!(target: "sub-libp2p", "Not a valid bootnode address: {}", bootnode),
}
}
// Initialize the reserved peers.
for reserved in config.reserved_nodes.iter() {
if let Ok((peer_id, addr)) = parse_str_addr(reserved) {
reserved_nodes.push(peer_id.clone());
known_addresses.push((peer_id, addr));
} else {
warn!(target: "sub-libp2p", "Not a valid reserved node address: {}", reserved);
}
}
// Build the peerset.
let (peerset, peerset_handle) = peerset::Peerset::from_config(peerset::PeersetConfig {
in_peers: config.in_peers,
out_peers: config.out_peers,
bootnodes,
reserved_only: config.non_reserved_mode == NonReservedPeerMode::Deny,
reserved_nodes,
});
// Private and public keys configuration.
if let NodeKeyConfig::Secp256k1(_) = config.node_key {
warn!(target: "sub-libp2p", "Secp256k1 keys are deprecated in favour of ed25519");
}
let local_identity = config.node_key.clone().into_keypair()?;
let local_public = local_identity.public();
let local_peer_id = local_public.clone().into_peer_id();
info!(target: "sub-libp2p", "Local node identity is: {}", local_peer_id.to_base58());
// Build the swarm.
let (mut swarm, bandwidth) = {
let user_agent = format!("{} ({})", config.client_version, config.node_name);
let proto = CustomProto::new(protocol_id, versions, peerset);
let behaviour = Behaviour::new(proto, user_agent, local_public, known_addresses, config.enable_mdns);
let (transport, bandwidth) = transport::build_transport(
local_identity,
config.wasm_external_transport
);
(Swarm::<B>::new(transport, behaviour, local_peer_id.clone()), bandwidth)
};
// Listen on multiaddresses.
for addr in &config.listen_addresses {
if let Err(err) = Swarm::<B>::listen_on(&mut swarm, addr.clone()) {
warn!(target: "sub-libp2p", "Can't listen on {} because: {:?}", addr, err)
}
}
// Add external addresses.
for addr in &config.public_addresses {
Swarm::<B>::add_external_address(&mut swarm, addr.clone());
}
Ok((swarm, bandwidth, peerset_handle))
}
@@ -14,17 +14,23 @@
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
#![cfg(test)]
use futures::{future, stream, prelude::*, try_ready};
use libp2p::core::swarm::ExpandedSwarm;
use rand::seq::SliceRandom;
use runtime_primitives::traits::Block as BlockT;
use std::{io, time::Duration, time::Instant};
use substrate_network_libp2p::{CustomMessage, Multiaddr, multiaddr::Protocol, ServiceEvent, build_multiaddr};
use test_client::runtime::Block;
use crate::protocol::message::generic::Message;
use crate::{Multiaddr, multiaddr::Protocol, build_multiaddr};
use crate::custom_proto::CustomProtoOut;
use super::{start_service, Swarm};
/// Builds two services. The second one and further have the first one as its bootstrap node.
/// This is to be used only for testing, and a panic will happen if something goes wrong.
fn build_nodes<TMsg>(num: usize, base_port: u16) -> Vec<substrate_network_libp2p::Service<TMsg>>
where TMsg: CustomMessage + Send + 'static
{
let mut result: Vec<substrate_network_libp2p::Service<_>> = Vec::with_capacity(num);
fn build_nodes<B: BlockT>(num: usize, base_port: u16) -> Vec<Swarm<B>> {
let mut result: Vec<Swarm<B>> = Vec::with_capacity(num);
let mut first_addr = None::<Multiaddr>;
for index in 0 .. num {
@@ -32,22 +38,21 @@ fn build_nodes<TMsg>(num: usize, base_port: u16) -> Vec<substrate_network_libp2p
if let Some(first_addr) = first_addr.as_ref() {
boot_nodes.push(first_addr.clone()
.with(Protocol::P2p(result[0].peer_id().clone().into()))
.with(Protocol::P2p(ExpandedSwarm::local_peer_id(&result[0]).clone().into()))
.to_string());
}
let config = substrate_network_libp2p::NetworkConfiguration {
let config = crate::config::NetworkConfiguration {
listen_addresses: vec![build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(base_port + index as u16)]],
boot_nodes,
..substrate_network_libp2p::NetworkConfiguration::default()
..crate::config::NetworkConfiguration::default()
};
if first_addr.is_none() {
first_addr = Some(config.listen_addresses.iter().next().unwrap().clone());
}
let proto = substrate_network_libp2p::RegisteredProtocol::new(&b"tst"[..], &[1]);
result.push(substrate_network_libp2p::start_service(config, proto).unwrap().0);
result.push(start_service::<B, _>(config, &b"tst"[..], &[1]).unwrap().0);
}
result
@@ -56,7 +61,7 @@ fn build_nodes<TMsg>(num: usize, base_port: u16) -> Vec<substrate_network_libp2p
#[test]
fn basic_two_nodes_connectivity() {
let (mut service1, mut service2) = {
let mut l = build_nodes::<Vec<u8>>(2, 50400).into_iter();
let mut l = build_nodes::<Block>(2, 50400).into_iter();
let a = l.next().unwrap();
let b = l.next().unwrap();
(a, b)
@@ -64,7 +69,7 @@ fn basic_two_nodes_connectivity() {
let fut1 = future::poll_fn(move || -> io::Result<_> {
match try_ready!(service1.poll()) {
Some(ServiceEvent::OpenedCustomProtocol { version, .. }) => {
Some(CustomProtoOut::CustomProtocolOpen { version, .. }) => {
assert_eq!(version, 1);
Ok(Async::Ready(()))
},
@@ -74,7 +79,7 @@ fn basic_two_nodes_connectivity() {
let fut2 = future::poll_fn(move || -> io::Result<_> {
match try_ready!(service2.poll()) {
Some(ServiceEvent::OpenedCustomProtocol { version, .. }) => {
Some(CustomProtoOut::CustomProtocolOpen { version, .. }) => {
assert_eq!(version, 1);
Ok(Async::Ready(()))
},
@@ -96,7 +101,7 @@ fn two_nodes_transfer_lots_of_packets() {
const NUM_PACKETS: u32 = 5000;
let (mut service1, mut service2) = {
let mut l = build_nodes::<Vec<u8>>(2, 50450).into_iter();
let mut l = build_nodes::<Block>(2, 50450).into_iter();
let a = l.next().unwrap();
let b = l.next().unwrap();
(a, b)
@@ -105,9 +110,12 @@ fn two_nodes_transfer_lots_of_packets() {
let fut1 = future::poll_fn(move || -> io::Result<_> {
loop {
match try_ready!(service1.poll()) {
Some(ServiceEvent::OpenedCustomProtocol { peer_id, .. }) => {
Some(CustomProtoOut::CustomProtocolOpen { peer_id, .. }) => {
for n in 0 .. NUM_PACKETS {
service1.send_custom_message(&peer_id, vec![(n % 256) as u8]);
service1.user_protocol_mut().send_packet(
&peer_id,
Message::ChainSpecific(vec![(n % 256) as u8])
);
}
},
_ => panic!(),
@@ -119,8 +127,8 @@ fn two_nodes_transfer_lots_of_packets() {
let fut2 = future::poll_fn(move || -> io::Result<_> {
loop {
match try_ready!(service2.poll()) {
Some(ServiceEvent::OpenedCustomProtocol { .. }) => {},
Some(ServiceEvent::CustomMessage { message, .. }) => {
Some(CustomProtoOut::CustomProtocolOpen { .. }) => {},
Some(CustomProtoOut::CustomMessage { message: Message::ChainSpecific(message), .. }) => {
assert_eq!(message.len(), 1);
packet_counter += 1;
if packet_counter == NUM_PACKETS {
@@ -144,25 +152,21 @@ fn many_nodes_connectivity() {
// increased in the `NetworkConfiguration`.
const NUM_NODES: usize = 25;
let mut futures = build_nodes::<Vec<u8>>(NUM_NODES, 50500)
let mut futures = build_nodes::<Block>(NUM_NODES, 50500)
.into_iter()
.map(move |mut node| {
let mut num_connecs = 0;
stream::poll_fn(move || -> io::Result<_> {
loop {
const MAX_BANDWIDTH: u64 = NUM_NODES as u64 * 2048; // 2kiB/s/node
assert!(node.average_download_per_sec() < MAX_BANDWIDTH);
assert!(node.average_upload_per_sec() < MAX_BANDWIDTH);
match try_ready!(node.poll()) {
Some(ServiceEvent::OpenedCustomProtocol { .. }) => {
Some(CustomProtoOut::CustomProtocolOpen { .. }) => {
num_connecs += 1;
assert!(num_connecs < NUM_NODES);
if num_connecs == NUM_NODES - 1 {
return Ok(Async::Ready(Some(true)))
}
}
Some(ServiceEvent::ClosedCustomProtocol { .. }) => {
Some(CustomProtoOut::CustomProtocolClosed { .. }) => {
let was_success = num_connecs == NUM_NODES - 1;
num_connecs -= 1;
if was_success && num_connecs < NUM_NODES - 1 {
@@ -200,7 +204,7 @@ fn many_nodes_connectivity() {
#[test]
fn basic_two_nodes_requests_in_parallel() {
let (mut service1, mut service2) = {
let mut l = build_nodes::<Vec<u8>>(2, 50550).into_iter();
let mut l = build_nodes::<Block>(2, 50550).into_iter();
let a = l.next().unwrap();
let b = l.next().unwrap();
(a, b)
@@ -211,7 +215,7 @@ fn basic_two_nodes_requests_in_parallel() {
let mut to_send = Vec::new();
for _ in 0..200 { // Note: don't make that number too high or the CPU usage will explode.
let msg = (0..10).map(|_| rand::random::<u8>()).collect::<Vec<_>>();
to_send.push(msg);
to_send.push(Message::ChainSpecific(msg));
}
to_send
};
@@ -224,9 +228,9 @@ fn basic_two_nodes_requests_in_parallel() {
let fut1 = future::poll_fn(move || -> io::Result<_> {
loop {
match try_ready!(service1.poll()) {
Some(ServiceEvent::OpenedCustomProtocol { peer_id, .. }) => {
Some(CustomProtoOut::CustomProtocolOpen { peer_id, .. }) => {
for msg in to_send.drain(..) {
service1.send_custom_message(&peer_id, msg);
service1.user_protocol_mut().send_packet(&peer_id, msg);
}
},
_ => panic!(),
@@ -237,8 +241,8 @@ fn basic_two_nodes_requests_in_parallel() {
let fut2 = future::poll_fn(move || -> io::Result<_> {
loop {
match try_ready!(service2.poll()) {
Some(ServiceEvent::OpenedCustomProtocol { .. }) => {},
Some(ServiceEvent::CustomMessage { message, .. }) => {
Some(CustomProtoOut::CustomProtocolOpen { .. }) => {},
Some(CustomProtoOut::CustomMessage { message, .. }) => {
let pos = to_receive.iter().position(|m| *m == message).unwrap();
to_receive.remove(pos);
if to_receive.is_empty() {
@@ -260,7 +264,7 @@ fn reconnect_after_disconnect() {
// check that the disconnect worked, and finally check whether they successfully reconnect.
let (mut service1, mut service2) = {
let mut l = build_nodes::<Vec<u8>>(2, 50350).into_iter();
let mut l = build_nodes::<Block>(2, 50350).into_iter();
let a = l.next().unwrap();
let b = l.next().unwrap();
(a, b)
@@ -281,19 +285,19 @@ fn reconnect_after_disconnect() {
let mut service1_not_ready = false;
match service1.poll().unwrap() {
Async::Ready(Some(ServiceEvent::OpenedCustomProtocol { .. })) => {
Async::Ready(Some(CustomProtoOut::CustomProtocolOpen { .. })) => {
match service1_state {
ServiceState::NotConnected => {
service1_state = ServiceState::FirstConnec;
if service2_state == ServiceState::FirstConnec {
service1.drop_node(service2.peer_id());
service1.user_protocol_mut().disconnect_peer(ExpandedSwarm::local_peer_id(&service2));
}
},
ServiceState::Disconnected => service1_state = ServiceState::ConnectedAgain,
ServiceState::FirstConnec | ServiceState::ConnectedAgain => panic!(),
}
},
Async::Ready(Some(ServiceEvent::ClosedCustomProtocol { .. })) => {
Async::Ready(Some(CustomProtoOut::CustomProtocolClosed { .. })) => {
match service1_state {
ServiceState::FirstConnec => service1_state = ServiceState::Disconnected,
ServiceState::ConnectedAgain| ServiceState::NotConnected |
@@ -305,19 +309,19 @@ fn reconnect_after_disconnect() {
}
match service2.poll().unwrap() {
Async::Ready(Some(ServiceEvent::OpenedCustomProtocol { .. })) => {
Async::Ready(Some(CustomProtoOut::CustomProtocolOpen { .. })) => {
match service2_state {
ServiceState::NotConnected => {
service2_state = ServiceState::FirstConnec;
if service1_state == ServiceState::FirstConnec {
service1.drop_node(service2.peer_id());
service1.user_protocol_mut().disconnect_peer(ExpandedSwarm::local_peer_id(&service2));
}
},
ServiceState::Disconnected => service2_state = ServiceState::ConnectedAgain,
ServiceState::FirstConnec | ServiceState::ConnectedAgain => panic!(),
}
},
Async::Ready(Some(ServiceEvent::ClosedCustomProtocol { .. })) => {
Async::Ready(Some(CustomProtoOut::CustomProtocolClosed { .. })) => {
match service2_state {
ServiceState::FirstConnec => service2_state = ServiceState::Disconnected,
ServiceState::ConnectedAgain| ServiceState::NotConnected |
+11 -11
View File
@@ -42,7 +42,7 @@ use consensus::{BlockOrigin, ForkChoiceStrategy, ImportBlock, JustificationImpor
use crate::consensus_gossip::{ConsensusGossip, MessageRecipient as GossipMessageRecipient, TopicNotification};
use futures::{prelude::*, sync::{mpsc, oneshot}};
use crate::message::Message;
use network_libp2p::PeerId;
use libp2p::PeerId;
use parking_lot::{Mutex, RwLock};
use primitives::{H256, sr25519::Public as AuthorityId, Blake2Hasher};
use crate::protocol::{Context, Protocol, ProtocolConfig, ProtocolStatus, CustomMessageOutcome, NetworkOut};
@@ -296,10 +296,10 @@ pub struct Peer<D, S: NetworkSpecialization<Block>> {
type MessageFilter = dyn Fn(&NetworkMsg<Block>) -> bool;
pub enum FromNetworkMsg<B: BlockT> {
/// A peer connected, with debug info.
PeerConnected(PeerId, String),
/// A peer disconnected, with debug info.
PeerDisconnected(PeerId, String),
/// A peer connected.
PeerConnected(PeerId),
/// A peer disconnected.
PeerDisconnected(PeerId),
/// A custom message from another peer.
CustomMessage(PeerId, Message<B>),
/// Synchronization request.
@@ -504,12 +504,12 @@ impl<D, S: NetworkSpecialization<Block>> Peer<D, S> {
/// Called on connection to other indicated peer.
fn on_connect(&self, other: &Self) {
self.net_proto_channel.send_from_net(FromNetworkMsg::PeerConnected(other.peer_id.clone(), String::new()));
self.net_proto_channel.send_from_net(FromNetworkMsg::PeerConnected(other.peer_id.clone()));
}
/// Called on disconnect from other indicated peer.
fn on_disconnect(&self, other: &Self) {
self.net_proto_channel.send_from_net(FromNetworkMsg::PeerDisconnected(other.peer_id.clone(), String::new()));
self.net_proto_channel.send_from_net(FromNetworkMsg::PeerDisconnected(other.peer_id.clone()));
}
/// Receive a message from another peer. Return a set of peers to disconnect.
@@ -828,12 +828,12 @@ pub trait TestNetFactory: Sized {
tokio::runtime::current_thread::run(futures::future::poll_fn(move || {
while let Async::Ready(msg) = network_to_protocol_rx.poll().unwrap() {
let outcome = match msg {
Some(FromNetworkMsg::PeerConnected(peer_id, debug_msg)) => {
protocol.on_peer_connected(&mut Ctxt(&network_sender), peer_id, debug_msg);
Some(FromNetworkMsg::PeerConnected(peer_id)) => {
protocol.on_peer_connected(&mut Ctxt(&network_sender), peer_id);
CustomMessageOutcome::None
},
Some(FromNetworkMsg::PeerDisconnected(peer_id, debug_msg)) => {
protocol.on_peer_disconnected(&mut Ctxt(&network_sender), peer_id, debug_msg);
Some(FromNetworkMsg::PeerDisconnected(peer_id)) => {
protocol.on_peer_disconnected(&mut Ctxt(&network_sender), peer_id);
CustomMessageOutcome::None
},
Some(FromNetworkMsg::CustomMessage(peer_id, message)) =>