Flexible peer filtering (#3458)
* first pass at peers iter cleanup * more flexible peer with diff lookups * PeersIter with impl Iterator * sync against outbound peers reorder peers filters so expensive filters come later * filter peers by capabilities during sync * prefer outbound peers with high total difficulty * with_difficulty now takes a fn to allow more flexible comparisons based on difficulty * rename peers_iter() -> iter()
This commit is contained in:
+128
-151
@@ -18,8 +18,7 @@ use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use rand::prelude::*;
|
||||
|
||||
use crate::chain;
|
||||
use crate::core::core;
|
||||
@@ -105,139 +104,32 @@ impl Peers {
|
||||
Ok(peers.contains_key(&addr))
|
||||
}
|
||||
|
||||
/// Get vec of peers we are currently connected to.
|
||||
pub fn connected_peers(&self) -> Vec<Arc<Peer>> {
|
||||
/// Iterator over our current peers.
|
||||
/// This allows us to hide try_read_for() behind a cleaner interface.
|
||||
/// PeersIter lets us chain various adaptors for convenience.
|
||||
pub fn iter(&self) -> PeersIter<impl Iterator<Item = Arc<Peer>>> {
|
||||
let peers = match self.peers.try_read_for(LOCK_TIMEOUT) {
|
||||
Some(peers) => peers,
|
||||
Some(peers) => peers.values().cloned().collect(),
|
||||
None => {
|
||||
error!("connected_peers: failed to get peers lock");
|
||||
return vec![];
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
let mut res = peers
|
||||
.values()
|
||||
.filter(|p| p.is_connected())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
res.shuffle(&mut thread_rng());
|
||||
res
|
||||
}
|
||||
|
||||
/// Get vec of peers we currently have an outgoing connection with.
|
||||
pub fn outgoing_connected_peers(&self) -> Vec<Arc<Peer>> {
|
||||
self.connected_peers()
|
||||
.into_iter()
|
||||
.filter(|x| x.info.is_outbound())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get vec of peers we currently have an incoming connection with.
|
||||
pub fn incoming_connected_peers(&self) -> Vec<Arc<Peer>> {
|
||||
self.connected_peers()
|
||||
.into_iter()
|
||||
.filter(|x| x.info.is_inbound())
|
||||
.collect()
|
||||
PeersIter {
|
||||
iter: peers.into_iter(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a peer we're connected to by address.
|
||||
pub fn get_connected_peer(&self, addr: PeerAddr) -> Option<Arc<Peer>> {
|
||||
let peers = match self.peers.try_read_for(LOCK_TIMEOUT) {
|
||||
Some(peers) => peers,
|
||||
None => {
|
||||
error!("get_connected_peer: failed to get peers lock");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
peers.get(&addr).cloned()
|
||||
self.iter().connected().by_addr(addr)
|
||||
}
|
||||
|
||||
/// Number of peers currently connected to.
|
||||
pub fn peer_count(&self) -> u32 {
|
||||
self.connected_peers().len() as u32
|
||||
}
|
||||
|
||||
/// Number of outbound peers currently connected to.
|
||||
pub fn peer_outbound_count(&self) -> u32 {
|
||||
self.outgoing_connected_peers().len() as u32
|
||||
}
|
||||
|
||||
/// Number of inbound peers currently connected to.
|
||||
pub fn peer_inbound_count(&self) -> u32 {
|
||||
self.incoming_connected_peers().len() as u32
|
||||
}
|
||||
|
||||
// Return vec of connected peers that currently advertise more work
|
||||
// (total_difficulty) than we do.
|
||||
pub fn more_work_peers(&self) -> Result<Vec<Arc<Peer>>, chain::Error> {
|
||||
let peers = self.connected_peers();
|
||||
if peers.is_empty() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
let total_difficulty = self.total_difficulty()?;
|
||||
|
||||
let mut max_peers = peers
|
||||
.into_iter()
|
||||
.filter(|x| x.info.total_difficulty() > total_difficulty)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
max_peers.shuffle(&mut thread_rng());
|
||||
Ok(max_peers)
|
||||
}
|
||||
|
||||
// Return number of connected peers that currently advertise more/same work
|
||||
// (total_difficulty) than/as we do.
|
||||
pub fn more_or_same_work_peers(&self) -> Result<usize, chain::Error> {
|
||||
let peers = self.connected_peers();
|
||||
if peers.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let total_difficulty = self.total_difficulty()?;
|
||||
|
||||
Ok(peers
|
||||
.iter()
|
||||
.filter(|x| x.info.total_difficulty() >= total_difficulty)
|
||||
.count())
|
||||
}
|
||||
|
||||
/// Returns single random peer with more work than us.
|
||||
pub fn more_work_peer(&self) -> Option<Arc<Peer>> {
|
||||
match self.more_work_peers() {
|
||||
Ok(mut peers) => peers.pop(),
|
||||
Err(e) => {
|
||||
error!("failed to get more work peers: {:?}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return vec of connected peers that currently have the most worked
|
||||
/// branch, showing the highest total difficulty.
|
||||
pub fn most_work_peers(&self) -> Vec<Arc<Peer>> {
|
||||
let peers = self.connected_peers();
|
||||
if peers.is_empty() {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let max_total_difficulty = match peers.iter().map(|x| x.info.total_difficulty()).max() {
|
||||
Some(v) => v,
|
||||
None => return vec![],
|
||||
};
|
||||
|
||||
let mut max_peers = peers
|
||||
.into_iter()
|
||||
.filter(|x| x.info.total_difficulty() == max_total_difficulty)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
max_peers.shuffle(&mut thread_rng());
|
||||
max_peers
|
||||
}
|
||||
|
||||
/// Returns single random peer with the most worked branch, showing the
|
||||
/// highest total difficulty.
|
||||
pub fn most_work_peer(&self) -> Option<Arc<Peer>> {
|
||||
self.most_work_peers().pop()
|
||||
pub fn max_peer_difficulty(&self) -> Difficulty {
|
||||
self.iter()
|
||||
.connected()
|
||||
.max_difficulty()
|
||||
.unwrap_or(Difficulty::zero())
|
||||
}
|
||||
|
||||
pub fn is_banned(&self, peer_addr: PeerAddr) -> bool {
|
||||
@@ -286,7 +178,7 @@ impl Peers {
|
||||
{
|
||||
let mut count = 0;
|
||||
|
||||
for p in self.connected_peers().iter() {
|
||||
for p in self.iter().connected() {
|
||||
match inner(&p) {
|
||||
Ok(true) => count += 1,
|
||||
Ok(false) => (),
|
||||
@@ -353,7 +245,7 @@ impl Peers {
|
||||
/// Ping all our connected peers. Always automatically expects a pong back
|
||||
/// or disconnects. This acts as a liveness test.
|
||||
pub fn check_all(&self, total_difficulty: Difficulty, height: u64) {
|
||||
for p in self.connected_peers().iter() {
|
||||
for p in self.iter().connected() {
|
||||
if let Err(e) = p.send_ping(total_difficulty, height) {
|
||||
debug!("Error pinging peer {:?}: {:?}", &p.info.addr, e);
|
||||
let mut peers = match self.peers.try_write_for(LOCK_TIMEOUT) {
|
||||
@@ -370,13 +262,13 @@ impl Peers {
|
||||
}
|
||||
|
||||
/// Iterator over all peers we know about (stored in our db).
|
||||
pub fn peers_iter(&self) -> Result<impl Iterator<Item = PeerData>, Error> {
|
||||
pub fn peer_data_iter(&self) -> Result<impl Iterator<Item = PeerData>, Error> {
|
||||
self.store.peers_iter().map_err(From::from)
|
||||
}
|
||||
|
||||
/// Convenience for reading all peers.
|
||||
pub fn all_peers(&self) -> Vec<PeerData> {
|
||||
self.peers_iter()
|
||||
/// Convenience for reading all peer data from the db.
|
||||
pub fn all_peer_data(&self) -> Vec<PeerData> {
|
||||
self.peer_data_iter()
|
||||
.map(|peers| peers.collect())
|
||||
.unwrap_or(vec![])
|
||||
}
|
||||
@@ -427,14 +319,8 @@ impl Peers {
|
||||
|
||||
// build a list of peers to be cleaned up
|
||||
{
|
||||
let peers = match self.peers.try_read_for(LOCK_TIMEOUT) {
|
||||
Some(peers) => peers,
|
||||
None => {
|
||||
error!("clean_peers: can't get peers lock");
|
||||
return;
|
||||
}
|
||||
};
|
||||
for peer in peers.values() {
|
||||
for peer in self.iter() {
|
||||
let ref peer: &Peer = peer.as_ref();
|
||||
if peer.is_banned() {
|
||||
debug!("clean_peers {:?}, peer banned", peer.info.addr);
|
||||
rm.push(peer.info.addr.clone());
|
||||
@@ -466,27 +352,34 @@ impl Peers {
|
||||
}
|
||||
}
|
||||
|
||||
// closure to build an iterator of our inbound peers
|
||||
let outbound_peers = || self.iter().outbound().connected().into_iter();
|
||||
|
||||
// check here to make sure we don't have too many outgoing connections
|
||||
let excess_outgoing_count =
|
||||
(self.peer_outbound_count() as usize).saturating_sub(max_outbound_count);
|
||||
// Preferred peers are treated preferentially here.
|
||||
// Also choose outbound peers with lowest total difficulty to drop.
|
||||
let excess_outgoing_count = outbound_peers().count().saturating_sub(max_outbound_count);
|
||||
if excess_outgoing_count > 0 {
|
||||
let mut addrs: Vec<_> = self
|
||||
.outgoing_connected_peers()
|
||||
.iter()
|
||||
.filter(|x| !preferred_peers.contains(&x.info.addr))
|
||||
let mut peer_infos: Vec<_> = outbound_peers()
|
||||
.map(|x| x.info.clone())
|
||||
.filter(|x| !preferred_peers.contains(&x.addr))
|
||||
.collect();
|
||||
peer_infos.sort_unstable_by_key(|x| x.total_difficulty());
|
||||
let mut addrs = peer_infos
|
||||
.into_iter()
|
||||
.map(|x| x.addr)
|
||||
.take(excess_outgoing_count)
|
||||
.map(|x| x.info.addr)
|
||||
.collect();
|
||||
rm.append(&mut addrs);
|
||||
}
|
||||
|
||||
// closure to build an iterator of our inbound peers
|
||||
let inbound_peers = || self.iter().inbound().connected().into_iter();
|
||||
|
||||
// check here to make sure we don't have too many incoming connections
|
||||
let excess_incoming_count =
|
||||
(self.peer_inbound_count() as usize).saturating_sub(max_inbound_count);
|
||||
let excess_incoming_count = inbound_peers().count().saturating_sub(max_inbound_count);
|
||||
if excess_incoming_count > 0 {
|
||||
let mut addrs: Vec<_> = self
|
||||
.incoming_connected_peers()
|
||||
.iter()
|
||||
let mut addrs: Vec<_> = inbound_peers()
|
||||
.filter(|x| !preferred_peers.contains(&x.info.addr))
|
||||
.take(excess_incoming_count)
|
||||
.map(|x| x.info.addr)
|
||||
@@ -522,7 +415,8 @@ impl Peers {
|
||||
|
||||
/// We have enough outbound connected peers
|
||||
pub fn enough_outbound_peers(&self) -> bool {
|
||||
self.peer_outbound_count() >= self.config.peer_min_preferred_outbound_count()
|
||||
self.iter().outbound().connected().count()
|
||||
>= self.config.peer_min_preferred_outbound_count() as usize
|
||||
}
|
||||
|
||||
/// Removes those peers that seem to have expired
|
||||
@@ -780,3 +674,86 @@ impl NetAdapter for Peers {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PeersIter<I> {
|
||||
iter: I,
|
||||
}
|
||||
|
||||
impl<I: Iterator> IntoIterator for PeersIter<I> {
|
||||
type Item = I::Item;
|
||||
type IntoIter = I;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Iterator<Item = Arc<Peer>>> PeersIter<I> {
|
||||
/// Filter peers that are currently connected.
|
||||
/// Note: This adaptor takes a read lock internally.
|
||||
/// So if we are chaining adaptors then defer this toward the end of the chain.
|
||||
pub fn connected(self) -> PeersIter<impl Iterator<Item = Arc<Peer>>> {
|
||||
PeersIter {
|
||||
iter: self.iter.filter(|p| p.is_connected()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Filter inbound peers.
|
||||
pub fn inbound(self) -> PeersIter<impl Iterator<Item = Arc<Peer>>> {
|
||||
PeersIter {
|
||||
iter: self.iter.filter(|p| p.info.is_inbound()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Filter outbound peers.
|
||||
pub fn outbound(self) -> PeersIter<impl Iterator<Item = Arc<Peer>>> {
|
||||
PeersIter {
|
||||
iter: self.iter.filter(|p| p.info.is_outbound()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Filter peers with the provided difficulty comparison fn.
|
||||
///
|
||||
/// with_difficulty(|x| x > diff)
|
||||
///
|
||||
/// Note: This adaptor takes a read lock internally for each peer.
|
||||
/// So if we are chaining adaptors then put this toward later in the chain.
|
||||
pub fn with_difficulty<F>(self, f: F) -> PeersIter<impl Iterator<Item = Arc<Peer>>>
|
||||
where
|
||||
F: Fn(Difficulty) -> bool,
|
||||
{
|
||||
PeersIter {
|
||||
iter: self.iter.filter(move |p| f(p.info.total_difficulty())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Filter peers that support the provided capabilities.
|
||||
pub fn with_capabilities(
|
||||
self,
|
||||
cap: Capabilities,
|
||||
) -> PeersIter<impl Iterator<Item = Arc<Peer>>> {
|
||||
PeersIter {
|
||||
iter: self.iter.filter(move |p| p.info.capabilities.contains(cap)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn by_addr(&mut self, addr: PeerAddr) -> Option<Arc<Peer>> {
|
||||
self.iter.find(|p| p.info.addr == addr)
|
||||
}
|
||||
|
||||
/// Choose a random peer from the current (filtered) peers.
|
||||
pub fn choose_random(self) -> Option<Arc<Peer>> {
|
||||
let mut rng = rand::thread_rng();
|
||||
self.iter.choose(&mut rng)
|
||||
}
|
||||
|
||||
/// Find the max difficulty of the current (filtered) peers.
|
||||
pub fn max_difficulty(self) -> Option<Difficulty> {
|
||||
self.iter.map(|p| p.info.total_difficulty()).max()
|
||||
}
|
||||
|
||||
/// Count the current (filtered) peers.
|
||||
pub fn count(self) -> usize {
|
||||
self.iter.count()
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -233,7 +233,7 @@ impl Server {
|
||||
/// different sets of peers themselves. In addition, it prevent potential
|
||||
/// duplicate connections, malicious or not.
|
||||
fn check_undesirable(&self, stream: &TcpStream) -> bool {
|
||||
if self.peers.peer_inbound_count()
|
||||
if self.peers.iter().inbound().connected().count() as u32
|
||||
>= self.config.peer_max_inbound_count() + self.config.peer_listener_buffer_count()
|
||||
{
|
||||
debug!("Accepting new connection will exceed peer limit, refusing connection.");
|
||||
|
||||
@@ -266,6 +266,7 @@ pub struct P2PConfig {
|
||||
/// The list of seed nodes, if using Seeding as a seed type
|
||||
pub seeds: Option<PeerAddrs>,
|
||||
|
||||
/// TODO: Rethink this. We need to separate what *we* advertise vs. who we connect to.
|
||||
/// Capabilities expose by this node, also conditions which other peers this
|
||||
/// node will have an affinity toward when connection.
|
||||
pub capabilities: Capabilities,
|
||||
|
||||
Reference in New Issue
Block a user