mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 19:09:54 +00:00
feat(net): temporarily ban bad peers (#492)
* feat(net): temporarily ban bad peers * use half duration interval * Update crates/net/network/src/peers/manager.rs Co-authored-by: Bjerg <onbjerg@users.noreply.github.com> * fix bad test Co-authored-by: Bjerg <onbjerg@users.noreply.github.com>
This commit is contained in:
@ -32,6 +32,21 @@ impl BanList {
|
|||||||
Self { banned_ips, banned_peers }
|
Self { banned_ips, banned_peers }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes all peers that are no longer banned.
|
||||||
|
pub fn evict_peers(&mut self, now: Instant) -> Vec<PeerId> {
|
||||||
|
let mut evicted = Vec::new();
|
||||||
|
self.banned_peers.retain(|peer, until| {
|
||||||
|
if let Some(until) = until {
|
||||||
|
if now > *until {
|
||||||
|
evicted.push(*peer);
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
});
|
||||||
|
evicted
|
||||||
|
}
|
||||||
|
|
||||||
/// Removes all entries that should no longer be banned.
|
/// Removes all entries that should no longer be banned.
|
||||||
pub fn evict(&mut self, now: Instant) {
|
pub fn evict(&mut self, now: Instant) {
|
||||||
self.banned_ips.retain(|_, until| {
|
self.banned_ips.retain(|_, until| {
|
||||||
|
|||||||
@ -23,6 +23,8 @@ pub use client::FetchClient;
|
|||||||
///
|
///
|
||||||
/// This type is hooked into the staged sync pipeline and delegates download request to available
|
/// This type is hooked into the staged sync pipeline and delegates download request to available
|
||||||
/// peers and sends the response once ready.
|
/// peers and sends the response once ready.
|
||||||
|
///
|
||||||
|
/// This type maintains a list of connected peers that are available for requests.
|
||||||
pub struct StateFetcher {
|
pub struct StateFetcher {
|
||||||
/// Currently active [`GetBlockHeaders`] requests
|
/// Currently active [`GetBlockHeaders`] requests
|
||||||
inflight_headers_requests:
|
inflight_headers_requests:
|
||||||
@ -30,7 +32,7 @@ pub struct StateFetcher {
|
|||||||
/// Currently active [`GetBlockBodies`] requests
|
/// Currently active [`GetBlockBodies`] requests
|
||||||
inflight_bodies_requests:
|
inflight_bodies_requests:
|
||||||
HashMap<PeerId, Request<Vec<H256>, PeerRequestResult<Vec<BlockBody>>>>,
|
HashMap<PeerId, Request<Vec<H256>, PeerRequestResult<Vec<BlockBody>>>>,
|
||||||
/// The list of available peers for requests.
|
/// The list of _available_ peers for requests.
|
||||||
peers: HashMap<PeerId, Peer>,
|
peers: HashMap<PeerId, Peer>,
|
||||||
/// The handle to the peers manager
|
/// The handle to the peers manager
|
||||||
peers_handle: PeersHandle,
|
peers_handle: PeersHandle,
|
||||||
@ -63,6 +65,9 @@ impl StateFetcher {
|
|||||||
self.peers.insert(peer_id, Peer { state: PeerState::Idle, best_hash, best_number });
|
self.peers.insert(peer_id, Peer { state: PeerState::Idle, best_hash, best_number });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes the peer from the peer list, after which it is no longer available for future
|
||||||
|
/// requests.
|
||||||
|
///
|
||||||
/// Invoked when an active session was closed.
|
/// Invoked when an active session was closed.
|
||||||
///
|
///
|
||||||
/// This cancels als inflight request and sends an error to the receiver.
|
/// This cancels als inflight request and sends an error to the receiver.
|
||||||
@ -97,7 +102,7 @@ impl StateFetcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the next idle peer that's ready to accept a request
|
/// Returns the _next_ idle peer that's ready to accept a request.
|
||||||
fn next_peer(&mut self) -> Option<(&PeerId, &mut Peer)> {
|
fn next_peer(&mut self) -> Option<(&PeerId, &mut Peer)> {
|
||||||
self.peers.iter_mut().find(|(_, peer)| peer.state.is_idle())
|
self.peers.iter_mut().find(|(_, peer)| peer.state.is_idle())
|
||||||
}
|
}
|
||||||
@ -181,7 +186,7 @@ impl StateFetcher {
|
|||||||
|
|
||||||
/// Returns a new followup request for the peer.
|
/// Returns a new followup request for the peer.
|
||||||
///
|
///
|
||||||
/// Caution: this expects that the peer is _not_ closed
|
/// Caution: this expects that the peer is _not_ closed.
|
||||||
fn followup_request(&mut self, peer_id: PeerId) -> Option<BlockResponseOutcome> {
|
fn followup_request(&mut self, peer_id: PeerId) -> Option<BlockResponseOutcome> {
|
||||||
let req = self.queued_requests.pop_front()?;
|
let req = self.queued_requests.pop_front()?;
|
||||||
let req = self.prepare_block_request(peer_id, req);
|
let req = self.prepare_block_request(peer_id, req);
|
||||||
|
|||||||
@ -603,9 +603,9 @@ where
|
|||||||
?error,
|
?error,
|
||||||
"Outgoing pending session failed"
|
"Outgoing pending session failed"
|
||||||
);
|
);
|
||||||
let swarm = this.swarm.state_mut().peers_mut();
|
let peers = this.swarm.state_mut().peers_mut();
|
||||||
swarm.on_closed_outgoing_pending_session();
|
peers.on_closed_outgoing_pending_session(&peer_id);
|
||||||
swarm.apply_reputation_change(&peer_id, ReputationChangeKind::FailedToConnect);
|
peers.apply_reputation_change(&peer_id, ReputationChangeKind::FailedToConnect);
|
||||||
|
|
||||||
if error.map(|err| err.merits_discovery_ban()).unwrap_or_default() {
|
if error.map(|err| err.merits_discovery_ban()).unwrap_or_default() {
|
||||||
this.swarm.state_mut().ban_discovery(peer_id, remote_addr.ip());
|
this.swarm.state_mut().ban_discovery(peer_id, remote_addr.ip());
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
error::{error_merits_discovery_ban, is_fatal_protocol_error},
|
error::{error_merits_discovery_ban, is_fatal_protocol_error},
|
||||||
peers::{reputation::BANNED_REPUTATION, ReputationChangeKind, ReputationChangeWeights},
|
peers::{
|
||||||
|
reputation::{is_banned_reputation, DEFAULT_REPUTATION},
|
||||||
|
ReputationChangeKind, ReputationChangeWeights,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use reth_eth_wire::{error::EthStreamError, DisconnectReason};
|
use reth_eth_wire::{error::EthStreamError, DisconnectReason};
|
||||||
@ -19,7 +22,7 @@ use tokio::{
|
|||||||
time::{Instant, Interval},
|
time::{Instant, Interval},
|
||||||
};
|
};
|
||||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||||
use tracing::{debug, trace};
|
use tracing::trace;
|
||||||
|
|
||||||
/// A communication channel to the [`PeersManager`] to apply manual changes to the peer set.
|
/// A communication channel to the [`PeersManager`] to apply manual changes to the peer set.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@ -82,14 +85,24 @@ pub(crate) struct PeersManager {
|
|||||||
connection_info: ConnectionInfo,
|
connection_info: ConnectionInfo,
|
||||||
/// Tracks unwanted ips/peer ids,
|
/// Tracks unwanted ips/peer ids,
|
||||||
ban_list: BanList,
|
ban_list: BanList,
|
||||||
|
/// Interval at which to check for peers to unban.
|
||||||
|
unban_interval: Interval,
|
||||||
|
/// How long to ban bad peers.
|
||||||
|
ban_duration: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PeersManager {
|
impl PeersManager {
|
||||||
/// Create a new instance with the given config
|
/// Create a new instance with the given config
|
||||||
pub(crate) fn new(config: PeersConfig) -> Self {
|
pub(crate) fn new(config: PeersConfig) -> Self {
|
||||||
let PeersConfig { refill_slots_interval, connection_info, reputation_weights, ban_list } =
|
let PeersConfig {
|
||||||
config;
|
refill_slots_interval,
|
||||||
|
connection_info,
|
||||||
|
reputation_weights,
|
||||||
|
ban_list,
|
||||||
|
ban_duration,
|
||||||
|
} = config;
|
||||||
let (manager_tx, handle_rx) = mpsc::unbounded_channel();
|
let (manager_tx, handle_rx) = mpsc::unbounded_channel();
|
||||||
|
let now = Instant::now();
|
||||||
Self {
|
Self {
|
||||||
peers: Default::default(),
|
peers: Default::default(),
|
||||||
manager_tx,
|
manager_tx,
|
||||||
@ -97,11 +110,14 @@ impl PeersManager {
|
|||||||
queued_actions: Default::default(),
|
queued_actions: Default::default(),
|
||||||
reputation_weights,
|
reputation_weights,
|
||||||
refill_slots_interval: tokio::time::interval_at(
|
refill_slots_interval: tokio::time::interval_at(
|
||||||
Instant::now() + refill_slots_interval,
|
now + refill_slots_interval,
|
||||||
refill_slots_interval,
|
refill_slots_interval,
|
||||||
),
|
),
|
||||||
|
// Use half of ban duration for interval
|
||||||
|
unban_interval: tokio::time::interval_at(now + ban_duration, ban_duration / 2),
|
||||||
connection_info,
|
connection_info,
|
||||||
ban_list,
|
ban_list,
|
||||||
|
ban_duration,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,8 +151,11 @@ impl PeersManager {
|
|||||||
self.connection_info.decr_in()
|
self.connection_info.decr_in()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Invoked when a pending session was closed.
|
/// Invoked when a pending outgoing session was closed.
|
||||||
pub(crate) fn on_closed_outgoing_pending_session(&mut self) {
|
pub(crate) fn on_closed_outgoing_pending_session(&mut self, peer_id: &PeerId) {
|
||||||
|
if let Some(mut peer) = self.peers.get_mut(peer_id) {
|
||||||
|
peer.state = PeerConnectionState::Idle;
|
||||||
|
}
|
||||||
self.connection_info.decr_out()
|
self.connection_info.decr_out()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,29 +188,40 @@ impl PeersManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Bans the peer temporarily with the given timeout
|
||||||
|
fn ban_peer(&mut self, peer_id: PeerId) {
|
||||||
|
self.ban_list.ban_peer_until(peer_id, std::time::Instant::now() + self.ban_duration);
|
||||||
|
self.queued_actions.push_back(PeerAction::BanPeer { peer_id });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unbans the peer
|
||||||
|
fn unban_peer(&mut self, peer_id: PeerId) {
|
||||||
|
self.ban_list.unban_peer(&peer_id);
|
||||||
|
self.queued_actions.push_back(PeerAction::UnBanPeer { peer_id });
|
||||||
|
}
|
||||||
|
|
||||||
/// Apply the corresponding reputation change to the given peer
|
/// Apply the corresponding reputation change to the given peer
|
||||||
pub(crate) fn apply_reputation_change(&mut self, peer_id: &PeerId, rep: ReputationChangeKind) {
|
pub(crate) fn apply_reputation_change(&mut self, peer_id: &PeerId, rep: ReputationChangeKind) {
|
||||||
let reputation_change = self.reputation_weights.change(rep);
|
let reputation_change = self.reputation_weights.change(rep);
|
||||||
let should_disconnect = if let Some(mut peer) = self.peers.get_mut(peer_id) {
|
let outcome = if let Some(peer) = self.peers.get_mut(peer_id) {
|
||||||
// we add reputation since negative reputation change decrease total reputation
|
peer.apply_reputation(reputation_change.as_i32())
|
||||||
peer.reputation = peer.reputation.saturating_add(reputation_change.as_i32());
|
|
||||||
trace!(target: "net::peers", repuation=%peer.reputation, banned=%peer.is_banned(), "applied reputation change");
|
|
||||||
let should_disconnect = peer.state.is_connected() && peer.is_banned();
|
|
||||||
|
|
||||||
if should_disconnect {
|
|
||||||
debug!(target: "net::peers", repuation=%peer.reputation, "disconnecting peer on reputation change");
|
|
||||||
peer.state.disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
should_disconnect
|
|
||||||
} else {
|
} else {
|
||||||
false
|
return
|
||||||
};
|
};
|
||||||
|
|
||||||
if should_disconnect {
|
match outcome {
|
||||||
// start the disconnect process
|
ReputationChangeOutcome::None => {}
|
||||||
self.queued_actions
|
ReputationChangeOutcome::Ban => {
|
||||||
.push_back(PeerAction::Disconnect { peer_id: *peer_id, reason: None })
|
self.ban_peer(*peer_id);
|
||||||
|
}
|
||||||
|
ReputationChangeOutcome::Unban => self.unban_peer(*peer_id),
|
||||||
|
ReputationChangeOutcome::DisconnectAndBan => {
|
||||||
|
self.queued_actions.push_back(PeerAction::Disconnect {
|
||||||
|
peer_id: *peer_id,
|
||||||
|
reason: Some(DisconnectReason::DisconnectRequested),
|
||||||
|
});
|
||||||
|
self.ban_peer(*peer_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -375,6 +405,17 @@ impl PeersManager {
|
|||||||
self.fill_outbound_slots();
|
self.fill_outbound_slots();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.unban_interval.poll_tick(cx).is_ready() {
|
||||||
|
for peer_id in self.ban_list.evict_peers(std::time::Instant::now()) {
|
||||||
|
if let Some(peer) = self.peers.get_mut(&peer_id) {
|
||||||
|
peer.unban();
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
self.queued_actions.push_back(PeerAction::UnBanPeer { peer_id });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.queued_actions.is_empty() {
|
if self.queued_actions.is_empty() {
|
||||||
return Poll::Pending
|
return Poll::Pending
|
||||||
}
|
}
|
||||||
@ -382,6 +423,12 @@ impl PeersManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for PeersManager {
|
||||||
|
fn default() -> Self {
|
||||||
|
PeersManager::new(Default::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Tracks stats about connected nodes
|
/// Tracks stats about connected nodes
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ConnectionInfo {
|
pub struct ConnectionInfo {
|
||||||
@ -439,8 +486,8 @@ impl Default for ConnectionInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
/// Tracks info about a single peer.
|
/// Tracks info about a single peer.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct Peer {
|
pub struct Peer {
|
||||||
/// Where to reach the peer
|
/// Where to reach the peer
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
@ -460,18 +507,60 @@ impl Peer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn with_state(addr: SocketAddr, state: PeerConnectionState) -> Self {
|
fn with_state(addr: SocketAddr, state: PeerConnectionState) -> Self {
|
||||||
Self { addr, state, reputation: 0, fork_id: None }
|
Self { addr, state, reputation: DEFAULT_REPUTATION, fork_id: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies a reputation change to the peer and returns what action should be taken.
|
||||||
|
fn apply_reputation(&mut self, reputation: i32) -> ReputationChangeOutcome {
|
||||||
|
let previous = self.reputation;
|
||||||
|
// we add reputation since negative reputation change decrease total reputation
|
||||||
|
self.reputation = previous.saturating_add(reputation);
|
||||||
|
|
||||||
|
trace!(target: "net::peers", repuation=%self.reputation, banned=%self.is_banned(), "applied reputation change");
|
||||||
|
|
||||||
|
if self.state.is_connected() && self.is_banned() {
|
||||||
|
self.state.disconnect();
|
||||||
|
return ReputationChangeOutcome::DisconnectAndBan
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.is_banned() && !is_banned_reputation(previous) {
|
||||||
|
return ReputationChangeOutcome::Ban
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.is_banned() && is_banned_reputation(previous) {
|
||||||
|
return ReputationChangeOutcome::Unban
|
||||||
|
}
|
||||||
|
|
||||||
|
ReputationChangeOutcome::None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the peer's reputation is below the banned threshold.
|
/// Returns true if the peer's reputation is below the banned threshold.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn is_banned(&self) -> bool {
|
fn is_banned(&self) -> bool {
|
||||||
self.reputation < BANNED_REPUTATION
|
is_banned_reputation(self.reputation)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unbans the peer by resetting its reputation
|
||||||
|
#[inline]
|
||||||
|
fn unban(&mut self) {
|
||||||
|
self.reputation = DEFAULT_REPUTATION
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Outcomes when a reputation change is applied to a peer
|
||||||
|
enum ReputationChangeOutcome {
|
||||||
|
/// Nothing to do.
|
||||||
|
None,
|
||||||
|
/// Ban the peer.
|
||||||
|
Ban,
|
||||||
|
/// Ban and disconnect
|
||||||
|
DisconnectAndBan,
|
||||||
|
/// Unban the peer
|
||||||
|
Unban,
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents the kind of connection established to the peer, if any
|
/// Represents the kind of connection established to the peer, if any
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
|
||||||
enum PeerConnectionState {
|
enum PeerConnectionState {
|
||||||
/// Not connected currently.
|
/// Not connected currently.
|
||||||
#[default]
|
#[default]
|
||||||
@ -546,19 +635,25 @@ pub enum PeerAction {
|
|||||||
},
|
},
|
||||||
/// Ban the peer in discovery.
|
/// Ban the peer in discovery.
|
||||||
DiscoveryBan { peer_id: PeerId, ip_addr: IpAddr },
|
DiscoveryBan { peer_id: PeerId, ip_addr: IpAddr },
|
||||||
|
/// Ban the peer temporarily
|
||||||
|
BanPeer { peer_id: PeerId },
|
||||||
|
/// Unban the peer temporarily
|
||||||
|
UnBanPeer { peer_id: PeerId },
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Config type for initiating a [`PeersManager`] instance
|
/// Config type for initiating a [`PeersManager`] instance
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PeersConfig {
|
pub struct PeersConfig {
|
||||||
/// How often to recheck free slots for outbound connections
|
/// How often to recheck free slots for outbound connections.
|
||||||
pub refill_slots_interval: Duration,
|
pub refill_slots_interval: Duration,
|
||||||
/// Restrictions on connections
|
/// Restrictions on connections.
|
||||||
pub connection_info: ConnectionInfo,
|
pub connection_info: ConnectionInfo,
|
||||||
/// How to weigh reputation changes
|
/// How to weigh reputation changes.
|
||||||
pub reputation_weights: ReputationChangeWeights,
|
pub reputation_weights: ReputationChangeWeights,
|
||||||
/// Restrictions on PeerIds and Ips
|
/// Restrictions on PeerIds and Ips.
|
||||||
pub ban_list: BanList,
|
pub ban_list: BanList,
|
||||||
|
/// How long to ban bad peers.
|
||||||
|
pub ban_duration: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PeersConfig {
|
impl Default for PeersConfig {
|
||||||
@ -568,6 +663,8 @@ impl Default for PeersConfig {
|
|||||||
connection_info: Default::default(),
|
connection_info: Default::default(),
|
||||||
reputation_weights: Default::default(),
|
reputation_weights: Default::default(),
|
||||||
ban_list: Default::default(),
|
ban_list: Default::default(),
|
||||||
|
// Ban peers for 12h
|
||||||
|
ban_duration: Duration::from_secs(60 * 60 * 12),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -626,7 +723,7 @@ mod test {
|
|||||||
use crate::{
|
use crate::{
|
||||||
peers::{
|
peers::{
|
||||||
manager::{ConnectionInfo, PeerConnectionState},
|
manager::{ConnectionInfo, PeerConnectionState},
|
||||||
PeerAction,
|
PeerAction, ReputationChangeKind,
|
||||||
},
|
},
|
||||||
PeersConfig,
|
PeersConfig,
|
||||||
};
|
};
|
||||||
@ -634,9 +731,143 @@ mod test {
|
|||||||
use reth_primitives::{PeerId, H512};
|
use reth_primitives::{PeerId, H512};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
|
future::{poll_fn, Future},
|
||||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||||
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct PeerActionFuture<'a> {
|
||||||
|
peers: &'a mut PeersManager,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Future for PeerActionFuture<'a> {
|
||||||
|
type Output = PeerAction;
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
self.get_mut().peers.poll(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! event {
|
||||||
|
($peers:expr) => {
|
||||||
|
PeerActionFuture { peers: &mut $peers }.await
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_insert() {
|
||||||
|
let peer = PeerId::random();
|
||||||
|
let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2)), 8008);
|
||||||
|
let mut peers = PeersManager::default();
|
||||||
|
peers.add_discovered_node(peer, socket_addr);
|
||||||
|
|
||||||
|
match event!(peers) {
|
||||||
|
PeerAction::Connect { peer_id, remote_addr } => {
|
||||||
|
assert_eq!(peer_id, peer);
|
||||||
|
assert_eq!(remote_addr, socket_addr);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_ban() {
|
||||||
|
let peer = PeerId::random();
|
||||||
|
let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2)), 8008);
|
||||||
|
let mut peers = PeersManager::default();
|
||||||
|
peers.ban_peer(peer);
|
||||||
|
peers.add_discovered_node(peer, socket_addr);
|
||||||
|
|
||||||
|
match event!(peers) {
|
||||||
|
PeerAction::BanPeer { peer_id } => {
|
||||||
|
assert_eq!(peer_id, peer);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
poll_fn(|cx| {
|
||||||
|
assert!(peers.poll(cx).is_pending());
|
||||||
|
Poll::Ready(())
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_reputation_change() {
|
||||||
|
let peer = PeerId::random();
|
||||||
|
let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2)), 8008);
|
||||||
|
let mut peers = PeersManager::default();
|
||||||
|
peers.add_discovered_node(peer, socket_addr);
|
||||||
|
|
||||||
|
match event!(peers) {
|
||||||
|
PeerAction::Connect { peer_id, remote_addr } => {
|
||||||
|
assert_eq!(peer_id, peer);
|
||||||
|
assert_eq!(remote_addr, socket_addr);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
peers.apply_reputation_change(&peer, ReputationChangeKind::BadProtocol);
|
||||||
|
|
||||||
|
let p = peers.peers.get(&peer).unwrap();
|
||||||
|
assert!(p.is_banned());
|
||||||
|
|
||||||
|
match event!(peers) {
|
||||||
|
PeerAction::Disconnect { peer_id, .. } => {
|
||||||
|
assert_eq!(peer_id, peer);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match event!(peers) {
|
||||||
|
PeerAction::BanPeer { peer_id } => {
|
||||||
|
assert_eq!(peer_id, peer);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_reputation_change_connected() {
|
||||||
|
let peer = PeerId::random();
|
||||||
|
let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2)), 8008);
|
||||||
|
let mut peers = PeersManager::default();
|
||||||
|
peers.add_discovered_node(peer, socket_addr);
|
||||||
|
|
||||||
|
match event!(peers) {
|
||||||
|
PeerAction::Connect { peer_id, remote_addr } => {
|
||||||
|
assert_eq!(peer_id, peer);
|
||||||
|
assert_eq!(remote_addr, socket_addr);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
let p = peers.peers.get_mut(&peer).unwrap();
|
||||||
|
assert_eq!(p.state, PeerConnectionState::Out);
|
||||||
|
|
||||||
|
peers.apply_reputation_change(&peer, ReputationChangeKind::BadProtocol);
|
||||||
|
|
||||||
|
let p = peers.peers.get(&peer).unwrap();
|
||||||
|
assert_eq!(p.state, PeerConnectionState::DisconnectingOut);
|
||||||
|
assert!(p.is_banned());
|
||||||
|
|
||||||
|
peers.on_disconnected(&peer);
|
||||||
|
|
||||||
|
let p = peers.peers.get(&peer).unwrap();
|
||||||
|
assert_eq!(p.state, PeerConnectionState::Idle);
|
||||||
|
assert!(p.is_banned());
|
||||||
|
|
||||||
|
match event!(peers) {
|
||||||
|
PeerAction::Disconnect { peer_id, .. } => {
|
||||||
|
assert_eq!(peer_id, peer);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_discovery_ban_list() {
|
async fn test_discovery_ban_list() {
|
||||||
let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2));
|
let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2));
|
||||||
|
|||||||
@ -3,6 +3,9 @@
|
|||||||
/// The type that tracks the reputation score.
|
/// The type that tracks the reputation score.
|
||||||
pub(crate) type Reputation = i32;
|
pub(crate) type Reputation = i32;
|
||||||
|
|
||||||
|
/// The default reputation of a peer
|
||||||
|
pub(crate) const DEFAULT_REPUTATION: Reputation = 0;
|
||||||
|
|
||||||
/// The minimal unit we're measuring reputation
|
/// The minimal unit we're measuring reputation
|
||||||
const REPUTATION_UNIT: i32 = -1024;
|
const REPUTATION_UNIT: i32 = -1024;
|
||||||
|
|
||||||
@ -24,6 +27,12 @@ const BAD_MESSAGE_REPUTATION_CHANGE: i32 = 8 * REPUTATION_UNIT;
|
|||||||
/// The reputation change to apply to a peer which violates protocol rules: minimal reputation
|
/// The reputation change to apply to a peer which violates protocol rules: minimal reputation
|
||||||
const BAD_PROTOCOL_REPUTATION_CHANGE: i32 = i32::MIN;
|
const BAD_PROTOCOL_REPUTATION_CHANGE: i32 = i32::MIN;
|
||||||
|
|
||||||
|
/// Returns `true` if the given reputation is below the [`BANNED_REPUTATION`] threshold
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn is_banned_reputation(reputation: i32) -> bool {
|
||||||
|
reputation < BANNED_REPUTATION
|
||||||
|
}
|
||||||
|
|
||||||
/// Various kinds of reputation changes.
|
/// Various kinds of reputation changes.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub enum ReputationChangeKind {
|
pub enum ReputationChangeKind {
|
||||||
|
|||||||
@ -285,6 +285,8 @@ where
|
|||||||
self.queued_messages.push_back(StateAction::Disconnect { peer_id, reason: None });
|
self.queued_messages.push_back(StateAction::Disconnect { peer_id, reason: None });
|
||||||
}
|
}
|
||||||
PeerAction::DiscoveryBan { peer_id, ip_addr } => self.ban_discovery(peer_id, ip_addr),
|
PeerAction::DiscoveryBan { peer_id, ip_addr } => self.ban_discovery(peer_id, ip_addr),
|
||||||
|
PeerAction::BanPeer { .. } => {}
|
||||||
|
PeerAction::UnBanPeer { .. } => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user