mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-17 03:42:46 +00:00
v0.11.1 Network update (#989)
* Minor log bumps * Initial building of extended RPC methods * Wire in extended RPC methods * Merge initial peer management template * Add a PeerDB and give the peer manager some basic functions * Initial connection of peer manager * Add peer manager to lighthouse * Connect peer manager with new RPC methods * Correct tests and metadata RPC Co-authored-by: Diva <divma@protonmail.com>
This commit is contained in:
287
beacon_node/eth2-libp2p/src/peer_manager/mod.rs
Normal file
287
beacon_node/eth2-libp2p/src/peer_manager/mod.rs
Normal file
@@ -0,0 +1,287 @@
|
||||
//! Implementation of a Lighthouse's peer management system.
|
||||
|
||||
pub use self::peerdb::*;
|
||||
use crate::rpc::MetaData;
|
||||
use crate::{NetworkGlobals, PeerId};
|
||||
use futures::prelude::*;
|
||||
use futures::Stream;
|
||||
use hashmap_delay::HashSetDelay;
|
||||
use slog::{crit, debug, error, warn};
|
||||
use smallvec::SmallVec;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use types::EthSpec;
|
||||
|
||||
mod peer_info;
|
||||
mod peerdb;
|
||||
|
||||
pub use peer_info::PeerInfo;
|
||||
/// The minimum reputation before a peer is disconnected.
|
||||
// Most likely this needs tweaking
|
||||
const MINIMUM_REPUTATION_BEFORE_BAN: Rep = 20;
|
||||
/// The time in seconds between re-status's peers.
|
||||
const STATUS_INTERVAL: u64 = 300;
|
||||
/// The time in seconds between PING events. We do not send a ping if the other peer as PING'd us within
|
||||
/// this time frame (Seconds)
|
||||
const PING_INTERVAL: u64 = 30;
|
||||
|
||||
/// The main struct that handles peer's reputation and connection status.
|
||||
pub struct PeerManager<TSpec: EthSpec> {
|
||||
/// Storage of network globals to access the PeerDB.
|
||||
network_globals: Arc<NetworkGlobals<TSpec>>,
|
||||
/// A queue of events that the `PeerManager` is waiting to produce.
|
||||
events: SmallVec<[PeerManagerEvent; 5]>,
|
||||
/// A collection of peers awaiting to be Ping'd.
|
||||
ping_peers: HashSetDelay<PeerId>,
|
||||
/// A collection of peers awaiting to be Status'd.
|
||||
status_peers: HashSetDelay<PeerId>,
|
||||
/// Last updated moment.
|
||||
last_updated: Instant,
|
||||
/// The logger associated with the `PeerManager`.
|
||||
log: slog::Logger,
|
||||
}
|
||||
|
||||
/// A collection of actions a peer can perform which will adjust its reputation
|
||||
/// Each variant has an associated reputation change.
|
||||
pub enum PeerAction {
|
||||
/// The peer timed out on an RPC request/response.
|
||||
TimedOut = -10,
|
||||
/// The peer sent and invalid request/response or encoding.
|
||||
InvalidMessage = -20,
|
||||
/// The peer sent something objectively malicious.
|
||||
Malicious = -50,
|
||||
/// Received an expected message.
|
||||
ValidMessage = 20,
|
||||
/// Peer disconnected.
|
||||
Disconnected = -30,
|
||||
}
|
||||
|
||||
/// The events that the PeerManager outputs (requests).
|
||||
pub enum PeerManagerEvent {
|
||||
/// Sends a STATUS to a peer.
|
||||
Status(PeerId),
|
||||
/// Sends a PING to a peer.
|
||||
Ping(PeerId),
|
||||
/// Request METADATA from a peer.
|
||||
MetaData(PeerId),
|
||||
/// The peer should be disconnected.
|
||||
DisconnectPeer(PeerId),
|
||||
/// The peer should be disconnected and banned.
|
||||
BanPeer(PeerId),
|
||||
}
|
||||
|
||||
impl<TSpec: EthSpec> PeerManager<TSpec> {
|
||||
pub fn new(network_globals: Arc<NetworkGlobals<TSpec>>, log: &slog::Logger) -> Self {
|
||||
PeerManager {
|
||||
network_globals,
|
||||
events: SmallVec::new(),
|
||||
last_updated: Instant::now(),
|
||||
ping_peers: HashSetDelay::new(Duration::from_secs(PING_INTERVAL)),
|
||||
status_peers: HashSetDelay::new(Duration::from_secs(STATUS_INTERVAL)),
|
||||
log: log.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/* Public accessible functions */
|
||||
|
||||
/// A ping request has been received.
|
||||
// NOTE: The behaviour responds with a PONG automatically
|
||||
pub fn ping_request(&mut self, peer_id: &PeerId, seq: u64) {
|
||||
if let Some(peer_info) = self.network_globals.peers.read().peer_info(peer_id) {
|
||||
// received a ping
|
||||
// reset the to-ping timer for this peer
|
||||
self.ping_peers.insert(peer_id.clone());
|
||||
|
||||
// if the sequence number is unknown send update the meta data of the peer.
|
||||
if let Some(meta_data) = &peer_info.meta_data {
|
||||
if meta_data.seq_number < seq {
|
||||
debug!(self.log, "Requesting new metadata from peer"; "peer_id" => format!("{}", peer_id), "known_seq_no" => meta_data.seq_number, "ping_seq_no" => seq);
|
||||
self.events
|
||||
.push(PeerManagerEvent::MetaData(peer_id.clone()));
|
||||
}
|
||||
} else {
|
||||
// if we don't know the meta-data, request it
|
||||
debug!(self.log, "Requesting first metadata from peer"; "peer_id" => format!("{}", peer_id));
|
||||
self.events
|
||||
.push(PeerManagerEvent::MetaData(peer_id.clone()));
|
||||
}
|
||||
} else {
|
||||
crit!(self.log, "Received a PING from an unknown peer"; "peer_id" => format!("{}", peer_id));
|
||||
}
|
||||
}
|
||||
|
||||
/// A PONG has been returned from a peer.
|
||||
pub fn pong_response(&mut self, peer_id: &PeerId, seq: u64) {
|
||||
if let Some(peer_info) = self.network_globals.peers.read().peer_info(peer_id) {
|
||||
// received a pong
|
||||
|
||||
// if the sequence number is unknown send update the meta data of the peer.
|
||||
if let Some(meta_data) = &peer_info.meta_data {
|
||||
if meta_data.seq_number < seq {
|
||||
debug!(self.log, "Requesting new metadata from peer"; "peer_id" => format!("{}", peer_id), "known_seq_no" => meta_data.seq_number, "pong_seq_no" => seq);
|
||||
self.events
|
||||
.push(PeerManagerEvent::MetaData(peer_id.clone()));
|
||||
}
|
||||
} else {
|
||||
// if we don't know the meta-data, request it
|
||||
debug!(self.log, "Requesting first metadata from peer"; "peer_id" => format!("{}", peer_id));
|
||||
self.events
|
||||
.push(PeerManagerEvent::MetaData(peer_id.clone()));
|
||||
}
|
||||
} else {
|
||||
crit!(self.log, "Received a PONG from an unknown peer"; "peer_id" => format!("{}", peer_id));
|
||||
}
|
||||
}
|
||||
|
||||
/// Received a metadata response from a peer.
|
||||
pub fn meta_data_response(&mut self, peer_id: &PeerId, meta_data: MetaData<TSpec>) {
|
||||
if let Some(peer_info) = self.network_globals.peers.write().peer_info_mut(peer_id) {
|
||||
if let Some(known_meta_data) = &peer_info.meta_data {
|
||||
if known_meta_data.seq_number < meta_data.seq_number {
|
||||
debug!(self.log, "Updating peer's metadata"; "peer_id" => format!("{}", peer_id), "known_seq_no" => known_meta_data.seq_number, "new_seq_no" => meta_data.seq_number);
|
||||
} else {
|
||||
warn!(self.log, "Received old metadata"; "peer_id" => format!("{}", peer_id), "known_seq_no" => known_meta_data.seq_number, "new_seq_no" => meta_data.seq_number);
|
||||
}
|
||||
} else {
|
||||
// we have no meta-data for this peer, update
|
||||
debug!(self.log, "Obtained peer's metadata"; "peer_id" => format!("{}", peer_id), "new_seq_no" => meta_data.seq_number);
|
||||
peer_info.meta_data = Some(meta_data);
|
||||
}
|
||||
} else {
|
||||
crit!(self.log, "Received METADATA from an unknown peer"; "peer_id" => format!("{}", peer_id));
|
||||
}
|
||||
}
|
||||
|
||||
/// A STATUS message has been received from a peer. This resets the status timer.
|
||||
pub fn peer_statusd(&mut self, peer_id: &PeerId) {
|
||||
self.status_peers.insert(peer_id.clone());
|
||||
}
|
||||
|
||||
/// Checks the reputation of a peer and if it is too low, bans it and
|
||||
/// sends the corresponding event. Informs if it got banned
|
||||
fn gets_banned(&mut self, peer_id: &PeerId) -> bool {
|
||||
// if the peer was already banned don't inform again
|
||||
let mut peerdb = self.network_globals.peers.write();
|
||||
if peerdb.reputation(peer_id) < MINIMUM_REPUTATION_BEFORE_BAN
|
||||
&& !peerdb.connection_status(peer_id).is_banned()
|
||||
{
|
||||
peerdb.ban(peer_id);
|
||||
self.events.push(PeerManagerEvent::BanPeer(peer_id.clone()));
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Sets a peer as disconnected. If its reputation gets too low requests
|
||||
/// the peer to be banned and to be disconnected otherwise
|
||||
pub fn disconnect(&mut self, peer_id: &PeerId) {
|
||||
self.update_reputations();
|
||||
{
|
||||
let mut peerdb = self.network_globals.peers.write();
|
||||
peerdb.disconnect(peer_id);
|
||||
peerdb.add_reputation(peer_id, PeerAction::Disconnected as Rep);
|
||||
}
|
||||
if !self.gets_banned(peer_id) {
|
||||
self.events
|
||||
.push(PeerManagerEvent::DisconnectPeer(peer_id.clone()));
|
||||
}
|
||||
|
||||
// remove the ping and status timer for the peer
|
||||
self.ping_peers.remove(peer_id);
|
||||
self.status_peers.remove(peer_id);
|
||||
}
|
||||
|
||||
/// Sets a peer as connected as long as their reputation allows it
|
||||
/// Informs if the peer was accepted
|
||||
pub fn connect_ingoing(&mut self, peer_id: &PeerId) -> bool {
|
||||
self.update_reputations();
|
||||
let mut peerdb = self.network_globals.peers.write();
|
||||
peerdb.new_peer(peer_id);
|
||||
if !peerdb.connection_status(peer_id).is_banned() {
|
||||
peerdb.connect_ingoing(peer_id);
|
||||
return true;
|
||||
}
|
||||
// start a ping and status timer for the peer
|
||||
self.ping_peers.insert(peer_id.clone());
|
||||
self.status_peers.insert(peer_id.clone());
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Sets a peer as connected as long as their reputation allows it
|
||||
/// Informs if the peer was accepted
|
||||
pub fn connect_outgoing(&mut self, peer_id: &PeerId) -> bool {
|
||||
self.update_reputations();
|
||||
let mut peerdb = self.network_globals.peers.write();
|
||||
peerdb.new_peer(peer_id);
|
||||
if !peerdb.connection_status(peer_id).is_banned() {
|
||||
peerdb.connect_outgoing(peer_id);
|
||||
return true;
|
||||
}
|
||||
// start a ping and status timer for the peer
|
||||
self.ping_peers.insert(peer_id.clone());
|
||||
self.status_peers.insert(peer_id.clone());
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Provides a given peer's reputation if it exists.
|
||||
pub fn get_peer_rep(&self, peer_id: &PeerId) -> Rep {
|
||||
self.network_globals.peers.read().reputation(peer_id)
|
||||
}
|
||||
|
||||
/// Updates the reputation of known peers according to their connection
|
||||
/// status and the time that has passed.
|
||||
pub fn update_reputations(&mut self) {
|
||||
let now = Instant::now();
|
||||
let elapsed = (now - self.last_updated).as_secs();
|
||||
// 0 seconds means now - last_updated < 0, but (most likely) not = 0.
|
||||
// In this case, do nothing (updating last_updated would propagate
|
||||
// rounding errors)
|
||||
if elapsed > 0 {
|
||||
self.last_updated = now;
|
||||
// TODO decide how reputations change with time. If they get too low
|
||||
// set the peers as banned
|
||||
}
|
||||
}
|
||||
|
||||
/// Reports a peer for some action.
|
||||
///
|
||||
/// If the peer doesn't exist, log a warning and insert defaults.
|
||||
pub fn report_peer(&mut self, peer_id: &PeerId, action: PeerAction) {
|
||||
self.update_reputations();
|
||||
self.network_globals
|
||||
.peers
|
||||
.write()
|
||||
.add_reputation(peer_id, action as Rep);
|
||||
self.update_reputations();
|
||||
}
|
||||
}
|
||||
|
||||
impl<TSpec: EthSpec> Stream for PeerManager<TSpec> {
|
||||
type Item = PeerManagerEvent;
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
// poll the timeouts for pings and status'
|
||||
while let Async::Ready(Some(peer_id)) = self.ping_peers.poll().map_err(|e| {
|
||||
error!(self.log, "Failed to check for peers to ping"; "error" => format!("{}",e));
|
||||
})? {
|
||||
self.events.push(PeerManagerEvent::Ping(peer_id));
|
||||
}
|
||||
|
||||
while let Async::Ready(Some(peer_id)) = self.ping_peers.poll().map_err(|e| {
|
||||
error!(self.log, "Failed to check for peers to status"; "error" => format!("{}",e));
|
||||
})? {
|
||||
self.events.push(PeerManagerEvent::Status(peer_id));
|
||||
}
|
||||
|
||||
if !self.events.is_empty() {
|
||||
return Ok(Async::Ready(Some(self.events.remove(0))));
|
||||
} else {
|
||||
self.events.shrink_to_fit();
|
||||
}
|
||||
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
188
beacon_node/eth2-libp2p/src/peer_manager/peer_info.rs
Normal file
188
beacon_node/eth2-libp2p/src/peer_manager/peer_info.rs
Normal file
@@ -0,0 +1,188 @@
|
||||
use super::peerdb::{Rep, DEFAULT_REPUTATION};
|
||||
use crate::rpc::MetaData;
|
||||
use std::time::Instant;
|
||||
use types::{EthSpec, SubnetId};
|
||||
use PeerConnectionStatus::*;
|
||||
|
||||
/// Information about a given connected peer.
|
||||
#[derive(Debug)]
|
||||
pub struct PeerInfo<T: EthSpec> {
|
||||
/// The connection status of the peer
|
||||
_status: PeerStatus,
|
||||
/// The peers reputation
|
||||
pub reputation: Rep,
|
||||
/// Client managing this peer
|
||||
_client: Client,
|
||||
/// Connection status of this peer
|
||||
pub connection_status: PeerConnectionStatus,
|
||||
/// The current syncing state of the peer. The state may be determined after it's initial
|
||||
/// connection.
|
||||
pub syncing_status: PeerSyncingStatus,
|
||||
/// The ENR subnet bitfield of the peer. This may be determined after it's initial
|
||||
/// connection.
|
||||
pub meta_data: Option<MetaData<T>>,
|
||||
}
|
||||
|
||||
impl<TSpec: EthSpec> Default for PeerInfo<TSpec> {
|
||||
fn default() -> PeerInfo<TSpec> {
|
||||
PeerInfo {
|
||||
reputation: DEFAULT_REPUTATION,
|
||||
_status: Default::default(),
|
||||
_client: Client {
|
||||
_client_name: "Unknown".into(),
|
||||
_version: vec![0],
|
||||
},
|
||||
connection_status: Default::default(),
|
||||
syncing_status: PeerSyncingStatus::Unknown,
|
||||
meta_data: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> PeerInfo<T> {
|
||||
/// Returns if the peer is subscribed to a given `SubnetId`
|
||||
pub fn on_subnet(&self, subnet_id: SubnetId) -> bool {
|
||||
if let Some(meta_data) = &self.meta_data {
|
||||
return meta_data
|
||||
.attnets
|
||||
.get(*subnet_id as usize)
|
||||
.unwrap_or_else(|_| false);
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PeerStatus {
|
||||
/// The peer is healthy
|
||||
Healthy,
|
||||
/// The peer is clogged. It has not been responding to requests on time
|
||||
Clogged,
|
||||
}
|
||||
|
||||
impl Default for PeerStatus {
|
||||
fn default() -> Self {
|
||||
PeerStatus::Healthy
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of the client managing a peer
|
||||
#[derive(Debug)]
|
||||
pub struct Client {
|
||||
/// The client's name (Ex: lighthouse, prism, nimbus, etc)
|
||||
_client_name: String,
|
||||
/// The client's version
|
||||
_version: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Connection Status of the peer
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PeerConnectionStatus {
|
||||
Connected {
|
||||
/// number of ingoing connections
|
||||
n_in: u8,
|
||||
/// number of outgoing connections
|
||||
n_out: u8,
|
||||
},
|
||||
Disconnected {
|
||||
/// last time the peer was connected or discovered
|
||||
since: Instant,
|
||||
},
|
||||
Banned {
|
||||
/// moment when the peer was banned
|
||||
since: Instant,
|
||||
},
|
||||
Unknown {
|
||||
/// time since we know of this peer
|
||||
since: Instant,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PeerSyncingStatus {
|
||||
/// At the current state as our node.
|
||||
Synced,
|
||||
/// The peer is further ahead than our node and useful for block downloads.
|
||||
Ahead,
|
||||
/// Is behind our current head and not useful for block downloads.
|
||||
Behind,
|
||||
/// Not currently known as a STATUS handshake has not occurred.
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl Default for PeerConnectionStatus {
|
||||
fn default() -> Self {
|
||||
PeerConnectionStatus::Unknown {
|
||||
since: Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PeerConnectionStatus {
|
||||
/// Checks if the status is connected
|
||||
pub fn is_connected(&self) -> bool {
|
||||
match self {
|
||||
PeerConnectionStatus::Connected { .. } => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the status is banned
|
||||
pub fn is_banned(&self) -> bool {
|
||||
match self {
|
||||
PeerConnectionStatus::Banned { .. } => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the status is disconnected
|
||||
pub fn is_disconnected(&self) -> bool {
|
||||
match self {
|
||||
Disconnected { .. } => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Modifies the status to Connected and increases the number of ingoing
|
||||
/// connections by one
|
||||
pub fn connect_ingoing(&mut self) {
|
||||
match self {
|
||||
Connected { n_in, .. } => *n_in += 1,
|
||||
Disconnected { .. } | Banned { .. } | Unknown { .. } => {
|
||||
*self = Connected { n_in: 1, n_out: 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Modifies the status to Connected and increases the number of outgoing
|
||||
/// connections by one
|
||||
pub fn connect_outgoing(&mut self) {
|
||||
match self {
|
||||
Connected { n_out, .. } => *n_out += 1,
|
||||
Disconnected { .. } | Banned { .. } | Unknown { .. } => {
|
||||
*self = Connected { n_in: 0, n_out: 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Modifies the status to Disconnected and sets the last seen instant to now
|
||||
pub fn disconnect(&mut self) {
|
||||
*self = Disconnected {
|
||||
since: Instant::now(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Modifies the status to Banned
|
||||
pub fn ban(&mut self) {
|
||||
*self = Banned {
|
||||
since: Instant::now(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn connections(&self) -> (u8, u8) {
|
||||
match self {
|
||||
Connected { n_in, n_out } => (*n_in, *n_out),
|
||||
_ => (0, 0),
|
||||
}
|
||||
}
|
||||
}
|
||||
427
beacon_node/eth2-libp2p/src/peer_manager/peerdb.rs
Normal file
427
beacon_node/eth2-libp2p/src/peer_manager/peerdb.rs
Normal file
@@ -0,0 +1,427 @@
|
||||
use super::peer_info::{PeerConnectionStatus, PeerInfo};
|
||||
use crate::rpc::methods::MetaData;
|
||||
use crate::PeerId;
|
||||
use slog::warn;
|
||||
use std::collections::HashMap;
|
||||
use types::{EthSpec, SubnetId};
|
||||
|
||||
/// A peer's reputation.
|
||||
pub type Rep = i32;
|
||||
|
||||
/// Max number of disconnected nodes to remember
|
||||
const MAX_DC_PEERS: usize = 30;
|
||||
/// The default starting reputation for an unknown peer.
|
||||
pub const DEFAULT_REPUTATION: Rep = 50;
|
||||
|
||||
/// Storage of known peers, their reputation and information
|
||||
pub struct PeerDB<TSpec: EthSpec> {
|
||||
/// The collection of known connected peers, their status and reputation
|
||||
peers: HashMap<PeerId, PeerInfo<TSpec>>,
|
||||
/// Tracking of number of disconnected nodes
|
||||
n_dc: usize,
|
||||
/// PeerDB's logger
|
||||
log: slog::Logger,
|
||||
}
|
||||
|
||||
impl<TSpec: EthSpec> PeerDB<TSpec> {
|
||||
pub fn new(log: &slog::Logger) -> Self {
|
||||
Self {
|
||||
log: log.clone(),
|
||||
n_dc: 0,
|
||||
peers: HashMap::new(),
|
||||
}
|
||||
}
|
||||
/// Gives the reputation of a peer, or DEFAULT_REPUTATION if it is unknown.
|
||||
pub fn reputation(&self, peer_id: &PeerId) -> Rep {
|
||||
self.peers
|
||||
.get(peer_id)
|
||||
.map_or(DEFAULT_REPUTATION, |info| info.reputation)
|
||||
}
|
||||
|
||||
/// Gives the ids of all known peers.
|
||||
pub fn peers(&self) -> impl Iterator<Item = &PeerId> {
|
||||
self.peers.keys()
|
||||
}
|
||||
|
||||
/// Returns a peer's info, if known.
|
||||
pub fn peer_info(&self, peer_id: &PeerId) -> Option<&PeerInfo<TSpec>> {
|
||||
self.peers.get(peer_id)
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to a peer's info if known.
|
||||
pub fn peer_info_mut(&mut self, peer_id: &PeerId) -> Option<&mut PeerInfo<TSpec>> {
|
||||
self.peers.get_mut(peer_id)
|
||||
}
|
||||
|
||||
/// Gives the ids of all known connected peers.
|
||||
pub fn connected_peers(&self) -> impl Iterator<Item = &PeerId> {
|
||||
self.peers
|
||||
.iter()
|
||||
.filter(|(_, info)| info.connection_status.is_connected())
|
||||
.map(|(peer_id, _)| peer_id)
|
||||
}
|
||||
|
||||
/// Gives an iterator of all peers on a given subnet.
|
||||
pub fn peers_on_subnet(&self, subnet_id: &SubnetId) -> impl Iterator<Item = &PeerId> {
|
||||
let subnet_id_filter = subnet_id.clone();
|
||||
self.peers
|
||||
.iter()
|
||||
.filter(move |(_, info)| {
|
||||
info.connection_status.is_connected() && info.on_subnet(subnet_id_filter)
|
||||
})
|
||||
.map(|(peer_id, _)| peer_id)
|
||||
}
|
||||
|
||||
/// Gives the ids of all known disconnected peers.
|
||||
pub fn disconnected_peers(&self) -> impl Iterator<Item = &PeerId> {
|
||||
self.peers
|
||||
.iter()
|
||||
.filter(|(_, info)| info.connection_status.is_disconnected())
|
||||
.map(|(peer_id, _)| peer_id)
|
||||
}
|
||||
|
||||
/// Gives the ids of all known banned peers.
|
||||
pub fn banned_peers(&self) -> impl Iterator<Item = &PeerId> {
|
||||
self.peers
|
||||
.iter()
|
||||
.filter(|(_, info)| info.connection_status.is_banned())
|
||||
.map(|(peer_id, _)| peer_id)
|
||||
}
|
||||
|
||||
/// Returns a vector containing peers (their ids and info), sorted by
|
||||
/// reputation from highest to lowest, and filtered using `is_status`
|
||||
pub fn best_peers_by_status<F>(&self, is_status: F) -> Vec<(&PeerId, &PeerInfo<TSpec>)>
|
||||
where
|
||||
F: Fn(&PeerConnectionStatus) -> bool,
|
||||
{
|
||||
let mut by_status = self
|
||||
.peers
|
||||
.iter()
|
||||
.filter(|(_, info)| is_status(&info.connection_status))
|
||||
.collect::<Vec<_>>();
|
||||
by_status.sort_by_key(|(_, info)| Rep::max_value() - info.reputation);
|
||||
by_status
|
||||
}
|
||||
|
||||
/// Returns the peer with highest reputation that satisfies `is_status`
|
||||
pub fn best_by_status<F>(&self, is_status: F) -> Option<&PeerId>
|
||||
where
|
||||
F: Fn(&PeerConnectionStatus) -> bool,
|
||||
{
|
||||
self.peers
|
||||
.iter()
|
||||
.filter(|(_, info)| is_status(&info.connection_status))
|
||||
.max_by_key(|(_, info)| info.reputation)
|
||||
.map(|(id, _)| id)
|
||||
}
|
||||
|
||||
/// Sets a peer as connected with an ingoing connection
|
||||
pub fn connect_ingoing(&mut self, peer_id: &PeerId) {
|
||||
let info = self
|
||||
.peers
|
||||
.entry(peer_id.clone())
|
||||
.or_insert_with(|| Default::default());
|
||||
|
||||
if info.connection_status.is_disconnected() {
|
||||
self.n_dc -= 1;
|
||||
}
|
||||
info.connection_status.connect_ingoing();
|
||||
}
|
||||
|
||||
/// Add the meta data of a peer.
|
||||
pub fn add_metadata(&mut self, peer_id: &PeerId, meta_data: MetaData<TSpec>) {
|
||||
if let Some(peer_info) = self.peers.get_mut(peer_id) {
|
||||
peer_info.meta_data = Some(meta_data);
|
||||
} else {
|
||||
warn!(self.log, "Tried to add meta data for a non-existant peer"; "peer_id" => format!("{}", peer_id));
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets a peer as connected with an outgoing connection
|
||||
pub fn connect_outgoing(&mut self, peer_id: &PeerId) {
|
||||
let info = self
|
||||
.peers
|
||||
.entry(peer_id.clone())
|
||||
.or_insert_with(|| Default::default());
|
||||
|
||||
if info.connection_status.is_disconnected() {
|
||||
self.n_dc -= 1;
|
||||
}
|
||||
info.connection_status.connect_outgoing();
|
||||
}
|
||||
|
||||
/// Sets the peer as disconnected
|
||||
pub fn disconnect(&mut self, peer_id: &PeerId) {
|
||||
let log_ref = &self.log;
|
||||
let info = self.peers.entry(peer_id.clone()).or_insert_with(|| {
|
||||
warn!(log_ref, "Disconnecting unknown peer";
|
||||
"peer_id" => format!("{:?}",peer_id));
|
||||
PeerInfo::default()
|
||||
});
|
||||
|
||||
if !info.connection_status.is_disconnected() {
|
||||
info.connection_status.disconnect();
|
||||
self.n_dc += 1;
|
||||
}
|
||||
self.shrink_to_fit();
|
||||
}
|
||||
|
||||
/// Drops the peers with the lowest reputation so that the number of
|
||||
/// disconnected peers is less than MAX_DC_PEERS
|
||||
pub fn shrink_to_fit(&mut self) {
|
||||
// for caution, but the difference should never be > 1
|
||||
while self.n_dc > MAX_DC_PEERS {
|
||||
let to_drop = self
|
||||
.peers
|
||||
.iter()
|
||||
.filter(|(_, info)| info.connection_status.is_disconnected())
|
||||
.min_by_key(|(_, info)| info.reputation)
|
||||
.map(|(id, _)| id.clone())
|
||||
.unwrap(); // should be safe since n_dc > MAX_DC_PEERS > 0
|
||||
self.peers.remove(&to_drop);
|
||||
self.n_dc -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets a peer as banned
|
||||
pub fn ban(&mut self, peer_id: &PeerId) {
|
||||
let log_ref = &self.log;
|
||||
let info = self.peers.entry(peer_id.clone()).or_insert_with(|| {
|
||||
warn!(log_ref, "Banning unknown peer";
|
||||
"peer_id" => format!("{:?}",peer_id));
|
||||
PeerInfo::default()
|
||||
});
|
||||
if info.connection_status.is_disconnected() {
|
||||
self.n_dc -= 1;
|
||||
}
|
||||
info.connection_status.ban();
|
||||
}
|
||||
|
||||
/// Inserts a new peer with the default PeerInfo if it is not already present
|
||||
/// Returns if the peer was new to the PeerDB
|
||||
pub fn new_peer(&mut self, peer_id: &PeerId) -> bool {
|
||||
if !self.peers.contains_key(peer_id) {
|
||||
self.peers.insert(peer_id.clone(), Default::default());
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Sets the reputation of peer
|
||||
pub fn set_reputation(&mut self, peer_id: &PeerId, rep: Rep) {
|
||||
let log_ref = &self.log;
|
||||
self.peers
|
||||
.entry(peer_id.clone())
|
||||
.or_insert_with(|| {
|
||||
warn!(log_ref, "Setting the reputation of an unknown peer";
|
||||
"peer_id" => format!("{:?}",peer_id));
|
||||
PeerInfo::default()
|
||||
})
|
||||
.reputation = rep;
|
||||
}
|
||||
|
||||
/// Adds to a peer's reputation by `change`. If the reputation exceeds Rep's
|
||||
/// upper (lower) bounds, it stays at the maximum (minimum) value
|
||||
pub fn add_reputation(&mut self, peer_id: &PeerId, change: Rep) {
|
||||
let log_ref = &self.log;
|
||||
let info = self.peers.entry(peer_id.clone()).or_insert_with(|| {
|
||||
warn!(log_ref, "Adding to the reputation of an unknown peer";
|
||||
"peer_id" => format!("{:?}",peer_id));
|
||||
PeerInfo::default()
|
||||
});
|
||||
info.reputation = info.reputation.saturating_add(change);
|
||||
}
|
||||
|
||||
pub fn connection_status(&self, peer_id: &PeerId) -> PeerConnectionStatus {
|
||||
self.peer_info(peer_id)
|
||||
.map_or(PeerConnectionStatus::default(), |info| {
|
||||
info.connection_status.clone()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use slog::{o, Drain};
|
||||
use types::MinimalEthSpec;
|
||||
type M = MinimalEthSpec;
|
||||
|
||||
pub fn build_log(level: slog::Level, enabled: bool) -> slog::Logger {
|
||||
let decorator = slog_term::TermDecorator::new().build();
|
||||
let drain = slog_term::FullFormat::new(decorator).build().fuse();
|
||||
let drain = slog_async::Async::new(drain).build().fuse();
|
||||
|
||||
if enabled {
|
||||
slog::Logger::root(drain.filter_level(level).fuse(), o!())
|
||||
} else {
|
||||
slog::Logger::root(drain.filter(|_| false).fuse(), o!())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_db() -> PeerDB<M> {
|
||||
let log = build_log(slog::Level::Debug, true);
|
||||
PeerDB::new(&log)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peer_connected_successfully() {
|
||||
let mut pdb = get_db();
|
||||
let random_peer = PeerId::random();
|
||||
|
||||
let (n_in, n_out) = (10, 20);
|
||||
for _ in 0..n_in {
|
||||
pdb.connect_ingoing(&random_peer);
|
||||
}
|
||||
for _ in 0..n_out {
|
||||
pdb.connect_outgoing(&random_peer);
|
||||
}
|
||||
|
||||
// the peer is known
|
||||
let peer_info = pdb.peer_info(&random_peer);
|
||||
assert!(peer_info.is_some());
|
||||
// this is the only peer
|
||||
assert_eq!(pdb.peers().count(), 1);
|
||||
// the peer has the default reputation
|
||||
assert_eq!(pdb.reputation(&random_peer), DEFAULT_REPUTATION);
|
||||
// it should be connected, and therefore not counted as disconnected
|
||||
assert_eq!(pdb.n_dc, 0);
|
||||
assert!(peer_info.unwrap().connection_status.is_connected());
|
||||
assert_eq!(
|
||||
peer_info.unwrap().connection_status.connections(),
|
||||
(n_in, n_out)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_reputation() {
|
||||
let mut pdb = get_db();
|
||||
let random_peer = PeerId::random();
|
||||
pdb.connect_ingoing(&random_peer);
|
||||
|
||||
let mut rep = Rep::min_value();
|
||||
pdb.set_reputation(&random_peer, rep);
|
||||
assert_eq!(pdb.reputation(&random_peer), rep);
|
||||
|
||||
rep = Rep::max_value();
|
||||
pdb.set_reputation(&random_peer, rep);
|
||||
assert_eq!(pdb.reputation(&random_peer), rep);
|
||||
|
||||
rep = Rep::max_value() / 100;
|
||||
pdb.set_reputation(&random_peer, rep);
|
||||
assert_eq!(pdb.reputation(&random_peer), rep);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reputation_change() {
|
||||
let mut pdb = get_db();
|
||||
|
||||
// 0 change does not change de reputation
|
||||
let random_peer = PeerId::random();
|
||||
let change: Rep = 0;
|
||||
pdb.connect_ingoing(&random_peer);
|
||||
pdb.add_reputation(&random_peer, change);
|
||||
assert_eq!(pdb.reputation(&random_peer), DEFAULT_REPUTATION);
|
||||
|
||||
// overflowing change is capped
|
||||
let random_peer = PeerId::random();
|
||||
let change = Rep::max_value();
|
||||
pdb.connect_ingoing(&random_peer);
|
||||
pdb.add_reputation(&random_peer, change);
|
||||
assert_eq!(pdb.reputation(&random_peer), Rep::max_value());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_disconnected_are_bounded() {
|
||||
let mut pdb = get_db();
|
||||
|
||||
for _ in 0..MAX_DC_PEERS + 1 {
|
||||
let p = PeerId::random();
|
||||
pdb.connect_ingoing(&p);
|
||||
}
|
||||
assert_eq!(pdb.n_dc, 0);
|
||||
|
||||
for p in pdb.connected_peers().cloned().collect::<Vec<_>>() {
|
||||
pdb.disconnect(&p);
|
||||
}
|
||||
|
||||
assert_eq!(pdb.n_dc, MAX_DC_PEERS);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_best_peers() {
|
||||
let mut pdb = get_db();
|
||||
|
||||
let p0 = PeerId::random();
|
||||
let p1 = PeerId::random();
|
||||
let p2 = PeerId::random();
|
||||
pdb.new_peer(&p0);
|
||||
pdb.new_peer(&p1);
|
||||
pdb.new_peer(&p2);
|
||||
pdb.connect_ingoing(&p0);
|
||||
pdb.connect_ingoing(&p1);
|
||||
pdb.connect_ingoing(&p2);
|
||||
pdb.set_reputation(&p0, 70);
|
||||
pdb.set_reputation(&p1, 100);
|
||||
pdb.set_reputation(&p2, 50);
|
||||
|
||||
let best_peers = pdb.best_peers_by_status(PeerConnectionStatus::is_connected);
|
||||
assert!(vec![&p1, &p0, &p2]
|
||||
.into_iter()
|
||||
.eq(best_peers.into_iter().map(|p| p.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_the_best_peer() {
|
||||
let mut pdb = get_db();
|
||||
|
||||
let p0 = PeerId::random();
|
||||
let p1 = PeerId::random();
|
||||
let p2 = PeerId::random();
|
||||
pdb.new_peer(&p0);
|
||||
pdb.new_peer(&p1);
|
||||
pdb.new_peer(&p2);
|
||||
pdb.connect_ingoing(&p0);
|
||||
pdb.connect_ingoing(&p1);
|
||||
pdb.connect_ingoing(&p2);
|
||||
pdb.set_reputation(&p0, 70);
|
||||
pdb.set_reputation(&p1, 100);
|
||||
pdb.set_reputation(&p2, 50);
|
||||
|
||||
let the_best = pdb.best_by_status(PeerConnectionStatus::is_connected);
|
||||
assert!(the_best.is_some());
|
||||
// Consistency check
|
||||
let best_peers = pdb.best_peers_by_status(PeerConnectionStatus::is_connected);
|
||||
assert_eq!(the_best, best_peers.into_iter().map(|p| p.0).next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_disconnected_consistency() {
|
||||
let mut pdb = get_db();
|
||||
|
||||
let random_peer = PeerId::random();
|
||||
|
||||
pdb.new_peer(&random_peer);
|
||||
assert_eq!(pdb.n_dc, pdb.disconnected_peers().count());
|
||||
|
||||
pdb.connect_ingoing(&random_peer);
|
||||
assert_eq!(pdb.n_dc, pdb.disconnected_peers().count());
|
||||
pdb.disconnect(&random_peer);
|
||||
assert_eq!(pdb.n_dc, pdb.disconnected_peers().count());
|
||||
|
||||
pdb.connect_outgoing(&random_peer);
|
||||
assert_eq!(pdb.n_dc, pdb.disconnected_peers().count());
|
||||
pdb.disconnect(&random_peer);
|
||||
assert_eq!(pdb.n_dc, pdb.disconnected_peers().count());
|
||||
|
||||
pdb.ban(&random_peer);
|
||||
assert_eq!(pdb.n_dc, pdb.disconnected_peers().count());
|
||||
pdb.disconnect(&random_peer);
|
||||
assert_eq!(pdb.n_dc, pdb.disconnected_peers().count());
|
||||
|
||||
pdb.disconnect(&random_peer);
|
||||
assert_eq!(pdb.n_dc, pdb.disconnected_peers().count());
|
||||
pdb.disconnect(&random_peer);
|
||||
assert_eq!(pdb.n_dc, pdb.disconnected_peers().count());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user