Phase 1 of repo reorg (#719)

* Remove unneeded script

* Rename Substrate Demo -> Substrate

* Rename demo -> node

* Build wasm from last rename.

* Merge ed25519 into substrate-primitives

* Minor tweak

* Rename substrate -> core

* Move substrate-runtime-support to core/runtime/support

* Rename/move substrate-runtime-version

* Move codec up a level

* Rename substrate-codec -> parity-codec

* Move environmental up a level

* Move pwasm-* up to top, ready for removal

* Remove requirement of s-r-support from s-r-primitives

* Move core/runtime/primitives into core/runtime-primitives

* Remove s-r-support dep from s-r-version

* Remove dep of s-r-support from bft

* Remove dep of s-r-support from node/consensus

* Sever all other core deps from s-r-support

* Forgot the no_std directive

* Rename non-SRML modules to sr-* to avoid match clashes

* Move runtime/* to srml/*

* Rename substrate-runtime-* -> srml-*

* Move srml to top-level
This commit is contained in:
Gav Wood
2018-09-12 11:13:31 +02:00
committed by Arkadiy Paronyan
parent 8fe5aa4c81
commit 1e01162505
374 changed files with 2845 additions and 2902 deletions
@@ -0,0 +1,31 @@
// Copyright 2015-2018 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/>.
//! Connection filter trait.
use super::NodeId;
/// Filtered connection direction.
pub enum ConnectionDirection {
Inbound,
Outbound,
}
/// Connection filter. Each connection is checked against `connection_allowed`.
pub trait ConnectionFilter : Send + Sync {
/// Filter a connection. Returns `true` if connection should be allowed. `false` if rejected.
fn connection_allowed(&self, own_id: &NodeId, connecting_id: &NodeId, direction: ConnectionDirection) -> bool;
}
@@ -0,0 +1,290 @@
// Copyright 2018 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 bytes::{Bytes, BytesMut};
use ProtocolId;
use libp2p::core::{Multiaddr, ConnectionUpgrade, Endpoint};
use PacketId;
use std::io::Error as IoError;
use std::vec::IntoIter as VecIntoIter;
use futures::{future, Future, stream, Stream, Sink};
use futures::sync::mpsc;
use tokio_io::{AsyncRead, AsyncWrite};
use unsigned_varint::codec::UviBytes;
/// Connection upgrade for a single protocol.
///
/// Note that "a single protocol" here refers to `par` for example. However
/// each protocol can have multiple different versions for networking purposes.
#[derive(Clone)]
pub struct RegisteredProtocol<T> {
/// Id of the protocol for API purposes.
id: ProtocolId,
/// Base name of the protocol as advertised on the network.
/// Ends with `/` so that we can append a version number behind.
base_name: Bytes,
/// List of protocol versions that we support, plus their packet count.
/// Ordered in descending order so that the best comes first.
/// The packet count is used to filter out invalid messages.
supported_versions: Vec<(u8, u8)>,
/// Custom data.
custom_data: T,
}
/// Output of a `RegisteredProtocol` upgrade.
pub struct RegisteredProtocolOutput<T> {
/// Data passed to `RegisteredProtocol::new`.
pub custom_data: T,
/// Id of the protocol.
pub protocol_id: ProtocolId,
/// Endpoint of the connection.
pub endpoint: Endpoint,
/// Version of the protocol that was negotiated.
pub protocol_version: u8,
/// Channel to sender outgoing messages to.
// TODO: consider assembling packet_id here
pub outgoing: mpsc::UnboundedSender<Bytes>,
/// Stream where incoming messages are received. The stream ends whenever
/// either side is closed.
pub incoming: Box<Stream<Item = (PacketId, Bytes), Error = IoError> + Send>,
}
impl<T> RegisteredProtocol<T> {
/// Creates a new `RegisteredProtocol`. The `custom_data` parameter will be
/// passed inside the `RegisteredProtocolOutput`.
pub fn new(custom_data: T, protocol: ProtocolId, versions: &[(u8, u8)])
-> Self {
let mut proto_name = Bytes::from_static(b"/core/");
proto_name.extend_from_slice(&protocol);
proto_name.extend_from_slice(b"/");
RegisteredProtocol {
base_name: proto_name,
id: protocol,
supported_versions: {
let mut tmp: Vec<_> = versions.iter().rev().cloned().collect();
tmp.sort_unstable_by(|a, b| b.1.cmp(&a.1));
tmp
},
custom_data: custom_data,
}
}
/// Returns the ID of the protocol.
pub fn id(&self) -> ProtocolId {
self.id
}
/// Returns the custom data that was passed to `new`.
pub fn custom_data(&self) -> &T {
&self.custom_data
}
}
// `Maf` is short for `MultiaddressFuture`
impl<T, C, Maf> ConnectionUpgrade<C, Maf> for RegisteredProtocol<T>
where C: AsyncRead + AsyncWrite + Send + 'static, // TODO: 'static :-/
Maf: Future<Item = Multiaddr, Error = IoError> + Send + 'static, // TODO: 'static :(
{
type NamesIter = VecIntoIter<(Bytes, Self::UpgradeIdentifier)>;
type UpgradeIdentifier = u8; // Protocol version
#[inline]
fn protocol_names(&self) -> Self::NamesIter {
// Report each version as an individual protocol.
self.supported_versions.iter().map(|&(ver, _)| {
let num = ver.to_string();
let mut name = self.base_name.clone();
name.extend_from_slice(num.as_bytes());
(name, ver)
}).collect::<Vec<_>>().into_iter()
}
type Output = RegisteredProtocolOutput<T>;
type MultiaddrFuture = Maf;
type Future = future::FutureResult<(Self::Output, Self::MultiaddrFuture), IoError>;
#[allow(deprecated)]
fn upgrade(
self,
socket: C,
protocol_version: Self::UpgradeIdentifier,
endpoint: Endpoint,
remote_addr: Maf
) -> Self::Future {
let packet_count = self.supported_versions
.iter()
.find(|&(v, _)| *v == protocol_version)
.expect("negotiated protocol version that wasn't advertised ; \
programmer error")
.1;
// This function is called whenever we successfully negotiated a
// protocol with a remote (both if initiated by us or by the remote)
// This channel is used to send outgoing packets to the custom_data
// for this open substream.
let (msg_tx, msg_rx) = mpsc::unbounded();
// Build the sink for outgoing network bytes, and the stream for
// incoming instructions. `stream` implements `Stream<Item = Message>`.
enum Message {
/// Received data from the network.
RecvSocket(BytesMut),
/// Data to send to the network.
/// The packet_id must already be inside the `Bytes`.
SendReq(Bytes),
/// The socket has been closed.
Finished,
}
let (sink, stream) = {
let framed = AsyncRead::framed(socket, UviBytes::default());
let msg_rx = msg_rx.map(Message::SendReq)
.map_err(|()| unreachable!("mpsc::UnboundedReceiver never errors"));
let (sink, stream) = framed.split();
let stream = stream.map(Message::RecvSocket)
.chain(stream::once(Ok(Message::Finished)));
(sink, msg_rx.select(stream))
};
let incoming = stream::unfold((sink, stream, false), move |(sink, stream, finished)| {
if finished {
return None
}
Some(stream
.into_future()
.map_err(|(err, _)| err)
.and_then(move |(message, stream)|
match message {
Some(Message::RecvSocket(mut data)) => {
// The `data` should be prefixed by the packet ID,
// therefore an empty packet is invalid.
if data.is_empty() {
debug!(target: "sub-libp2p", "ignoring incoming \
packet because it was empty");
let f = future::ok((None, (sink, stream, false)));
return future::Either::A(f)
}
let packet_id = data[0];
let data = data.split_off(1);
if packet_id >= packet_count {
debug!(target: "sub-libp2p", "ignoring incoming packet \
because packet_id {} is too large", packet_id);
let f = future::ok((None, (sink, stream, false)));
future::Either::A(f)
} else {
let out = Some((packet_id, data.freeze()));
let f = future::ok((out, (sink, stream, false)));
future::Either::A(f)
}
},
Some(Message::SendReq(data)) => {
let fut = sink.send(data)
.map(move |sink| (None, (sink, stream, false)));
future::Either::B(fut)
},
Some(Message::Finished) | None => {
let f = future::ok((None, (sink, stream, true)));
future::Either::A(f)
},
}
))
}).filter_map(|v| v);
let out = RegisteredProtocolOutput {
custom_data: self.custom_data,
protocol_id: self.id,
endpoint,
protocol_version: protocol_version,
outgoing: msg_tx,
incoming: Box::new(incoming),
};
future::ok((out, remote_addr))
}
}
// Connection upgrade for all the protocols contained in it.
#[derive(Clone)]
pub struct RegisteredProtocols<T>(pub Vec<RegisteredProtocol<T>>);
impl<T> RegisteredProtocols<T> {
/// Finds a protocol in the list by its id.
pub fn find_protocol(&self, protocol: ProtocolId)
-> Option<&RegisteredProtocol<T>> {
self.0.iter().find(|p| p.id == protocol)
}
/// Returns true if the given protocol is in the list.
pub fn has_protocol(&self, protocol: ProtocolId) -> bool {
self.0.iter().any(|p| p.id == protocol)
}
}
impl<T> Default for RegisteredProtocols<T> {
fn default() -> Self {
RegisteredProtocols(Vec::new())
}
}
impl<T, C, Maf> ConnectionUpgrade<C, Maf> for RegisteredProtocols<T>
where C: AsyncRead + AsyncWrite + Send + 'static, // TODO: 'static :-/
Maf: Future<Item = Multiaddr, Error = IoError> + Send + 'static, // TODO: 'static :(
{
type NamesIter = VecIntoIter<(Bytes, Self::UpgradeIdentifier)>;
type UpgradeIdentifier = (usize,
<RegisteredProtocol<T> as ConnectionUpgrade<C, Maf>>::UpgradeIdentifier);
fn protocol_names(&self) -> Self::NamesIter {
// We concat the lists of `RegisteredProtocol::protocol_names` for
// each protocol.
self.0.iter().enumerate().flat_map(|(n, proto)|
ConnectionUpgrade::<C, Maf>::protocol_names(proto)
.map(move |(name, id)| (name, (n, id)))
).collect::<Vec<_>>().into_iter()
}
type Output = <RegisteredProtocol<T> as ConnectionUpgrade<C, Maf>>::Output;
type MultiaddrFuture = <RegisteredProtocol<T> as
ConnectionUpgrade<C, Maf>>::MultiaddrFuture;
type Future = <RegisteredProtocol<T> as ConnectionUpgrade<C, Maf>>::Future;
#[inline]
fn upgrade(
self,
socket: C,
upgrade_identifier: Self::UpgradeIdentifier,
endpoint: Endpoint,
remote_addr: Maf
) -> Self::Future {
let (protocol_index, inner_proto_id) = upgrade_identifier;
self.0.into_iter()
.nth(protocol_index)
.expect("invalid protocol index ; programmer logic error")
.upgrade(socket, inner_proto_id, endpoint, remote_addr)
}
}
+221
View File
@@ -0,0 +1,221 @@
// Copyright 2015-2018 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 std::{io, net, fmt};
use libc::{ENFILE, EMFILE};
use io::IoError;
use ethkey;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum DisconnectReason
{
DisconnectRequested,
TCPError,
BadProtocol,
UselessPeer,
TooManyPeers,
DuplicatePeer,
IncompatibleProtocol,
NullIdentity,
ClientQuit,
UnexpectedIdentity,
LocalIdentity,
PingTimeout,
Unknown,
}
impl DisconnectReason {
pub fn from_u8(n: u8) -> DisconnectReason {
match n {
0 => DisconnectReason::DisconnectRequested,
1 => DisconnectReason::TCPError,
2 => DisconnectReason::BadProtocol,
3 => DisconnectReason::UselessPeer,
4 => DisconnectReason::TooManyPeers,
5 => DisconnectReason::DuplicatePeer,
6 => DisconnectReason::IncompatibleProtocol,
7 => DisconnectReason::NullIdentity,
8 => DisconnectReason::ClientQuit,
9 => DisconnectReason::UnexpectedIdentity,
10 => DisconnectReason::LocalIdentity,
11 => DisconnectReason::PingTimeout,
_ => DisconnectReason::Unknown,
}
}
}
impl fmt::Display for DisconnectReason {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::DisconnectReason::*;
let msg = match *self {
DisconnectRequested => "disconnect requested",
TCPError => "TCP error",
BadProtocol => "bad protocol",
UselessPeer => "useless peer",
TooManyPeers => "too many peers",
DuplicatePeer => "duplicate peer",
IncompatibleProtocol => "incompatible protocol",
NullIdentity => "null identity",
ClientQuit => "client quit",
UnexpectedIdentity => "unexpected identity",
LocalIdentity => "local identity",
PingTimeout => "ping timeout",
Unknown => "unknown",
};
f.write_str(msg)
}
}
error_chain! {
foreign_links {
SocketIo(IoError) #[doc = "Socket IO error."];
}
errors {
#[doc = "Error concerning the network address parsing subsystem."]
AddressParse {
description("Failed to parse network address"),
display("Failed to parse network address"),
}
#[doc = "Error concerning the network address resolution subsystem."]
AddressResolve(err: Option<io::Error>) {
description("Failed to resolve network address"),
display("Failed to resolve network address {}", err.as_ref().map_or("".to_string(), |e| e.to_string())),
}
#[doc = "Authentication failure"]
Auth {
description("Authentication failure"),
display("Authentication failure"),
}
#[doc = "Unrecognised protocol"]
BadProtocol {
description("Bad protocol"),
display("Bad protocol"),
}
#[doc = "Expired message"]
Expired {
description("Expired message"),
display("Expired message"),
}
#[doc = "Peer not found"]
PeerNotFound {
description("Peer not found"),
display("Peer not found"),
}
#[doc = "Peer is disconnected"]
Disconnect(reason: DisconnectReason) {
description("Peer disconnected"),
display("Peer disconnected: {}", reason),
}
#[doc = "Invalid node id"]
InvalidNodeId {
description("Invalid node id"),
display("Invalid node id"),
}
#[doc = "Packet size is over the protocol limit"]
OversizedPacket {
description("Packet is too large"),
display("Packet is too large"),
}
#[doc = "Reached system resource limits for this process"]
ProcessTooManyFiles {
description("Too many open files in process."),
display("Too many open files in this process. Check your resource limits and restart parity"),
}
#[doc = "Reached system wide resource limits"]
SystemTooManyFiles {
description("Too many open files on system."),
display("Too many open files on system. Consider closing some processes/release some file handlers or increas the system-wide resource limits and restart parity."),
}
#[doc = "An unknown IO error occurred."]
Io(err: io::Error) {
description("IO Error"),
display("Unexpected IO error: {}", err),
}
}
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Self {
match err.raw_os_error() {
Some(ENFILE) => ErrorKind::ProcessTooManyFiles.into(),
Some(EMFILE) => ErrorKind::SystemTooManyFiles.into(),
_ => Error::from_kind(ErrorKind::Io(err))
}
}
}
impl From<ethkey::Error> for Error {
fn from(_err: ethkey::Error) -> Self {
ErrorKind::Auth.into()
}
}
impl From<ethkey::crypto::Error> for Error {
fn from(_err: ethkey::crypto::Error) -> Self {
ErrorKind::Auth.into()
}
}
impl From<net::AddrParseError> for Error {
fn from(_err: net::AddrParseError) -> Self { ErrorKind::AddressParse.into() }
}
#[test]
fn test_errors() {
assert_eq!(DisconnectReason::ClientQuit, DisconnectReason::from_u8(8));
let mut r = DisconnectReason::DisconnectRequested;
for i in 0 .. 20 {
r = DisconnectReason::from_u8(i);
}
assert_eq!(DisconnectReason::Unknown, r);
}
#[test]
fn test_io_errors() {
use libc::{EMFILE, ENFILE};
assert_matches!(
<Error as From<io::Error>>::from(
io::Error::from_raw_os_error(ENFILE)
).kind(),
ErrorKind::ProcessTooManyFiles);
assert_matches!(
<Error as From<io::Error>>::from(
io::Error::from_raw_os_error(EMFILE)
).kind(),
ErrorKind::SystemTooManyFiles);
assert_matches!(
<Error as From<io::Error>>::from(
io::Error::from_raw_os_error(0)
).kind(),
ErrorKind::Io(_));
}
+75
View File
@@ -0,0 +1,75 @@
// Copyright 2018 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/>.
// tag::description[]
//! TODO: Missing doc
// end::description[]
#![recursion_limit="128"]
#![type_length_limit = "268435456"]
extern crate parking_lot;
extern crate fnv;
extern crate futures;
extern crate tokio;
extern crate tokio_io;
extern crate tokio_timer;
extern crate ethkey;
extern crate libc;
extern crate libp2p;
extern crate rand;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate bytes;
extern crate unsigned_varint;
extern crate ethcore_io as io;
extern crate ethereum_types;
#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate log;
#[cfg(test)] #[macro_use]
extern crate assert_matches;
pub use connection_filter::{ConnectionFilter, ConnectionDirection};
pub use io::TimerToken;
pub use error::{Error, ErrorKind, DisconnectReason};
pub use libp2p::{Multiaddr, multiaddr::AddrComponent};
pub use traits::*;
mod connection_filter;
mod custom_proto;
mod error;
mod network_state;
mod service;
mod timeouts;
mod topology;
mod traits;
mod transport;
pub use service::NetworkService;
/// Check if node url is valid
pub fn validate_node_url(url: &str) -> Result<(), Error> {
match url.parse::<libp2p::multiaddr::Multiaddr>() {
Ok(_) => Ok(()),
Err(_) => Err(ErrorKind::InvalidNodeId.into()),
}
}
@@ -0,0 +1,953 @@
// Copyright 2018 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 bytes::Bytes;
use fnv::{FnvHashMap, FnvHashSet};
use futures::sync::mpsc;
use libp2p::core::{multiaddr::ToMultiaddr, Multiaddr, AddrComponent, Endpoint, UniqueConnec};
use libp2p::core::{UniqueConnecState, PeerId, PublicKey};
use libp2p::kad::KadConnecController;
use libp2p::ping::Pinger;
use libp2p::secio;
use {Error, ErrorKind, NetworkConfiguration, NonReservedPeerMode};
use {NodeIndex, ProtocolId, SessionInfo};
use parking_lot::{Mutex, RwLock};
use rand::{self, Rng};
use topology::{DisconnectReason, NetTopology};
use std::fs;
use std::io::{Error as IoError, ErrorKind as IoErrorKind, Read, Write};
use std::path::Path;
use std::sync::atomic;
use std::time::{Duration, Instant};
// File where the peers are stored.
const NODES_FILE: &str = "nodes.json";
// File where the private key is stored.
const SECRET_FILE: &str = "secret";
// Duration during which a peer is disabled.
const PEER_DISABLE_DURATION: Duration = Duration::from_secs(5 * 60);
// Common struct shared throughout all the components of the service.
pub struct NetworkState {
/// Contains the information about the network.
topology: RwLock<NetTopology>,
/// Active connections.
connections: RwLock<Connections>,
/// Maximum incoming peers.
max_incoming_peers: u32,
/// Maximum outgoing peers.
max_outgoing_peers: u32,
/// If true, only reserved peers can connect.
reserved_only: atomic::AtomicBool,
/// List of the IDs of the reserved peers.
reserved_peers: RwLock<FnvHashSet<PeerId>>,
/// Each node we discover gets assigned a new unique ID. This ID increases linearly.
next_node_index: atomic::AtomicUsize,
/// List of the IDs of the disabled peers. These peers will see their
/// connections refused. Includes the time when the disabling expires.
disabled_nodes: Mutex<FnvHashMap<PeerId, Instant>>,
/// Local private key.
local_private_key: secio::SecioKeyPair,
/// Local public key.
local_public_key: PublicKey,
}
struct Connections {
/// For each libp2p peer ID, the ID of the peer in the API we expose.
/// Also corresponds to the index in `info_by_peer`.
peer_by_nodeid: FnvHashMap<PeerId, usize>,
/// For each peer ID, information about our connection to this peer.
info_by_peer: FnvHashMap<NodeIndex, PeerConnectionInfo>,
}
struct PeerConnectionInfo {
/// A list of protocols, and the potential corresponding connection.
/// The `UniqueConnec` contains a sender and the protocol version.
/// The sender can be used to transmit data for the remote. Note that the
/// packet_id has to be inside the `Bytes`.
protocols: Vec<(ProtocolId, UniqueConnec<(mpsc::UnboundedSender<Bytes>, u8)>)>,
/// The Kademlia connection to this node.
kad_connec: UniqueConnec<KadConnecController>,
/// The ping connection to this node.
ping_connec: UniqueConnec<Pinger>,
/// Id of the peer.
id: PeerId,
/// True if this connection was initiated by us. `None` if we're not connected.
/// Note that it is theoretically possible that we dial the remote at the
/// same time they dial us, in which case the protocols may be dispatched
/// between both connections, and in which case the value here will be racy.
originated: Option<bool>,
/// Latest known ping duration.
ping: Option<Duration>,
/// The client version of the remote, or `None` if not known.
client_version: Option<String>,
/// The multiaddresses of the remote, or `None` if not known.
remote_addresses: Vec<Multiaddr>,
/// The local multiaddress used to communicate with the remote, or `None`
/// if not known.
// TODO: never filled ; also shouldn't be an `Option`
local_address: Option<Multiaddr>,
}
/// Simplified, POD version of PeerConnectionInfo.
#[derive(Debug, Clone)]
pub struct PeerInfo {
/// Id of the peer.
pub id: PeerId,
/// True if this connection was initiated by us.
/// Note that it is theoretically possible that we dial the remote at the
/// same time they dial us, in which case the protocols may be dispatched
/// between both connections, and in which case the value here will be racy.
pub originated: bool,
/// Latest known ping duration.
pub ping: Option<Duration>,
/// The client version of the remote, or `None` if not known.
pub client_version: Option<String>,
/// The multiaddress of the remote.
pub remote_address: Option<Multiaddr>,
/// The local multiaddress used to communicate with the remote, or `None`
/// if not known.
pub local_address: Option<Multiaddr>,
}
impl<'a> From<&'a PeerConnectionInfo> for PeerInfo {
fn from(i: &'a PeerConnectionInfo) -> PeerInfo {
PeerInfo {
id: i.id.clone(),
originated: i.originated.unwrap_or(true),
ping: i.ping,
client_version: i.client_version.clone(),
remote_address: i.remote_addresses.get(0).map(|a| a.clone()),
local_address: i.local_address.clone(),
}
}
}
impl NetworkState {
pub fn new(config: &NetworkConfiguration) -> Result<NetworkState, Error> {
// Private and public keys configuration.
let local_private_key = obtain_private_key(&config)?;
let local_public_key = local_private_key.to_public_key();
// Build the storage for peers, including the bootstrap nodes.
let mut topology = if let Some(ref path) = config.net_config_path {
let path = Path::new(path).join(NODES_FILE);
debug!(target: "sub-libp2p", "Initializing peer store for JSON file {:?}", path);
NetTopology::from_file(path)
} else {
debug!(target: "sub-libp2p", "No peers file configured ; peers won't be saved");
NetTopology::memory()
};
let reserved_peers = {
let mut reserved_peers = FnvHashSet::with_capacity_and_hasher(
config.reserved_nodes.len(),
Default::default()
);
for peer in config.reserved_nodes.iter() {
let (id, _) = parse_and_add_to_topology(peer, &mut topology)?;
reserved_peers.insert(id);
}
RwLock::new(reserved_peers)
};
let expected_max_peers = config.max_peers as usize + config.reserved_nodes.len();
Ok(NetworkState {
topology: RwLock::new(topology),
max_outgoing_peers: config.min_peers,
max_incoming_peers: config.max_peers.saturating_sub(config.min_peers),
connections: RwLock::new(Connections {
peer_by_nodeid: FnvHashMap::with_capacity_and_hasher(expected_max_peers, Default::default()),
info_by_peer: FnvHashMap::with_capacity_and_hasher(expected_max_peers, Default::default()),
}),
reserved_only: atomic::AtomicBool::new(config.non_reserved_mode == NonReservedPeerMode::Deny),
reserved_peers,
next_node_index: atomic::AtomicUsize::new(0),
disabled_nodes: Mutex::new(Default::default()),
local_private_key,
local_public_key,
})
}
/// Returns the private key of the local node.
pub fn local_private_key(&self) -> &secio::SecioKeyPair {
&self.local_private_key
}
/// Returns the public key of the local node.
pub fn local_public_key(&self) -> &PublicKey {
&self.local_public_key
}
/// Returns a list of peers and addresses which we should try connect to.
///
/// Because of expiration and back-off mechanisms, this list can change
/// by itself over time. The `Instant` that is returned corresponds to
/// the earlier known time when a new entry will be added automatically to
/// the list.
pub fn outgoing_connections_to_attempt(&self) -> (Vec<(PeerId, Multiaddr)>, Instant) {
// TODO: handle better
let connections = self.connections.read();
let num_to_attempt = if self.reserved_only.load(atomic::Ordering::Relaxed) {
0
} else {
let num_open_custom_connections = num_open_custom_connections(&connections, &self.reserved_peers.read());
self.max_outgoing_peers.saturating_sub(num_open_custom_connections.unreserved_outgoing)
};
let topology = self.topology.read();
let (list, change) = topology.addrs_to_attempt();
let list = list
.filter(|&(peer, _)| {
// Filter out peers which we are already connected to.
let cur = match connections.peer_by_nodeid.get(peer) {
Some(e) => e,
None => return true
};
let infos = match connections.info_by_peer.get(&cur) {
Some(i) => i,
None => return true
};
!infos.protocols.iter().any(|(_, conn)| conn.is_alive())
})
.take(num_to_attempt as usize)
.map(|(addr, peer)| (addr.clone(), peer.clone()))
.collect();
(list, change)
}
/// Returns true if we are connected to any peer at all.
pub fn has_connected_peer(&self) -> bool {
!self.connections.read().peer_by_nodeid.is_empty()
}
/// Get a list of all connected peers by id.
pub fn connected_peers(&self) -> Vec<NodeIndex> {
self.connections.read().peer_by_nodeid.values().cloned().collect()
}
/// Returns true if the given `NodeIndex` is valid.
///
/// `NodeIndex`s are never reused, so once this function returns `false` it
/// will never return `true` again for the same `NodeIndex`.
pub fn is_peer_connected(&self, peer: NodeIndex) -> bool {
self.connections.read().info_by_peer.contains_key(&peer)
}
/// Reports the ping of the peer. Returned later by `session_info()`.
/// No-op if the `who` is not valid/expired.
pub fn report_ping_duration(&self, who: NodeIndex, ping: Duration) {
let mut connections = self.connections.write();
let info = match connections.info_by_peer.get_mut(&who) {
Some(info) => info,
None => return,
};
info.ping = Some(ping);
}
/// If we're connected to a peer with the given protocol, returns
/// information about the connection. Otherwise, returns `None`.
pub fn session_info(&self, peer: NodeIndex, protocol: ProtocolId) -> Option<SessionInfo> {
let connections = self.connections.read();
let info = match connections.info_by_peer.get(&peer) {
Some(info) => info,
None => return None,
};
let protocol_version = match info.protocols.iter().find(|&(ref p, _)| p == &protocol) {
Some(&(_, ref unique_connec)) =>
if let Some(val) = unique_connec.poll() {
val.1 as u32
} else {
return None
}
None => return None,
};
Some(SessionInfo {
id: None, // TODO: ???? what to do??? wrong format!
client_version: info.client_version.clone().take().unwrap_or(String::new()),
protocol_version,
capabilities: Vec::new(), // TODO: list of supported protocols ; hard
peer_capabilities: Vec::new(), // TODO: difference with `peer_capabilities`?
ping: info.ping,
originated: info.originated.unwrap_or(true),
remote_address: info.remote_addresses.get(0).map(|a| a.to_string()).unwrap_or_default(),
local_address: info.local_address.as_ref().map(|a| a.to_string())
.unwrap_or(String::new()),
})
}
/// If we're connected to a peer with the given protocol, returns the
/// protocol version. Otherwise, returns `None`.
pub fn protocol_version(&self, peer: NodeIndex, protocol: ProtocolId) -> Option<u8> {
let connections = self.connections.read();
let peer = match connections.info_by_peer.get(&peer) {
Some(peer) => peer,
None => return None,
};
peer.protocols.iter()
.find(|p| p.0 == protocol)
.and_then(|p| p.1.poll())
.map(|(_, version)| version)
}
/// Equivalent to `session_info(peer).map(|info| info.client_version)`.
pub fn peer_client_version(&self, peer: NodeIndex, protocol: ProtocolId) -> Option<String> {
// TODO: implement more directly, without going through `session_info`
self.session_info(peer, protocol)
.map(|info| info.client_version)
}
/// Adds an address discovered by Kademlia.
/// Note that we don't have to be connected to a peer to add an address.
/// If `connectable` is `true`, that means we have a hint from a remote that this node can be
/// connected to.
pub fn add_kad_discovered_addr(&self, node_id: &PeerId, addr: Multiaddr, connectable: bool) {
self.topology.write().add_kademlia_discovered_addr(node_id, addr, connectable)
}
/// Returns the known multiaddresses of a peer.
///
/// The boolean associated to each address indicates whether we're connected to it.
pub fn addrs_of_peer(&self, node_id: &PeerId) -> Vec<(Multiaddr, bool)> {
let topology = self.topology.read();
// Note: I have no idea why, but fusing the two lines below fails the
// borrow check
let out: Vec<_> = topology
.addrs_of_peer(node_id).map(|(a, c)| (a.clone(), c)).collect();
out
}
/// Sets information about a peer.
///
/// No-op if the node index is invalid.
pub fn set_node_info(
&self,
node_index: NodeIndex,
client_version: String
) {
let mut connections = self.connections.write();
let infos = match connections.info_by_peer.get_mut(&node_index) {
Some(i) => i,
None => return
};
infos.client_version = Some(client_version);
}
/// Adds a peer to the internal peer store.
/// Returns an error if the peer address is invalid.
pub fn add_bootstrap_peer(&self, peer: &str) -> Result<(PeerId, Multiaddr), Error> {
parse_and_add_to_topology(peer, &mut self.topology.write())
}
/// Adds a reserved peer to the list of reserved peers.
/// Returns an error if the peer address is invalid.
pub fn add_reserved_peer(&self, peer: &str) -> Result<(), Error> {
let (id, _) = parse_and_add_to_topology(peer, &mut self.topology.write())?;
self.reserved_peers.write().insert(id);
Ok(())
}
/// Removes the peer from the list of reserved peers. If we're in reserved mode, drops any
/// active connection to this peer.
/// Returns an error if the peer address is invalid.
pub fn remove_reserved_peer(&self, peer: &str) -> Result<(), Error> {
let (id, _) = parse_and_add_to_topology(peer, &mut self.topology.write())?;
self.reserved_peers.write().remove(&id);
// Dropping the peer if we're in reserved mode.
if self.reserved_only.load(atomic::Ordering::SeqCst) {
let mut connections = self.connections.write();
if let Some(who) = connections.peer_by_nodeid.remove(&id) {
connections.info_by_peer.remove(&who);
// TODO: use drop_peer instead
}
}
Ok(())
}
/// Set the non-reserved peer mode.
pub fn set_non_reserved_mode(&self, mode: NonReservedPeerMode) {
match mode {
NonReservedPeerMode::Accept =>
self.reserved_only.store(false, atomic::Ordering::SeqCst),
NonReservedPeerMode::Deny =>
// TODO: drop existing peers?
self.reserved_only.store(true, atomic::Ordering::SeqCst),
}
}
/// Reports that we tried to connect to the given address but failed.
///
/// This decreases the chance this address will be tried again in the future.
#[inline]
pub fn report_failed_to_connect(&self, addr: &Multiaddr) {
trace!(target: "sub-libp2p", "Failed to connect to {:?}", addr);
self.topology.write().report_failed_to_connect(addr);
}
/// Returns the `NodeIndex` corresponding to a node id, or assigns a `NodeIndex` if none
/// exists.
///
/// Returns an error if this node is on the list of disabled/banned nodes..
pub fn assign_node_index(
&self,
node_id: &PeerId
) -> Result<NodeIndex, IoError> {
// Check whether node is disabled.
// TODO: figure out the locking strategy here to avoid possible deadlocks
// TODO: put disabled_nodes in connections?
let mut disabled_nodes = self.disabled_nodes.lock();
if let Some(timeout) = disabled_nodes.get(node_id).cloned() {
if timeout > Instant::now() {
debug!(target: "sub-libp2p", "Refusing peer {:?} because it is disabled", node_id);
return Err(IoError::new(IoErrorKind::ConnectionRefused, "peer is disabled"));
} else {
disabled_nodes.remove(node_id);
}
}
drop(disabled_nodes);
let mut connections = self.connections.write();
let connections = &mut *connections;
let peer_by_nodeid = &mut connections.peer_by_nodeid;
let info_by_peer = &mut connections.info_by_peer;
let who = *peer_by_nodeid.entry(node_id.clone()).or_insert_with(|| {
let new_id = self.next_node_index.fetch_add(1, atomic::Ordering::Relaxed);
trace!(target: "sub-libp2p", "Creating new peer #{:?} for {:?}", new_id, node_id);
info_by_peer.insert(new_id, PeerConnectionInfo {
protocols: Vec::new(), // TODO: Vec::with_capacity(num_registered_protocols),
kad_connec: UniqueConnec::empty(),
ping_connec: UniqueConnec::empty(),
id: node_id.clone(),
originated: None,
ping: None,
client_version: None,
local_address: None,
remote_addresses: Vec::with_capacity(1),
});
new_id
});
Ok(who)
}
/// Notifies that we're connected to a node through an address.
///
/// Returns an error if we refuse the connection.
///
/// Note that is it legal to connection multiple times to the same node id through different
/// addresses and endpoints.
pub fn report_connected(
&self,
node_index: NodeIndex,
addr: &Multiaddr,
endpoint: Endpoint
) -> Result<(), IoError> {
let mut connections = self.connections.write();
// TODO: double locking in this function ; although this has been reviewed to not deadlock
// as of the writing of this code, it is possible that a later change that isn't carefully
// reviewed triggers one
if endpoint == Endpoint::Listener {
let stats = num_open_custom_connections(&connections, &self.reserved_peers.read());
if stats.unreserved_incoming >= self.max_incoming_peers {
debug!(target: "sub-libp2p", "Refusing incoming connection from {} because we \
reached max incoming peers", addr);
return Err(IoError::new(IoErrorKind::ConnectionRefused,
"maximum incoming peers reached"));
}
}
let infos = match connections.info_by_peer.get_mut(&node_index) {
Some(i) => i,
None => return Ok(())
};
if !infos.remote_addresses.iter().any(|a| a == addr) {
infos.remote_addresses.push(addr.clone());
}
if infos.originated.is_none() {
infos.originated = Some(endpoint == Endpoint::Dialer);
}
self.topology.write().report_connected(addr, &infos.id);
Ok(())
}
/// Returns the node id from a node index.
///
/// Returns `None` if the node index is invalid.
pub fn node_id_from_index(
&self,
node_index: NodeIndex
) -> Option<PeerId> {
let mut connections = self.connections.write();
let infos = match connections.info_by_peer.get_mut(&node_index) {
Some(i) => i,
None => return None
};
Some(infos.id.clone())
}
/// Obtains the `UniqueConnec` corresponding to the Kademlia connection to a peer.
///
/// Returns `None` if the node index is invalid.
pub fn kad_connection(
&self,
node_index: NodeIndex
) -> Option<UniqueConnec<KadConnecController>> {
let mut connections = self.connections.write();
let infos = match connections.info_by_peer.get_mut(&node_index) {
Some(i) => i,
None => return None
};
Some(infos.kad_connec.clone())
}
/// Obtains the `UniqueConnec` corresponding to the Ping connection to a peer.
///
/// Returns `None` if the node index is invalid.
pub fn ping_connection(
&self,
node_index: NodeIndex
) -> Option<UniqueConnec<Pinger>> {
let mut connections = self.connections.write();
let infos = match connections.info_by_peer.get_mut(&node_index) {
Some(i) => i,
None => return None
};
Some(infos.ping_connec.clone())
}
/// Cleans up inactive connections and returns a list of
/// connections to ping and identify.
pub fn cleanup_and_prepare_updates(
&self
) -> Vec<PeriodicUpdate> {
self.topology.write().cleanup();
let mut connections = self.connections.write();
let connections = &mut *connections;
let peer_by_nodeid = &mut connections.peer_by_nodeid;
let info_by_peer = &mut connections.info_by_peer;
let mut ret = Vec::with_capacity(info_by_peer.len());
info_by_peer.retain(|&who, infos| {
// Remove the peer if neither Kad nor any protocol is alive.
if !infos.kad_connec.is_alive() &&
!infos.protocols.iter().any(|(_, conn)| conn.is_alive())
{
peer_by_nodeid.remove(&infos.id);
trace!(target: "sub-libp2p", "Cleaning up expired peer \
#{:?} ({:?})", who, infos.id);
return false;
}
if let Some(addr) = infos.remote_addresses.get(0) {
ret.push(PeriodicUpdate {
node_index: who,
peer_id: infos.id.clone(),
address: addr.clone(),
pinger: infos.ping_connec.clone(),
identify: infos.client_version.is_none(),
});
}
true
});
ret
}
/// Obtains the `UniqueConnec` corresponding to a custom protocol connection to a peer.
///
/// Returns `None` if the node index is invalid.
pub fn custom_proto(
&self,
node_index: NodeIndex,
protocol_id: ProtocolId,
) -> Option<UniqueConnec<(mpsc::UnboundedSender<Bytes>, u8)>> {
let mut connections = self.connections.write();
let infos = match connections.info_by_peer.get_mut(&node_index) {
Some(i) => i,
None => return None
};
if let Some((_, ref uconn)) = infos.protocols.iter().find(|&(prot, _)| prot == &protocol_id) {
return Some(uconn.clone())
}
let unique_connec = UniqueConnec::empty();
infos.protocols.push((protocol_id.clone(), unique_connec.clone()));
Some(unique_connec)
}
/// Sends some data to the given peer, using the sender that was passed
/// to the `UniqueConnec` of `custom_proto`.
pub fn send(&self, who: NodeIndex, protocol: ProtocolId, message: Bytes) -> Result<(), Error> {
if let Some(peer) = self.connections.read().info_by_peer.get(&who) {
let sender = peer.protocols.iter().find(|elem| elem.0 == protocol)
.and_then(|e| e.1.poll())
.map(|e| e.0);
if let Some(sender) = sender {
sender.unbounded_send(message)
.map_err(|err| ErrorKind::Io(IoError::new(IoErrorKind::Other, err)))?;
Ok(())
} else {
// We are connected to this peer, but not with the current
// protocol.
debug!(target: "sub-libp2p",
"Tried to send message to peer {} for which we aren't connected with the requested protocol",
who
);
return Err(ErrorKind::PeerNotFound.into())
}
} else {
debug!(target: "sub-libp2p", "Tried to send message to invalid peer ID {}", who);
return Err(ErrorKind::PeerNotFound.into())
}
}
/// Get the info on a peer, if there's an active connection.
pub fn peer_info(&self, who: NodeIndex) -> Option<PeerInfo> {
self.connections.read().info_by_peer.get(&who).map(Into::into)
}
/// Reports that an attempt to make a low-level ping of the peer failed.
pub fn report_ping_failed(&self, who: NodeIndex) {
self.drop_peer(who);
}
/// Disconnects a peer, if a connection exists (ie. drops the Kademlia
/// controller, and the senders that were stored in the `UniqueConnec` of
/// `custom_proto`).
pub fn drop_peer(&self, who: NodeIndex) {
let mut connections = self.connections.write();
if let Some(peer_info) = connections.info_by_peer.remove(&who) {
trace!(target: "sub-libp2p", "Destroying peer #{} {:?} ; kademlia = {:?} ; num_protos = {:?}",
who,
peer_info.id,
peer_info.kad_connec.is_alive(),
peer_info.protocols.iter().filter(|c| c.1.is_alive()).count());
let old = connections.peer_by_nodeid.remove(&peer_info.id);
debug_assert_eq!(old, Some(who));
for addr in &peer_info.remote_addresses {
self.topology.write().report_disconnected(addr,
DisconnectReason::ClosedGracefully); // TODO: wrong reason
}
}
}
/// Disconnects all the peers.
/// This destroys all the Kademlia controllers and the senders that were
/// stored in the `UniqueConnec` of `custom_proto`.
pub fn disconnect_all(&self) {
let mut connec = self.connections.write();
*connec = Connections {
info_by_peer: FnvHashMap::with_capacity_and_hasher(
connec.peer_by_nodeid.capacity(), Default::default()),
peer_by_nodeid: FnvHashMap::with_capacity_and_hasher(
connec.peer_by_nodeid.capacity(), Default::default()),
};
}
/// Disables a peer for `PEER_DISABLE_DURATION`. This adds the peer to the
/// list of disabled peers, and drops any existing connections if
/// necessary (ie. drops the sender that was stored in the `UniqueConnec`
/// of `custom_proto`).
pub fn ban_peer(&self, who: NodeIndex, reason: &str) {
// TODO: what do we do if the peer is reserved?
// TODO: same logging as in drop_peer
let mut connections = self.connections.write();
let peer_info = if let Some(peer_info) = connections.info_by_peer.remove(&who) {
if let &Some(ref client_version) = &peer_info.client_version {
info!(target: "network", "Peer {} (version: {}, addresses: {:?}) disabled. {}", who, client_version, peer_info.remote_addresses, reason);
} else {
info!(target: "network", "Peer {} (addresses: {:?}) disabled. {}", who, peer_info.remote_addresses, reason);
}
let old = connections.peer_by_nodeid.remove(&peer_info.id);
debug_assert_eq!(old, Some(who));
peer_info
} else {
return
};
drop(connections);
let timeout = Instant::now() + PEER_DISABLE_DURATION;
self.disabled_nodes.lock().insert(peer_info.id.clone(), timeout);
}
/// Flushes the caches to the disk.
///
/// This is done in an atomical way, so that an error doesn't corrupt
/// anything.
pub fn flush_caches_to_disk(&self) -> Result<(), IoError> {
match self.topology.read().flush_to_disk() {
Ok(()) => {
debug!(target: "sub-libp2p", "Flushed JSON peer store to disk");
Ok(())
}
Err(err) => {
warn!(target: "sub-libp2p", "Failed to flush changes to JSON peer store: {}", err);
Err(err)
}
}
}
}
impl Drop for NetworkState {
fn drop(&mut self) {
let _ = self.flush_caches_to_disk();
}
}
/// Periodic update that should be performed by the user of the network state.
pub struct PeriodicUpdate {
/// Index of the node in the network state.
pub node_index: NodeIndex,
/// Id of the peer.
pub peer_id: PeerId,
/// Address of the node to ping.
pub address: Multiaddr,
/// Object that allows pinging the node.
pub pinger: UniqueConnec<Pinger>,
/// The node should be identified as well.
pub identify: bool,
}
struct OpenCustomConnectionsNumbers {
/// Total number of open and pending connections.
pub total: u32,
/// Unreserved incoming number of open and pending connections.
pub unreserved_incoming: u32,
/// Unreserved outgoing number of open and pending connections.
pub unreserved_outgoing: u32,
}
/// Returns the number of open and pending connections with
/// custom protocols.
fn num_open_custom_connections(connections: &Connections, reserved_peers: &FnvHashSet<PeerId>) -> OpenCustomConnectionsNumbers {
let filtered = connections
.info_by_peer
.values()
.filter(|info|
info.protocols.iter().any(|&(_, ref connec)|
match connec.state() {
UniqueConnecState::Pending | UniqueConnecState::Full => true,
_ => false
}
)
);
let mut total: u32 = 0;
let mut unreserved_incoming: u32 = 0;
let mut unreserved_outgoing: u32 = 0;
for info in filtered {
total += 1;
let node_is_reserved = reserved_peers.contains(&info.id);
if !node_is_reserved {
if !info.originated.unwrap_or(true) {
unreserved_incoming += 1;
} else {
unreserved_outgoing += 1;
}
}
}
OpenCustomConnectionsNumbers {
total,
unreserved_incoming,
unreserved_outgoing,
}
}
/// Parses an address of the form `/ip4/x.x.x.x/tcp/x/p2p/xxxxxx`, and adds it
/// to the given topology. Returns the corresponding peer ID and multiaddr.
fn parse_and_add_to_topology(
addr_str: &str,
topology: &mut NetTopology
) -> Result<(PeerId, Multiaddr), Error> {
let mut addr = addr_str.to_multiaddr().map_err(|_| ErrorKind::AddressParse)?;
let who = match addr.pop() {
Some(AddrComponent::P2P(key)) =>
PeerId::from_multihash(key).map_err(|_| ErrorKind::AddressParse)?,
_ => return Err(ErrorKind::AddressParse.into()),
};
topology.add_bootstrap_addr(&who, addr.clone());
Ok((who, addr))
}
/// Obtains or generates the local private key using the configuration.
fn obtain_private_key(config: &NetworkConfiguration)
-> Result<secio::SecioKeyPair, IoError> {
if let Some(ref secret) = config.use_secret {
// Key was specified in the configuration.
secio::SecioKeyPair::secp256k1_raw_key(&secret[..])
.map_err(|err| IoError::new(IoErrorKind::InvalidData, err))
} else {
if let Some(ref path) = config.net_config_path {
fs::create_dir_all(Path::new(path))?;
// Try fetch the key from a the file containing th esecret.
let secret_path = Path::new(path).join(SECRET_FILE);
match load_private_key_from_file(&secret_path) {
Ok(s) => Ok(s),
Err(err) => {
// Failed to fetch existing file ; generate a new key
trace!(target: "sub-libp2p",
"Failed to load existing secret key file {:?}, generating new key ; err = {:?}",
secret_path,
err
);
Ok(gen_key_and_try_write_to_file(&secret_path))
}
}
} else {
// No path in the configuration, nothing we can do except generate
// a new key.
let mut key: [u8; 32] = [0; 32];
rand::rngs::EntropyRng::new().fill(&mut key);
Ok(secio::SecioKeyPair::secp256k1_raw_key(&key)
.expect("randomly-generated key with correct len should always be valid"))
}
}
}
/// Tries to load a private key from a file located at the given path.
fn load_private_key_from_file<P>(path: P)
-> Result<secio::SecioKeyPair, IoError>
where P: AsRef<Path>
{
fs::File::open(path)
.and_then(|mut file| {
// We are in 2018 and there is still no method on `std::io::Read`
// that directly returns a `Vec`.
let mut buf = Vec::new();
file.read_to_end(&mut buf).map(|_| buf)
})
.and_then(|content|
secio::SecioKeyPair::secp256k1_raw_key(&content)
.map_err(|err| IoError::new(IoErrorKind::InvalidData, err))
)
}
/// Generates a new secret key and tries to write it to the given file.
/// Doesn't error if we couldn't open or write to the file.
fn gen_key_and_try_write_to_file<P>(path: P) -> secio::SecioKeyPair
where P: AsRef<Path> {
let raw_key: [u8; 32] = rand::rngs::EntropyRng::new().gen();
let secio_key = secio::SecioKeyPair::secp256k1_raw_key(&raw_key)
.expect("randomly-generated key with correct len should always be valid");
// And store the newly-generated key in the file if possible.
// Errors that happen while doing so are ignored.
match open_priv_key_file(&path) {
Ok(mut file) =>
match file.write_all(&raw_key) {
Ok(()) => (),
Err(err) => warn!(target: "sub-libp2p",
"Failed to write secret key in file {:?} ; err = {:?}",
path.as_ref(),
err
),
},
Err(err) => warn!(target: "sub-libp2p",
"Failed to store secret key in file {:?} ; err = {:?}",
path.as_ref(),
err
),
}
secio_key
}
/// Opens a file containing a private key in write mode.
#[cfg(unix)]
fn open_priv_key_file<P>(path: P) -> Result<fs::File, IoError>
where P: AsRef<Path>
{
use std::os::unix::fs::OpenOptionsExt;
fs::OpenOptions::new()
.write(true)
.create_new(true)
.mode(256 | 128) // 0o600 in decimal
.open(path)
}
/// Opens a file containing a private key in write mode.
#[cfg(not(unix))]
fn open_priv_key_file<P>(path: P) -> Result<fs::File, IoError>
where P: AsRef<Path>
{
fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(path)
}
#[cfg(test)]
mod tests {
use libp2p::core::PublicKey;
use network_state::NetworkState;
#[test]
fn refuse_disabled_peer() {
let state = NetworkState::new(&Default::default()).unwrap();
let example_peer = PublicKey::Rsa(vec![1, 2, 3, 4]).into_peer_id();
let who = state.assign_node_index(&example_peer).unwrap();
state.ban_peer(who, "Just a test");
assert!(state.assign_node_index(&example_peer).is_err());
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,115 @@
// Copyright 2018 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::{Async, future, Future, Poll, stream, Stream, sync::mpsc};
use std::io::{Error as IoError, ErrorKind as IoErrorKind};
use std::marker::PhantomData;
use std::time::{Duration, Instant};
use tokio_timer::{self, Delay};
/// Builds the timeouts system.
///
/// The `timeouts_rx` should be a stream receiving newly-created timeout
/// requests. Returns a stream that produces items as their timeout elapses.
/// `T` can be anything you want, as it is transparently passed from the input
/// to the output. Timeouts continue to fire forever, as there is no way to
/// unregister them.
pub fn build_timeouts_stream<'a, T>(
timeouts_rx: mpsc::UnboundedReceiver<(Duration, T)>
) -> Box<Stream<Item = T, Error = IoError> + 'a>
where T: Clone + 'a {
let next_timeout = next_in_timeouts_stream(timeouts_rx);
// The `unfold` function is essentially a loop turned into a stream. The
// first parameter is the initial state, and the closure returns the new
// state and an item.
let stream = stream::unfold(vec![future::Either::A(next_timeout)], move |timeouts| {
// `timeouts` is a `Vec` of futures that produce an `Out`.
// `select_ok` panics if `timeouts` is empty anyway.
if timeouts.is_empty() {
return None
}
Some(future::select_ok(timeouts.into_iter())
.and_then(move |(item, mut timeouts)|
match item {
Out::NewTimeout((Some((duration, item)), next_timeouts)) => {
// Received a new timeout request on the channel.
let next_timeout = next_in_timeouts_stream(next_timeouts);
let timeout = Delay::new(Instant::now() + duration);
let timeout = TimeoutWrapper(timeout, duration, Some(item), PhantomData);
timeouts.push(future::Either::B(timeout));
timeouts.push(future::Either::A(next_timeout));
Ok((None, timeouts))
},
Out::NewTimeout((None, _)) =>
// The channel has been closed.
Ok((None, timeouts)),
Out::Timeout(duration, item) => {
// A timeout has happened.
let returned = item.clone();
let timeout = Delay::new(Instant::now() + duration);
let timeout = TimeoutWrapper(timeout, duration, Some(item), PhantomData);
timeouts.push(future::Either::B(timeout));
Ok((Some(returned), timeouts))
},
}
)
)
}).filter_map(|item| item);
// Note that we use a `Box` in order to speed up compilation time.
Box::new(stream) as Box<Stream<Item = _, Error = _>>
}
/// Local enum representing the output of the selection.
enum Out<A, B> {
NewTimeout(A),
Timeout(Duration, B),
}
/// Convenience function that calls `.into_future()` on the timeouts stream,
/// and applies some modifiers.
/// This function is necessary. Otherwise if we copy-paste its content we run
/// into errors because the type of the copy-pasted closures differs.
fn next_in_timeouts_stream<T, B>(
stream: mpsc::UnboundedReceiver<T>
) -> impl Future<Item = Out<(Option<T>, mpsc::UnboundedReceiver<T>), B>, Error = IoError> {
stream
.into_future()
.map(Out::NewTimeout)
.map_err(|_| unreachable!("an UnboundedReceiver can never error"))
}
/// Does the equivalent to `future.map(move |()| (duration, item)).map_err(|err| to_io_err(err))`.
struct TimeoutWrapper<A, F, T>(F, Duration, Option<T>, PhantomData<A>);
impl<A, F, T> Future for TimeoutWrapper<A, F, T>
where F: Future<Item = (), Error = tokio_timer::Error> {
type Item = Out<A, T>;
type Error = IoError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.0.poll() {
Ok(Async::Ready(())) => (),
Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(err) => return Err(IoError::new(IoErrorKind::Other, err.to_string())),
}
let out = Out::Timeout(self.1, self.2.take().expect("poll() called again after success"));
Ok(Async::Ready(out))
}
}
@@ -0,0 +1,655 @@
// Copyright 2018 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 fnv::FnvHashMap;
use parking_lot::Mutex;
use libp2p::{Multiaddr, PeerId};
use serde_json;
use std::{cmp, fs};
use std::io::{Read, Cursor, Error as IoError, ErrorKind as IoErrorKind, Write, BufReader, BufWriter};
use std::path::{Path, PathBuf};
use std::time::{Duration, Instant, SystemTime};
/// For each address we're connected to, a period of this duration increases the score by 1.
const CONNEC_DURATION_PER_SCORE: Duration = Duration::from_secs(10);
/// Maximum value for the score.
const MAX_SCORE: u32 = 100;
/// When we successfully connect to a node, raises its score to the given minimum value.
const CONNECTED_MINIMUM_SCORE: u32 = 20;
/// Initial score that a node discovered through Kademlia receives, where we have a hint that the
/// node is reachable.
const KADEMLIA_DISCOVERY_INITIAL_SCORE_CONNECTABLE: u32 = 15;
/// Initial score that a node discovered through Kademlia receives, without any hint.
const KADEMLIA_DISCOVERY_INITIAL_SCORE: u32 = 10;
/// Score adjustement when we fail to connect to an address.
const SCORE_DIFF_ON_FAILED_TO_CONNECT: i32 = -1;
/// Default time-to-live for addresses discovered through Kademlia.
/// After this time has elapsed and no connection has succeeded, the address will be removed.
const KADEMLIA_DISCOVERY_EXPIRATION: Duration = Duration::from_secs(2 * 3600);
/// After a successful connection, the TTL is set to a minimum at this amount.
const EXPIRATION_PUSH_BACK_CONNEC: Duration = Duration::from_secs(2 * 3600);
/// Initial score that a bootstrap node receives when registered.
const BOOTSTRAP_NODE_SCORE: u32 = 100;
/// Time to live of a boostrap node. This only applies if you start the node later *without*
/// that bootstrap node configured anymore.
const BOOTSTRAP_NODE_EXPIRATION: Duration = Duration::from_secs(24 * 3600);
/// The first time we fail to connect to an address, wait this duration before trying again.
const FIRST_CONNECT_FAIL_BACKOFF: Duration = Duration::from_secs(2);
/// Every time we fail to connect to an address, multiply the backoff by this constant.
const FAIL_BACKOFF_MULTIPLIER: u32 = 2;
/// We need a maximum value for the backoff, overwise we risk an overflow.
const MAX_BACKOFF: Duration = Duration::from_secs(30 * 60);
// TODO: should be merged with the Kademlia k-buckets
/// Stores information about the topology of the network.
#[derive(Debug)]
pub struct NetTopology {
store: FnvHashMap<PeerId, PeerInfo>,
cache_path: Option<PathBuf>,
}
impl Default for NetTopology {
#[inline]
fn default() -> NetTopology {
NetTopology::memory()
}
}
impl NetTopology {
/// Initializes a new `NetTopology` that isn't tied to any file.
///
/// `flush_to_disk()` will be a no-op.
#[inline]
pub fn memory() -> NetTopology {
NetTopology {
store: Default::default(),
cache_path: None,
}
}
/// Builds a `NetTopology` that will use `path` as a cache.
///
/// This function tries to load a known topology from the file. If the file doesn't exist
/// or contains garbage data, the execution still continues.
///
/// Calling `flush_to_disk()` in the future writes to the given path.
pub fn from_file<P: AsRef<Path>>(path: P) -> NetTopology {
let path = path.as_ref();
debug!(target: "sub-libp2p", "Initializing peer store for JSON file {:?}", path);
NetTopology {
store: try_load(path),
cache_path: Some(path.to_owned()),
}
}
/// Writes the topology into the path passed to `from_file`.
///
/// No-op if the object was created with `memory()`.
pub fn flush_to_disk(&self) -> Result<(), IoError> {
let path = match self.cache_path {
Some(ref p) => p,
None => return Ok(())
};
let file = fs::File::create(path)?;
// TODO: the capacity of the BufWriter is kind of arbitrary ; decide better
serialize(BufWriter::with_capacity(1024 * 1024, file), &self.store)
}
/// Perform a cleanup pass, removing all obsolete addresses and peers.
///
/// This should be done from time to time.
pub fn cleanup(&mut self) {
let now_systime = SystemTime::now();
self.store.retain(|_, peer| {
peer.addrs.retain(|a| {
a.expires > now_systime || a.is_connected()
});
!peer.addrs.is_empty()
});
}
/// Returns the known potential addresses of a peer, ordered by score.
///
/// The boolean associated to each address indicates whether we're connected to it.
// TODO: filter out backed off ones?
pub fn addrs_of_peer(&self, peer: &PeerId) -> impl Iterator<Item = (&Multiaddr, bool)> {
let peer = if let Some(peer) = self.store.get(peer) {
peer
} else {
// TODO: use an EitherIterator or something
return Vec::new().into_iter();
};
let now = SystemTime::now();
let mut list = peer.addrs.iter().filter_map(move |addr| {
let (score, connected) = addr.score_and_is_connected();
if (addr.expires >= now && score > 0) || connected {
Some((score, connected, &addr.addr))
} else {
None
}
}).collect::<Vec<_>>();
list.sort_by(|a, b| a.0.cmp(&b.0));
// TODO: meh, optimize
let l = list.into_iter().map(|(_, connec, addr)| (addr, connec)).collect::<Vec<_>>();
l.into_iter()
}
/// Returns a list of all the known addresses of peers, ordered by the
/// order in which we should attempt to connect to them.
///
/// Because of expiration and back-off mechanisms, this list can grow
/// by itself over time. The `Instant` that is returned corresponds to
/// the earlier known time when a new entry will be added automatically to
/// the list.
pub fn addrs_to_attempt(&self) -> (impl Iterator<Item = (&PeerId, &Multiaddr)>, Instant) {
// TODO: optimize
let now = Instant::now();
let now_systime = SystemTime::now();
let mut instant = now + Duration::from_secs(3600);
let mut addrs_out = Vec::new();
for (peer, info) in &self.store {
for addr in &info.addrs {
let (score, is_connected) = addr.score_and_is_connected();
if score == 0 || addr.expires < now_systime {
continue;
}
if !is_connected && addr.back_off_until > now {
instant = cmp::min(instant, addr.back_off_until);
continue;
}
addrs_out.push(((peer, &addr.addr), score));
}
}
addrs_out.sort_by(|a, b| b.1.cmp(&a.1));
(addrs_out.into_iter().map(|a| a.0), instant)
}
/// Adds an address corresponding to a boostrap node.
///
/// We assume that the address is valid, so its score starts very high.
pub fn add_bootstrap_addr(&mut self, peer: &PeerId, addr: Multiaddr) {
let now_systime = SystemTime::now();
let now = Instant::now();
let peer = peer_access(&mut self.store, peer);
let mut found = false;
peer.addrs.retain(|a| {
if a.expires < now_systime && !a.is_connected() {
return false;
}
if a.addr == addr {
found = true;
}
true
});
if !found {
peer.addrs.push(Addr {
addr,
expires: now_systime + BOOTSTRAP_NODE_EXPIRATION,
back_off_until: now,
next_back_off: FIRST_CONNECT_FAIL_BACKOFF,
score: Mutex::new(AddrScore {
connected_since: None,
score: BOOTSTRAP_NODE_SCORE,
latest_score_update: now,
}),
});
}
}
/// Adds an address discovered through the Kademlia DHT.
///
/// This address is not necessarily valid and should expire after a TTL.
///
/// If `connectable` is true, that means we have some sort of hint that this node can
/// be reached.
pub fn add_kademlia_discovered_addr(
&mut self,
peer_id: &PeerId,
addr: Multiaddr,
connectable: bool
) {
let now_systime = SystemTime::now();
let now = Instant::now();
let peer = peer_access(&mut self.store, peer_id);
let mut found = false;
peer.addrs.retain(|a| {
if a.expires < now_systime && !a.is_connected() {
return false;
}
if a.addr == addr {
found = true;
}
true
});
if !found {
trace!(
target: "sub-libp2p",
"Peer store: adding address {} for {:?} (connectable hint: {:?})",
addr,
peer_id,
connectable
);
let initial_score = if connectable {
KADEMLIA_DISCOVERY_INITIAL_SCORE_CONNECTABLE
} else {
KADEMLIA_DISCOVERY_INITIAL_SCORE
};
peer.addrs.push(Addr {
addr,
expires: now_systime + KADEMLIA_DISCOVERY_EXPIRATION,
back_off_until: now,
next_back_off: FIRST_CONNECT_FAIL_BACKOFF,
score: Mutex::new(AddrScore {
connected_since: None,
score: initial_score,
latest_score_update: now,
}),
});
}
}
/// Indicates the peer store that we're connected to this given address.
///
/// This increases the score of the address that we connected to. Since we assume that only
/// one peer can be reached with any specific address, we also remove all addresses from other
/// peers that match the one we connected to.
pub fn report_connected(&mut self, addr: &Multiaddr, peer: &PeerId) {
let now = Instant::now();
// Just making sure that we have an entry for this peer in `store`, but don't use it.
let _ = peer_access(&mut self.store, peer);
for (peer_in_store, info_in_store) in self.store.iter_mut() {
if peer == peer_in_store {
if let Some(addr) = info_in_store.addrs.iter_mut().find(|a| &a.addr == addr) {
addr.connected_now(CONNECTED_MINIMUM_SCORE);
addr.back_off_until = now;
addr.next_back_off = FIRST_CONNECT_FAIL_BACKOFF;
continue;
}
// TODO: a else block would be better, but we get borrowck errors
info_in_store.addrs.push(Addr {
addr: addr.clone(),
expires: SystemTime::now() + EXPIRATION_PUSH_BACK_CONNEC,
back_off_until: now,
next_back_off: FIRST_CONNECT_FAIL_BACKOFF,
score: Mutex::new(AddrScore {
connected_since: Some(now),
latest_score_update: now,
score: CONNECTED_MINIMUM_SCORE,
}),
});
} else {
// Set the score to 0 for any address that matches the one we connected to.
for addr_in_store in &mut info_in_store.addrs {
if &addr_in_store.addr == addr {
addr_in_store.adjust_score(-(MAX_SCORE as i32));
}
}
}
}
}
/// Indicates the peer store that we're disconnected from an address.
///
/// There's no need to indicate a peer ID, as each address can only have one peer ID.
/// If we were indeed connected to this addr, then we can find out which peer ID it is.
pub fn report_disconnected(&mut self, addr: &Multiaddr, reason: DisconnectReason) {
let score_diff = match reason {
DisconnectReason::ClosedGracefully => -1,
};
for info in self.store.values_mut() {
for a in info.addrs.iter_mut() {
if &a.addr == addr {
a.disconnected_now(score_diff);
a.back_off_until = Instant::now() + a.next_back_off;
a.next_back_off = cmp::min(a.next_back_off * FAIL_BACKOFF_MULTIPLIER, MAX_BACKOFF);
let expires_push_back = SystemTime::now() + EXPIRATION_PUSH_BACK_CONNEC;
if a.expires < expires_push_back {
a.expires = expires_push_back;
}
return;
}
}
}
}
/// Indicates the peer store that we failed to connect to an address.
///
/// We don't care about which peer is supposed to be behind that address. If we failed to dial
/// it for a specific peer, we would also fail to dial it for all peers that have this
/// address.
pub fn report_failed_to_connect(&mut self, addr: &Multiaddr) {
for info in self.store.values_mut() {
for a in info.addrs.iter_mut() {
if &a.addr == addr {
a.adjust_score(SCORE_DIFF_ON_FAILED_TO_CONNECT);
a.back_off_until = Instant::now() + a.next_back_off;
a.next_back_off = cmp::min(a.next_back_off * FAIL_BACKOFF_MULTIPLIER, MAX_BACKOFF);
}
}
}
}
}
/// Reason why we disconnected from a peer.
pub enum DisconnectReason {
/// The disconnection was graceful.
ClosedGracefully,
}
fn peer_access<'a>(store: &'a mut FnvHashMap<PeerId, PeerInfo>, peer: &PeerId) -> &'a mut PeerInfo {
// TODO: should be optimizable if HashMap gets a better API
store.entry(peer.clone()).or_insert_with(Default::default)
}
#[derive(Debug, Clone, Default)]
struct PeerInfo {
/// Addresses of that peer.
addrs: Vec<Addr>,
}
#[derive(Debug)]
struct Addr {
/// The multiaddress.
addr: Multiaddr,
/// When the address expires.
expires: SystemTime,
next_back_off: Duration,
/// Don't try to connect to this node until `Instant`.
back_off_until: Instant,
score: Mutex<AddrScore>,
}
impl Clone for Addr {
fn clone(&self) -> Addr {
Addr {
addr: self.addr.clone(),
expires: self.expires.clone(),
next_back_off: self.next_back_off.clone(),
back_off_until: self.back_off_until.clone(),
score: Mutex::new(self.score.lock().clone()),
}
}
}
#[derive(Debug, Clone)]
struct AddrScore {
/// If connected, contains the moment when we connected. `None` if we're not connected.
connected_since: Option<Instant>,
/// Score of this address. Potentially needs to be updated based on `latest_score_update`.
score: u32,
/// When we last updated the score.
latest_score_update: Instant,
}
impl Addr {
/// Sets the addr to connected. If the score is lower than the given value, raises it to this
/// value.
fn connected_now(&self, raise_to_min: u32) {
let mut score = self.score.lock();
let now = Instant::now();
Addr::flush(&mut score, now);
score.connected_since = Some(now);
if score.score < raise_to_min {
score.score = raise_to_min;
}
}
/// Applies a modification to the score.
fn adjust_score(&self, score_diff: i32) {
let mut score = self.score.lock();
Addr::flush(&mut score, Instant::now());
if score_diff >= 0 {
score.score = cmp::min(MAX_SCORE, score.score + score_diff as u32);
} else {
score.score = score.score.saturating_sub(-score_diff as u32);
}
}
/// Sets the addr to disconnected and applies a modification to the score.
fn disconnected_now(&self, score_diff: i32) {
let mut score = self.score.lock();
Addr::flush(&mut score, Instant::now());
score.connected_since = None;
if score_diff >= 0 {
score.score = cmp::min(MAX_SCORE, score.score + score_diff as u32);
} else {
score.score = score.score.saturating_sub(-score_diff as u32);
}
}
/// Returns true if we are connected to this addr.
fn is_connected(&self) -> bool {
let score = self.score.lock();
score.connected_since.is_some()
}
/// Returns the score, and true if we are connected to this addr.
fn score_and_is_connected(&self) -> (u32, bool) {
let mut score = self.score.lock();
Addr::flush(&mut score, Instant::now());
let is_connected = score.connected_since.is_some();
(score.score, is_connected)
}
/// Updates `score` and `latest_score_update`, and returns the score.
fn score(&self) -> u32 {
let mut score = self.score.lock();
Addr::flush(&mut score, Instant::now());
score.score
}
fn flush(score: &mut AddrScore, now: Instant) {
if let Some(connected_since) = score.connected_since {
let potential_score: u32 = div_dur_with_dur(now - connected_since, CONNEC_DURATION_PER_SCORE);
// We flush when we connect to an address.
debug_assert!(score.latest_score_update >= connected_since);
let effective_score: u32 =
div_dur_with_dur(score.latest_score_update - connected_since, CONNEC_DURATION_PER_SCORE);
let to_add = potential_score.saturating_sub(effective_score);
score.score = cmp::min(MAX_SCORE, score.score + to_add);
}
score.latest_score_update = now;
}
}
/// Divides a `Duration` with a `Duration`. This exists in the stdlib but isn't stable yet.
// TODO: remove this function once stable
fn div_dur_with_dur(a: Duration, b: Duration) -> u32 {
let a_ms = a.as_secs() * 1_000_000 + (a.subsec_nanos() / 1_000) as u64;
let b_ms = b.as_secs() * 1_000_000 + (b.subsec_nanos() / 1_000) as u64;
(a_ms / b_ms) as u32
}
/// Serialized version of a `PeerInfo`. Suitable for storage in the cache file.
#[derive(Debug, Clone, Serialize, Deserialize)]
struct SerializedPeerInfo {
addrs: Vec<SerializedAddr>,
}
/// Serialized version of an `Addr`. Suitable for storage in the cache file.
#[derive(Debug, Clone, Serialize, Deserialize)]
struct SerializedAddr {
addr: String,
expires: SystemTime,
score: u32,
}
impl<'a> From<&'a Addr> for SerializedAddr {
fn from(addr: &'a Addr) -> SerializedAddr {
SerializedAddr {
addr: addr.addr.to_string(),
expires: addr.expires,
score: addr.score(),
}
}
}
/// Attempts to load storage from a file.
/// Deletes the file and returns an empty map if the file doesn't exist, cannot be opened
/// or is corrupted.
fn try_load(path: impl AsRef<Path>) -> FnvHashMap<PeerId, PeerInfo> {
let path = path.as_ref();
if !path.exists() {
debug!(target: "sub-libp2p", "Peer storage file {:?} doesn't exist", path);
return Default::default()
}
let mut file = match fs::File::open(path) {
// TODO: the capacity of the BufReader is kind of arbitrary ; decide better
Ok(f) => BufReader::with_capacity(1024 * 1024, f),
Err(err) => {
warn!(target: "sub-libp2p", "Failed to open peer storage file: {:?}", err);
info!(target: "sub-libp2p", "Deleting peer storage file {:?}", path);
let _ = fs::remove_file(path);
return Default::default()
}
};
// We want to support empty files (and treat them as an empty recordset). Unfortunately
// `serde_json` will always produce an error if we do this ("unexpected EOF at line 0
// column 0"). Therefore we start by reading one byte from the file in order to check
// for EOF.
let mut first_byte = [0];
let num_read = match file.read(&mut first_byte) {
Ok(f) => f,
Err(err) => {
// TODO: DRY
warn!(target: "sub-libp2p", "Failed to read peer storage file: {:?}", err);
info!(target: "sub-libp2p", "Deleting peer storage file {:?}", path);
let _ = fs::remove_file(path);
return Default::default()
}
};
if num_read == 0 {
// File is empty.
debug!(target: "sub-libp2p", "Peer storage file {:?} is empty", path);
Default::default()
} else {
let data = Cursor::new(first_byte).chain(file);
match serde_json::from_reader::<_, serde_json::Value>(data) {
Ok(serde_json::Value::Null) => Default::default(),
Ok(serde_json::Value::Object(map)) => deserialize_tolerant(map.into_iter()),
Ok(_) | Err(_) => {
// The `Ok(_)` case means that the file doesn't contain a map.
let _ = fs::remove_file(path);
Default::default()
},
}
}
}
/// Attempts to turn a deserialized version of the storage into the final version.
///
/// Skips entries that are invalid.
fn deserialize_tolerant(
iter: impl Iterator<Item = (String, serde_json::Value)>
) -> FnvHashMap<PeerId, PeerInfo> {
let now = Instant::now();
let now_systime = SystemTime::now();
let mut out = FnvHashMap::default();
for (peer, info) in iter {
let peer: PeerId = match peer.parse() {
Ok(p) => p,
Err(_) => continue,
};
let info: SerializedPeerInfo = match serde_json::from_value(info) {
Ok(i) => i,
Err(_) => continue,
};
let mut addrs = Vec::with_capacity(info.addrs.len());
for addr in info.addrs {
let multiaddr = match addr.addr.parse() {
Ok(a) => a,
Err(_) => continue,
};
if addr.expires < now_systime {
continue
}
addrs.push(Addr {
addr: multiaddr,
expires: addr.expires,
next_back_off: FIRST_CONNECT_FAIL_BACKOFF,
back_off_until: now,
score: Mutex::new(AddrScore {
connected_since: None,
score: addr.score,
latest_score_update: now,
}),
});
}
if addrs.is_empty() {
continue;
}
out.insert(peer, PeerInfo { addrs });
}
out
}
/// Attempts to turn a deserialized version of the storage into the final version.
///
/// Skips entries that are invalid or expired.
fn serialize<W: Write>(out: W, map: &FnvHashMap<PeerId, PeerInfo>) -> Result<(), IoError> {
let now = SystemTime::now();
let array: FnvHashMap<_, _> = map.iter().filter_map(|(peer, info)| {
if info.addrs.is_empty() {
return None
}
let peer = peer.to_base58();
let info = SerializedPeerInfo {
addrs: info.addrs.iter()
.filter(|a| a.expires > now || a.is_connected())
.map(Into::into)
.collect(),
};
Some((peer, info))
}).collect();
serde_json::to_writer_pretty(out, &array)
.map_err(|err| IoError::new(IoErrorKind::Other, err))
}
+299
View File
@@ -0,0 +1,299 @@
// Copyright 2015-2018 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 std::fmt;
use std::cmp::Ordering;
use std::iter;
use std::net::Ipv4Addr;
use std::str;
use std::time::Duration;
use io::TimerToken;
use libp2p::{multiaddr::AddrComponent, Multiaddr};
use error::Error;
use ethkey::Secret;
use ethereum_types::H512;
/// Protocol handler level packet id
pub type PacketId = u8;
/// Protocol / handler id
pub type ProtocolId = [u8; 3];
/// Node public key
pub type NodeId = H512;
/// Local (temporary) peer session ID.
pub type NodeIndex = usize;
/// Shared session information
#[derive(Debug, Clone)]
pub struct SessionInfo {
/// Peer public key
pub id: Option<NodeId>,
/// Peer client ID
pub client_version: String,
/// Peer RLPx protocol version
pub protocol_version: u32,
/// Session protocol capabilities
pub capabilities: Vec<SessionCapabilityInfo>,
/// Peer protocol capabilities
pub peer_capabilities: Vec<PeerCapabilityInfo>,
/// Peer ping delay
pub ping: Option<Duration>,
/// True if this session was originated by us.
pub originated: bool,
/// Remote endpoint address of the session
pub remote_address: String,
/// Local endpoint address of the session
pub local_address: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PeerCapabilityInfo {
pub protocol: ProtocolId,
pub version: u8,
}
impl ToString for PeerCapabilityInfo {
fn to_string(&self) -> String {
format!("{}/{}", str::from_utf8(&self.protocol[..]).unwrap_or("???"), self.version)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SessionCapabilityInfo {
pub protocol: [u8; 3],
pub version: u8,
pub packet_count: u8,
pub id_offset: u8,
}
impl PartialOrd for SessionCapabilityInfo {
fn partial_cmp(&self, other: &SessionCapabilityInfo) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for SessionCapabilityInfo {
fn cmp(&self, b: &SessionCapabilityInfo) -> Ordering {
// By protocol id first
if self.protocol != b.protocol {
return self.protocol.cmp(&b.protocol);
}
// By version
self.version.cmp(&b.version)
}
}
/// Network service configuration
#[derive(Debug, PartialEq, 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>,
/// Use provided node key instead of default
pub use_secret: Option<Secret>,
/// Minimum number of connected peers to maintain
pub min_peers: u32,
/// Maximum allowed number of peers
pub max_peers: u32,
/// List of reserved node addresses.
pub reserved_nodes: Vec<String>,
/// The non-reserved peer mode.
pub non_reserved_mode: NonReservedPeerMode,
/// Client identifier
pub client_version: String,
}
impl Default for NetworkConfiguration {
fn default() -> Self {
NetworkConfiguration::new()
}
}
impl NetworkConfiguration {
/// Create a new instance of default settings.
pub fn new() -> Self {
NetworkConfiguration {
config_path: None,
net_config_path: None,
listen_addresses: vec![
iter::once(AddrComponent::IP4(Ipv4Addr::new(0, 0, 0, 0)))
.chain(iter::once(AddrComponent::TCP(30333)))
.collect()
],
public_addresses: Vec::new(),
boot_nodes: Vec::new(),
use_secret: None,
min_peers: 25,
max_peers: 50,
reserved_nodes: Vec::new(),
non_reserved_mode: NonReservedPeerMode::Accept,
client_version: "Parity-network".into(),
}
}
/// 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(AddrComponent::IP4(Ipv4Addr::new(127, 0, 0, 1)))
.chain(iter::once(AddrComponent::TCP(0)))
.collect()
];
config
}
}
/// The severity of misbehaviour of a peer that is reported.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Severity<'a> {
/// Peer is timing out. Could be bad connectivity of overload of work on either of our sides.
Timeout,
/// Peer has been notably useless. E.g. unable to answer a request that we might reasonably consider
/// it could answer.
Useless(&'a str),
/// Peer has behaved in an invalid manner. This doesn't necessarily need to be Byzantine, but peer
/// must have taken concrete action in order to behave in such a way which is wantanly invalid.
Bad(&'a str),
}
impl<'a> fmt::Display for Severity<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self {
Severity::Timeout => write!(fmt, "Timeout"),
Severity::Useless(r) => write!(fmt, "Useless ({})", r),
Severity::Bad(r) => write!(fmt, "Bad ({})", r),
}
}
}
/// IO access point. This is passed to all IO handlers and provides an interface to the IO subsystem.
pub trait NetworkContext {
/// Send a packet over the network to another peer.
fn send(&self, peer: NodeIndex, packet_id: PacketId, data: Vec<u8>);
/// Send a packet over the network to another peer using specified protocol.
fn send_protocol(&self, protocol: ProtocolId, peer: NodeIndex, packet_id: PacketId, data: Vec<u8>);
/// Respond to a current network message. Panics if no there is no packet in the context. If the session is expired returns nothing.
fn respond(&self, packet_id: PacketId, data: Vec<u8>);
/// Report peer. Depending on the report, peer may be disconnected and possibly banned.
fn report_peer(&self, peer: NodeIndex, reason: Severity);
/// Check if the session is still active.
fn is_expired(&self) -> bool;
/// Register a new IO timer. 'IoHandler::timeout' will be called with the token.
fn register_timer(&self, token: TimerToken, delay: Duration) -> Result<(), Error>;
/// Returns peer identification string
fn peer_client_version(&self, peer: NodeIndex) -> String;
/// Returns information on p2p session
fn session_info(&self, peer: NodeIndex) -> Option<SessionInfo>;
/// Returns max version for a given protocol.
fn protocol_version(&self, protocol: ProtocolId, peer: NodeIndex) -> Option<u8>;
/// Returns this object's subprotocol name.
fn subprotocol_name(&self) -> ProtocolId;
}
impl<'a, T> NetworkContext for &'a T where T: ?Sized + NetworkContext {
fn send(&self, peer: NodeIndex, packet_id: PacketId, data: Vec<u8>) {
(**self).send(peer, packet_id, data)
}
fn send_protocol(&self, protocol: ProtocolId, peer: NodeIndex, packet_id: PacketId, data: Vec<u8>) {
(**self).send_protocol(protocol, peer, packet_id, data)
}
fn respond(&self, packet_id: PacketId, data: Vec<u8>) {
(**self).respond(packet_id, data)
}
fn report_peer(&self, peer: NodeIndex, reason: Severity) {
(**self).report_peer(peer, reason)
}
fn is_expired(&self) -> bool {
(**self).is_expired()
}
fn register_timer(&self, token: TimerToken, delay: Duration) -> Result<(), Error> {
(**self).register_timer(token, delay)
}
fn peer_client_version(&self, peer: NodeIndex) -> String {
(**self).peer_client_version(peer)
}
fn session_info(&self, peer: NodeIndex) -> Option<SessionInfo> {
(**self).session_info(peer)
}
fn protocol_version(&self, protocol: ProtocolId, peer: NodeIndex) -> Option<u8> {
(**self).protocol_version(protocol, peer)
}
fn subprotocol_name(&self) -> ProtocolId {
(**self).subprotocol_name()
}
}
/// Network IO protocol handler. This needs to be implemented for each new subprotocol.
/// All the handler function are called from within IO event loop.
/// `Message` is the type for message data.
pub trait NetworkProtocolHandler: Sync + Send {
/// Initialize the handler
fn initialize(&self, _io: &NetworkContext) {}
/// Called when new network packet received.
fn read(&self, io: &NetworkContext, peer: &NodeIndex, packet_id: u8, data: &[u8]);
/// Called when new peer is connected. Only called when peer supports the same protocol.
fn connected(&self, io: &NetworkContext, peer: &NodeIndex);
/// Called when a previously connected peer disconnects.
fn disconnected(&self, io: &NetworkContext, peer: &NodeIndex);
/// Timer function called after a timeout created with `NetworkContext::timeout`.
fn timeout(&self, _io: &NetworkContext, _timer: TimerToken) {}
}
/// Non-reserved peer modes.
#[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,
}
}
}
@@ -0,0 +1,50 @@
// Copyright 2018 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 libp2p::{self, PeerId, Transport, mplex, secio, yamux};
use libp2p::core::{either, upgrade, transport::BoxedMuxed};
use libp2p::transport_timeout::TransportTimeout;
use std::time::Duration;
use std::usize;
use tokio_io::{AsyncRead, AsyncWrite};
/// Builds the transport that serves as a common ground for all connections.
pub fn build_transport(
local_private_key: secio::SecioKeyPair
) -> BoxedMuxed<(PeerId, impl AsyncRead + AsyncWrite)> {
let mut mplex_config = mplex::MplexConfig::new();
mplex_config.max_buffer_len_behaviour(mplex::MaxBufferBehaviour::Block);
mplex_config.max_buffer_len(usize::MAX);
let base = libp2p::CommonTransport::new()
.with_upgrade(secio::SecioConfig {
key: local_private_key,
})
.and_then(move |out, endpoint, client_addr| {
let upgrade = upgrade::or(
upgrade::map(mplex_config, either::EitherOutput::First),
upgrade::map(yamux::Config::default(), either::EitherOutput::Second),
);
let key = out.remote_key;
let upgrade = upgrade::map(upgrade, move |muxer| (key, muxer));
upgrade::apply(out.stream, upgrade, endpoint, client_addr)
})
.into_connection_reuse()
.map(|(key, substream), _| (key.into_peer_id(), substream));
TransportTimeout::new(base, Duration::from_secs(20))
.boxed_muxed()
}