mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 17:31:05 +00:00
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:
committed by
Arkadiy Paronyan
parent
8fe5aa4c81
commit
1e01162505
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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(_));
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
Reference in New Issue
Block a user