mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-16 03:12:41 +00:00
Rename eth2_libp2p to lighthouse_network (#2702)
## Description The `eth2_libp2p` crate was originally named and designed to incorporate a simple libp2p integration into lighthouse. Since its origins the crates purpose has expanded dramatically. It now houses a lot more sophistication that is specific to lighthouse and no longer just a libp2p integration. As of this writing it currently houses the following high-level lighthouse-specific logic: - Lighthouse's implementation of the eth2 RPC protocol and specific encodings/decodings - Integration and handling of ENRs with respect to libp2p and eth2 - Lighthouse's discovery logic, its integration with discv5 and logic about searching and handling peers. - Lighthouse's peer manager - This is a large module handling various aspects of Lighthouse's network, such as peer scoring, handling pings and metadata, connection maintenance and recording, etc. - Lighthouse's peer database - This is a collection of information stored for each individual peer which is specific to lighthouse. We store connection state, sync state, last seen ips and scores etc. The data stored for each peer is designed for various elements of the lighthouse code base such as syncing and the http api. - Gossipsub scoring - This stores a collection of gossipsub 1.1 scoring mechanisms that are continuously analyssed and updated based on the ethereum 2 networks and how Lighthouse performs on these networks. - Lighthouse specific types for managing gossipsub topics, sync status and ENR fields - Lighthouse's network HTTP API metrics - A collection of metrics for lighthouse network monitoring - Lighthouse's custom configuration of all networking protocols, RPC, gossipsub, discovery, identify and libp2p. Therefore it makes sense to rename the crate to be more akin to its current purposes, simply that it manages the majority of Lighthouse's network stack. This PR renames this crate to `lighthouse_network` Co-authored-by: Paul Hauner <paul@paulhauner.com>
This commit is contained in:
1323
beacon_node/lighthouse_network/src/peer_manager/mod.rs
Normal file
1323
beacon_node/lighthouse_network/src/peer_manager/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
1842
beacon_node/lighthouse_network/src/peer_manager/peerdb.rs
Normal file
1842
beacon_node/lighthouse_network/src/peer_manager/peerdb.rs
Normal file
File diff suppressed because it is too large
Load Diff
201
beacon_node/lighthouse_network/src/peer_manager/peerdb/client.rs
Normal file
201
beacon_node/lighthouse_network/src/peer_manager/peerdb/client.rs
Normal file
@@ -0,0 +1,201 @@
|
||||
//! Known Ethereum 2.0 clients and their fingerprints.
|
||||
//!
|
||||
//! Currently using identify to fingerprint.
|
||||
|
||||
use libp2p::identify::IdentifyInfo;
|
||||
use serde::Serialize;
|
||||
use strum::{AsRefStr, AsStaticStr};
|
||||
|
||||
/// Various client and protocol information related to a node.
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct Client {
|
||||
/// The client's name (Ex: lighthouse, prism, nimbus, etc)
|
||||
pub kind: ClientKind,
|
||||
/// The client's version.
|
||||
pub version: String,
|
||||
/// The OS version of the client.
|
||||
pub os_version: String,
|
||||
/// The libp2p protocol version.
|
||||
pub protocol_version: String,
|
||||
/// Identify agent string
|
||||
pub agent_string: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, PartialEq, AsRefStr, AsStaticStr)]
|
||||
pub enum ClientKind {
|
||||
/// A lighthouse node (the best kind).
|
||||
Lighthouse,
|
||||
/// A Nimbus node.
|
||||
Nimbus,
|
||||
/// A Teku node.
|
||||
Teku,
|
||||
/// A Prysm node.
|
||||
Prysm,
|
||||
/// A lodestar node.
|
||||
Lodestar,
|
||||
/// An unknown client.
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl Default for Client {
|
||||
fn default() -> Self {
|
||||
Client {
|
||||
kind: ClientKind::Unknown,
|
||||
version: "unknown".into(),
|
||||
os_version: "unknown".into(),
|
||||
protocol_version: "unknown".into(),
|
||||
agent_string: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Builds a `Client` from `IdentifyInfo`.
|
||||
pub fn from_identify_info(info: &IdentifyInfo) -> Self {
|
||||
let (kind, version, os_version) = client_from_agent_version(&info.agent_version);
|
||||
|
||||
Client {
|
||||
kind,
|
||||
version,
|
||||
os_version,
|
||||
protocol_version: info.protocol_version.clone(),
|
||||
agent_string: Some(info.agent_version.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Client {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.kind {
|
||||
ClientKind::Lighthouse => write!(
|
||||
f,
|
||||
"Lighthouse: version: {}, os_version: {}",
|
||||
self.version, self.os_version
|
||||
),
|
||||
ClientKind::Teku => write!(
|
||||
f,
|
||||
"Teku: version: {}, os_version: {}",
|
||||
self.version, self.os_version
|
||||
),
|
||||
ClientKind::Nimbus => write!(
|
||||
f,
|
||||
"Nimbus: version: {}, os_version: {}",
|
||||
self.version, self.os_version
|
||||
),
|
||||
ClientKind::Prysm => write!(
|
||||
f,
|
||||
"Prysm: version: {}, os_version: {}",
|
||||
self.version, self.os_version
|
||||
),
|
||||
ClientKind::Lodestar => write!(f, "Lodestar: version: {}", self.version),
|
||||
ClientKind::Unknown => {
|
||||
if let Some(agent_string) = &self.agent_string {
|
||||
write!(f, "Unknown: {}", agent_string)
|
||||
} else {
|
||||
write!(f, "Unknown")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ClientKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(self.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
// helper function to identify clients from their agent_version. Returns the client
|
||||
// kind and it's associated version and the OS kind.
|
||||
fn client_from_agent_version(agent_version: &str) -> (ClientKind, String, String) {
|
||||
let mut agent_split = agent_version.split('/');
|
||||
match agent_split.next() {
|
||||
Some("Lighthouse") => {
|
||||
let kind = ClientKind::Lighthouse;
|
||||
let mut version = String::from("unknown");
|
||||
let mut os_version = version.clone();
|
||||
if let Some(agent_version) = agent_split.next() {
|
||||
version = agent_version.into();
|
||||
if let Some(agent_os_version) = agent_split.next() {
|
||||
os_version = agent_os_version.into();
|
||||
}
|
||||
}
|
||||
(kind, version, os_version)
|
||||
}
|
||||
Some("teku") => {
|
||||
let kind = ClientKind::Teku;
|
||||
let mut version = String::from("unknown");
|
||||
let mut os_version = version.clone();
|
||||
if agent_split.next().is_some() {
|
||||
if let Some(agent_version) = agent_split.next() {
|
||||
version = agent_version.into();
|
||||
if let Some(agent_os_version) = agent_split.next() {
|
||||
os_version = agent_os_version.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
(kind, version, os_version)
|
||||
}
|
||||
Some("github.com") => {
|
||||
let kind = ClientKind::Prysm;
|
||||
let unknown = String::from("unknown");
|
||||
(kind, unknown.clone(), unknown)
|
||||
}
|
||||
Some("Prysm") => {
|
||||
let kind = ClientKind::Prysm;
|
||||
let mut version = String::from("unknown");
|
||||
let mut os_version = version.clone();
|
||||
if agent_split.next().is_some() {
|
||||
if let Some(agent_version) = agent_split.next() {
|
||||
version = agent_version.into();
|
||||
if let Some(agent_os_version) = agent_split.next() {
|
||||
os_version = agent_os_version.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
(kind, version, os_version)
|
||||
}
|
||||
Some("nimbus") => {
|
||||
let kind = ClientKind::Nimbus;
|
||||
let mut version = String::from("unknown");
|
||||
let mut os_version = version.clone();
|
||||
if agent_split.next().is_some() {
|
||||
if let Some(agent_version) = agent_split.next() {
|
||||
version = agent_version.into();
|
||||
if let Some(agent_os_version) = agent_split.next() {
|
||||
os_version = agent_os_version.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
(kind, version, os_version)
|
||||
}
|
||||
Some("nim-libp2p") => {
|
||||
let kind = ClientKind::Nimbus;
|
||||
let mut version = String::from("unknown");
|
||||
let mut os_version = version.clone();
|
||||
if let Some(agent_version) = agent_split.next() {
|
||||
version = agent_version.into();
|
||||
if let Some(agent_os_version) = agent_split.next() {
|
||||
os_version = agent_os_version.into();
|
||||
}
|
||||
}
|
||||
(kind, version, os_version)
|
||||
}
|
||||
Some("js-libp2p") => {
|
||||
let kind = ClientKind::Lodestar;
|
||||
let mut version = String::from("unknown");
|
||||
let mut os_version = version.clone();
|
||||
if let Some(agent_version) = agent_split.next() {
|
||||
version = agent_version.into();
|
||||
if let Some(agent_os_version) = agent_split.next() {
|
||||
os_version = agent_os_version.into();
|
||||
}
|
||||
}
|
||||
(kind, version, os_version)
|
||||
}
|
||||
_ => {
|
||||
let unknown = String::from("unknown");
|
||||
(ClientKind::Unknown, unknown.clone(), unknown)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,505 @@
|
||||
use super::client::Client;
|
||||
use super::score::{PeerAction, Score, ScoreState};
|
||||
use super::sync_status::SyncStatus;
|
||||
use crate::Multiaddr;
|
||||
use crate::{rpc::MetaData, types::Subnet};
|
||||
use discv5::Enr;
|
||||
use serde::{
|
||||
ser::{SerializeStruct, Serializer},
|
||||
Serialize,
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::time::Instant;
|
||||
use strum::AsRefStr;
|
||||
use types::EthSpec;
|
||||
use PeerConnectionStatus::*;
|
||||
|
||||
/// Information about a given connected peer.
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(bound = "T: EthSpec")]
|
||||
pub struct PeerInfo<T: EthSpec> {
|
||||
/// The connection status of the peer
|
||||
_status: PeerStatus,
|
||||
/// The peers reputation
|
||||
score: Score,
|
||||
/// Client managing this peer
|
||||
client: Client,
|
||||
/// Connection status of this peer
|
||||
connection_status: PeerConnectionStatus,
|
||||
/// The known listening addresses of this peer. This is given by identify and can be arbitrary
|
||||
/// (including local IPs).
|
||||
listening_addresses: Vec<Multiaddr>,
|
||||
/// This is addresses we have physically seen and this is what we use for banning/un-banning
|
||||
/// peers.
|
||||
seen_addresses: HashSet<SocketAddr>,
|
||||
/// The current syncing state of the peer. The state may be determined after it's initial
|
||||
/// connection.
|
||||
sync_status: SyncStatus,
|
||||
/// The ENR subnet bitfield of the peer. This may be determined after it's initial
|
||||
/// connection.
|
||||
meta_data: Option<MetaData<T>>,
|
||||
/// Subnets the peer is connected to.
|
||||
subnets: HashSet<Subnet>,
|
||||
/// The time we would like to retain this peer. After this time, the peer is no longer
|
||||
/// necessary.
|
||||
#[serde(skip)]
|
||||
min_ttl: Option<Instant>,
|
||||
/// Is the peer a trusted peer.
|
||||
is_trusted: bool,
|
||||
/// Direction of the first connection of the last (or current) connected session with this peer.
|
||||
/// None if this peer was never connected.
|
||||
connection_direction: Option<ConnectionDirection>,
|
||||
/// The enr of the peer, if known.
|
||||
enr: Option<Enr>,
|
||||
}
|
||||
|
||||
impl<TSpec: EthSpec> Default for PeerInfo<TSpec> {
|
||||
fn default() -> PeerInfo<TSpec> {
|
||||
PeerInfo {
|
||||
_status: Default::default(),
|
||||
score: Score::default(),
|
||||
client: Client::default(),
|
||||
connection_status: Default::default(),
|
||||
listening_addresses: Vec::new(),
|
||||
seen_addresses: HashSet::new(),
|
||||
subnets: HashSet::new(),
|
||||
sync_status: SyncStatus::Unknown,
|
||||
meta_data: None,
|
||||
min_ttl: None,
|
||||
is_trusted: false,
|
||||
connection_direction: None,
|
||||
enr: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> PeerInfo<T> {
|
||||
/// Return a PeerInfo struct for a trusted peer.
|
||||
pub fn trusted_peer_info() -> Self {
|
||||
PeerInfo {
|
||||
score: Score::max_score(),
|
||||
is_trusted: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns if the peer is subscribed to a given `Subnet` from the metadata attnets/syncnets field.
|
||||
pub fn on_subnet_metadata(&self, subnet: &Subnet) -> bool {
|
||||
if let Some(meta_data) = &self.meta_data {
|
||||
match subnet {
|
||||
Subnet::Attestation(id) => {
|
||||
return meta_data.attnets().get(**id as usize).unwrap_or(false)
|
||||
}
|
||||
Subnet::SyncCommittee(id) => {
|
||||
return meta_data
|
||||
.syncnets()
|
||||
.map_or(false, |s| s.get(**id as usize).unwrap_or(false))
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Obtains the client of the peer.
|
||||
pub fn client(&self) -> &Client {
|
||||
&self.client
|
||||
}
|
||||
|
||||
/// Returns the listening addresses of the Peer.
|
||||
pub fn listening_addresses(&self) -> &Vec<Multiaddr> {
|
||||
&self.listening_addresses
|
||||
}
|
||||
|
||||
/// Returns the connection direction for the peer.
|
||||
pub fn connection_direction(&self) -> Option<&ConnectionDirection> {
|
||||
self.connection_direction.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the sync status of the peer.
|
||||
pub fn sync_status(&self) -> &SyncStatus {
|
||||
&self.sync_status
|
||||
}
|
||||
|
||||
/// Returns the metadata for the peer if currently known.
|
||||
pub fn meta_data(&self) -> Option<&MetaData<T>> {
|
||||
self.meta_data.as_ref()
|
||||
}
|
||||
|
||||
/// Returns whether the peer is a trusted peer or not.
|
||||
pub fn is_trusted(&self) -> bool {
|
||||
self.is_trusted
|
||||
}
|
||||
|
||||
/// The time a peer is expected to be useful until for an attached validator. If this is set to
|
||||
/// None, the peer is not required for any upcoming duty.
|
||||
pub fn min_ttl(&self) -> Option<&Instant> {
|
||||
self.min_ttl.as_ref()
|
||||
}
|
||||
|
||||
/// The ENR of the peer if it is known.
|
||||
pub fn enr(&self) -> Option<&Enr> {
|
||||
self.enr.as_ref()
|
||||
}
|
||||
|
||||
/// Returns if the peer is subscribed to a given `Subnet` from the gossipsub subscriptions.
|
||||
pub fn on_subnet_gossipsub(&self, subnet: &Subnet) -> bool {
|
||||
self.subnets.contains(subnet)
|
||||
}
|
||||
|
||||
/// Returns the seen addresses of the peer.
|
||||
pub fn seen_addresses(&self) -> impl Iterator<Item = &SocketAddr> + '_ {
|
||||
self.seen_addresses.iter()
|
||||
}
|
||||
|
||||
/// Returns a list of seen IP addresses for the peer.
|
||||
pub fn seen_ip_addresses(&self) -> impl Iterator<Item = IpAddr> + '_ {
|
||||
self.seen_addresses
|
||||
.iter()
|
||||
.map(|socket_addr| socket_addr.ip())
|
||||
}
|
||||
|
||||
/// Returns the connection status of the peer.
|
||||
pub fn connection_status(&self) -> &PeerConnectionStatus {
|
||||
&self.connection_status
|
||||
}
|
||||
|
||||
/// Reports if this peer has some future validator duty in which case it is valuable to keep it.
|
||||
pub fn has_future_duty(&self) -> bool {
|
||||
self.min_ttl.map_or(false, |i| i >= Instant::now())
|
||||
}
|
||||
|
||||
/// Returns score of the peer.
|
||||
pub fn score(&self) -> &Score {
|
||||
&self.score
|
||||
}
|
||||
|
||||
/// Returns the state of the peer based on the score.
|
||||
pub(crate) fn score_state(&self) -> ScoreState {
|
||||
self.score.state()
|
||||
}
|
||||
|
||||
/// Returns true if the gossipsub score is sufficient.
|
||||
pub fn is_good_gossipsub_peer(&self) -> bool {
|
||||
self.score.is_good_gossipsub_peer()
|
||||
}
|
||||
|
||||
/* Peer connection status API */
|
||||
|
||||
/// Checks if the status is connected.
|
||||
pub fn is_connected(&self) -> bool {
|
||||
matches!(
|
||||
self.connection_status,
|
||||
PeerConnectionStatus::Connected { .. }
|
||||
)
|
||||
}
|
||||
|
||||
/// Checks if the status is connected.
|
||||
pub fn is_dialing(&self) -> bool {
|
||||
matches!(self.connection_status, PeerConnectionStatus::Dialing { .. })
|
||||
}
|
||||
|
||||
/// The peer is either connected or in the process of being dialed.
|
||||
pub fn is_connected_or_dialing(&self) -> bool {
|
||||
self.is_connected() || self.is_dialing()
|
||||
}
|
||||
|
||||
/// Checks if the connection status is banned. This can lag behind the score state
|
||||
/// temporarily.
|
||||
pub fn is_banned(&self) -> bool {
|
||||
matches!(self.connection_status, PeerConnectionStatus::Banned { .. })
|
||||
}
|
||||
|
||||
/// Checks if the peer's score is banned.
|
||||
pub fn score_is_banned(&self) -> bool {
|
||||
matches!(self.score.state(), ScoreState::Banned)
|
||||
}
|
||||
|
||||
/// Checks if the status is disconnected.
|
||||
pub fn is_disconnected(&self) -> bool {
|
||||
matches!(self.connection_status, Disconnected { .. })
|
||||
}
|
||||
|
||||
/// Checks if the peer is outbound-only
|
||||
pub fn is_outbound_only(&self) -> bool {
|
||||
matches!(self.connection_status, Connected {n_in, n_out} if n_in == 0 && n_out > 0)
|
||||
}
|
||||
|
||||
/// Returns the number of connections with this peer.
|
||||
pub fn connections(&self) -> (u8, u8) {
|
||||
match self.connection_status {
|
||||
Connected { n_in, n_out } => (n_in, n_out),
|
||||
_ => (0, 0),
|
||||
}
|
||||
}
|
||||
|
||||
/* Mutable Functions */
|
||||
|
||||
/// Updates the sync status. Returns true if the status was changed.
|
||||
// VISIBILITY: Both the peer manager the network sync is able to update the sync state of a peer
|
||||
pub fn update_sync_status(&mut self, sync_status: SyncStatus) -> bool {
|
||||
self.sync_status.update(sync_status)
|
||||
}
|
||||
|
||||
/// Sets the client of the peer.
|
||||
// VISIBILITY: The peer manager is able to set the client
|
||||
pub(in crate::peer_manager) fn set_client(&mut self, client: Client) {
|
||||
self.client = client
|
||||
}
|
||||
|
||||
/// Replaces the current listening addresses with those specified, returning the current
|
||||
/// listening addresses.
|
||||
// VISIBILITY: The peer manager is able to set the listening addresses
|
||||
pub(in crate::peer_manager) fn set_listening_addresses(
|
||||
&mut self,
|
||||
listening_addresses: Vec<Multiaddr>,
|
||||
) -> Vec<Multiaddr> {
|
||||
std::mem::replace(&mut self.listening_addresses, listening_addresses)
|
||||
}
|
||||
|
||||
/// Sets an explicit value for the meta data.
|
||||
// VISIBILITY: The peer manager is able to adjust the meta_data
|
||||
pub(in crate::peer_manager) fn set_meta_data(&mut self, meta_data: MetaData<T>) {
|
||||
self.meta_data = Some(meta_data)
|
||||
}
|
||||
|
||||
/// Sets the connection status of the peer.
|
||||
pub(super) fn set_connection_status(&mut self, connection_status: PeerConnectionStatus) {
|
||||
self.connection_status = connection_status
|
||||
}
|
||||
|
||||
/// Sets the ENR of the peer if one is known.
|
||||
pub(super) fn set_enr(&mut self, enr: Enr) {
|
||||
self.enr = Some(enr)
|
||||
}
|
||||
|
||||
/// Sets the time that the peer is expected to be needed until for an attached validator duty.
|
||||
pub(super) fn set_min_ttl(&mut self, min_ttl: Instant) {
|
||||
self.min_ttl = Some(min_ttl)
|
||||
}
|
||||
|
||||
/// Adds a known subnet for the peer.
|
||||
pub(super) fn insert_subnet(&mut self, subnet: Subnet) {
|
||||
self.subnets.insert(subnet);
|
||||
}
|
||||
|
||||
/// Removes a subnet from the peer.
|
||||
pub(super) fn remove_subnet(&mut self, subnet: &Subnet) {
|
||||
self.subnets.remove(subnet);
|
||||
}
|
||||
|
||||
/// Removes all subnets from the peer.
|
||||
pub(super) fn clear_subnets(&mut self) {
|
||||
self.subnets.clear()
|
||||
}
|
||||
|
||||
/// Applies decay rates to a non-trusted peer's score.
|
||||
pub(super) fn score_update(&mut self) {
|
||||
if !self.is_trusted {
|
||||
self.score.update()
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply peer action to a non-trusted peer's score.
|
||||
// VISIBILITY: The peer manager is able to modify the score of a peer.
|
||||
pub(in crate::peer_manager) fn apply_peer_action_to_score(&mut self, peer_action: PeerAction) {
|
||||
if !self.is_trusted {
|
||||
self.score.apply_peer_action(peer_action)
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the gossipsub score with a new score. Optionally ignore the gossipsub score.
|
||||
pub(super) fn update_gossipsub_score(&mut self, new_score: f64, ignore: bool) {
|
||||
self.score.update_gossipsub_score(new_score, ignore);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// Resets the peers score.
|
||||
pub fn reset_score(&mut self) {
|
||||
self.score.test_reset();
|
||||
}
|
||||
|
||||
/// Modifies the status to Dialing
|
||||
/// Returns an error if the current state is unexpected.
|
||||
pub(super) fn dialing_peer(&mut self) -> Result<(), &'static str> {
|
||||
match &mut self.connection_status {
|
||||
Connected { .. } => return Err("Dialing connected peer"),
|
||||
Dialing { .. } => return Err("Dialing an already dialing peer"),
|
||||
Disconnecting { .. } => return Err("Dialing a disconnecting peer"),
|
||||
Disconnected { .. } | Banned { .. } | Unknown => {}
|
||||
}
|
||||
self.connection_status = Dialing {
|
||||
since: Instant::now(),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Modifies the status to Connected and increases the number of ingoing
|
||||
/// connections by one
|
||||
pub(super) fn connect_ingoing(&mut self, seen_address: Option<SocketAddr>) {
|
||||
match &mut self.connection_status {
|
||||
Connected { n_in, .. } => *n_in += 1,
|
||||
Disconnected { .. }
|
||||
| Banned { .. }
|
||||
| Dialing { .. }
|
||||
| Disconnecting { .. }
|
||||
| Unknown => {
|
||||
self.connection_status = Connected { n_in: 1, n_out: 0 };
|
||||
self.connection_direction = Some(ConnectionDirection::Incoming);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(socket_addr) = seen_address {
|
||||
self.seen_addresses.insert(socket_addr);
|
||||
}
|
||||
}
|
||||
|
||||
/// Modifies the status to Connected and increases the number of outgoing
|
||||
/// connections by one
|
||||
pub(super) fn connect_outgoing(&mut self, seen_address: Option<SocketAddr>) {
|
||||
match &mut self.connection_status {
|
||||
Connected { n_out, .. } => *n_out += 1,
|
||||
Disconnected { .. }
|
||||
| Banned { .. }
|
||||
| Dialing { .. }
|
||||
| Disconnecting { .. }
|
||||
| Unknown => {
|
||||
self.connection_status = Connected { n_in: 0, n_out: 1 };
|
||||
self.connection_direction = Some(ConnectionDirection::Outgoing);
|
||||
}
|
||||
}
|
||||
if let Some(ip_addr) = seen_address {
|
||||
self.seen_addresses.insert(ip_addr);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// Add an f64 to a non-trusted peer's score abiding by the limits.
|
||||
pub fn add_to_score(&mut self, score: f64) {
|
||||
if !self.is_trusted {
|
||||
self.score.test_add(score)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn set_gossipsub_score(&mut self, score: f64) {
|
||||
self.score.set_gossipsub_score(score);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
/// The current health status of the peer.
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/// Connection Direction of connection.
|
||||
#[derive(Debug, Clone, Serialize, AsRefStr)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum ConnectionDirection {
|
||||
/// The connection was established by a peer dialing us.
|
||||
Incoming,
|
||||
/// The connection was established by us dialing a peer.
|
||||
Outgoing,
|
||||
}
|
||||
|
||||
/// Connection Status of the peer.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PeerConnectionStatus {
|
||||
/// The peer is connected.
|
||||
Connected {
|
||||
/// number of ingoing connections.
|
||||
n_in: u8,
|
||||
/// number of outgoing connections.
|
||||
n_out: u8,
|
||||
},
|
||||
/// The peer is being disconnected.
|
||||
Disconnecting {
|
||||
// After the disconnection the peer will be considered banned.
|
||||
to_ban: bool,
|
||||
},
|
||||
/// The peer has disconnected.
|
||||
Disconnected {
|
||||
/// last time the peer was connected or discovered.
|
||||
since: Instant,
|
||||
},
|
||||
/// The peer has been banned and is disconnected.
|
||||
Banned {
|
||||
/// moment when the peer was banned.
|
||||
since: Instant,
|
||||
},
|
||||
/// We are currently dialing this peer.
|
||||
Dialing {
|
||||
/// time since we last communicated with the peer.
|
||||
since: Instant,
|
||||
},
|
||||
/// The connection status has not been specified.
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// Serialization for http requests.
|
||||
impl Serialize for PeerConnectionStatus {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let mut s = serializer.serialize_struct("connection_status", 6)?;
|
||||
match self {
|
||||
Connected { n_in, n_out } => {
|
||||
s.serialize_field("status", "connected")?;
|
||||
s.serialize_field("connections_in", n_in)?;
|
||||
s.serialize_field("connections_out", n_out)?;
|
||||
s.serialize_field("last_seen", &0)?;
|
||||
s.end()
|
||||
}
|
||||
Disconnecting { .. } => {
|
||||
s.serialize_field("status", "disconnecting")?;
|
||||
s.serialize_field("connections_in", &0)?;
|
||||
s.serialize_field("connections_out", &0)?;
|
||||
s.serialize_field("last_seen", &0)?;
|
||||
s.end()
|
||||
}
|
||||
Disconnected { since } => {
|
||||
s.serialize_field("status", "disconnected")?;
|
||||
s.serialize_field("connections_in", &0)?;
|
||||
s.serialize_field("connections_out", &0)?;
|
||||
s.serialize_field("last_seen", &since.elapsed().as_secs())?;
|
||||
s.serialize_field("banned_ips", &Vec::<IpAddr>::new())?;
|
||||
s.end()
|
||||
}
|
||||
Banned { since } => {
|
||||
s.serialize_field("status", "banned")?;
|
||||
s.serialize_field("connections_in", &0)?;
|
||||
s.serialize_field("connections_out", &0)?;
|
||||
s.serialize_field("last_seen", &since.elapsed().as_secs())?;
|
||||
s.end()
|
||||
}
|
||||
Dialing { since } => {
|
||||
s.serialize_field("status", "dialing")?;
|
||||
s.serialize_field("connections_in", &0)?;
|
||||
s.serialize_field("connections_out", &0)?;
|
||||
s.serialize_field("last_seen", &since.elapsed().as_secs())?;
|
||||
s.end()
|
||||
}
|
||||
Unknown => {
|
||||
s.serialize_field("status", "unknown")?;
|
||||
s.serialize_field("connections_in", &0)?;
|
||||
s.serialize_field("connections_out", &0)?;
|
||||
s.serialize_field("last_seen", &0)?;
|
||||
s.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PeerConnectionStatus {
|
||||
fn default() -> Self {
|
||||
PeerConnectionStatus::Unknown
|
||||
}
|
||||
}
|
||||
425
beacon_node/lighthouse_network/src/peer_manager/peerdb/score.rs
Normal file
425
beacon_node/lighthouse_network/src/peer_manager/peerdb/score.rs
Normal file
@@ -0,0 +1,425 @@
|
||||
//! This contains the scoring logic for peers.
|
||||
//!
|
||||
//! A peer's score is a rational number in the range [-100, 100].
|
||||
//!
|
||||
//! As the logic develops this documentation will advance.
|
||||
//!
|
||||
//! The scoring algorithms are currently experimental.
|
||||
use crate::behaviour::gossipsub_scoring_parameters::GREYLIST_THRESHOLD as GOSSIPSUB_GREYLIST_THRESHOLD;
|
||||
use serde::Serialize;
|
||||
use std::time::Instant;
|
||||
use strum::AsRefStr;
|
||||
use tokio::time::Duration;
|
||||
|
||||
lazy_static! {
|
||||
static ref HALFLIFE_DECAY: f64 = -(2.0f64.ln()) / SCORE_HALFLIFE;
|
||||
}
|
||||
|
||||
/// The default score for new peers.
|
||||
pub(crate) const DEFAULT_SCORE: f64 = 0.0;
|
||||
/// The minimum reputation before a peer is disconnected.
|
||||
const MIN_SCORE_BEFORE_DISCONNECT: f64 = -20.0;
|
||||
/// The minimum reputation before a peer is banned.
|
||||
const MIN_SCORE_BEFORE_BAN: f64 = -50.0;
|
||||
/// If a peer has a lighthouse score below this constant all other score parts will get ignored and
|
||||
/// the peer will get banned regardless of the other parts.
|
||||
const MIN_LIGHTHOUSE_SCORE_BEFORE_BAN: f64 = -60.0;
|
||||
/// The maximum score a peer can obtain.
|
||||
const MAX_SCORE: f64 = 100.0;
|
||||
/// The minimum score a peer can obtain.
|
||||
const MIN_SCORE: f64 = -100.0;
|
||||
/// The halflife of a peer's score. I.e the number of seconds it takes for the score to decay to half its value.
|
||||
const SCORE_HALFLIFE: f64 = 600.0;
|
||||
/// The number of seconds we ban a peer for before their score begins to decay.
|
||||
const BANNED_BEFORE_DECAY: Duration = Duration::from_secs(12 * 3600); // 12 hours
|
||||
|
||||
/// We weight negative gossipsub scores in such a way that they never result in a disconnect by
|
||||
/// themselves. This "solves" the problem of non-decaying gossipsub scores for disconnected peers.
|
||||
const GOSSIPSUB_NEGATIVE_SCORE_WEIGHT: f64 =
|
||||
(MIN_SCORE_BEFORE_DISCONNECT + 1.0) / GOSSIPSUB_GREYLIST_THRESHOLD;
|
||||
const GOSSIPSUB_POSITIVE_SCORE_WEIGHT: f64 = GOSSIPSUB_NEGATIVE_SCORE_WEIGHT;
|
||||
|
||||
/// A collection of actions a peer can perform which will adjust its score.
|
||||
/// Each variant has an associated score change.
|
||||
// To easily assess the behaviour of scores changes the number of variants should stay low, and
|
||||
// somewhat generic.
|
||||
#[derive(Debug, Clone, Copy, AsRefStr)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum PeerAction {
|
||||
/// We should not communicate more with this peer.
|
||||
/// This action will cause the peer to get banned.
|
||||
Fatal,
|
||||
/// This peer's action is not malicious but will not be tolerated. A few occurrences will cause
|
||||
/// the peer to get kicked.
|
||||
/// NOTE: ~5 occurrences will get the peer banned
|
||||
LowToleranceError,
|
||||
/// An error occurred with this peer but it is not necessarily malicious.
|
||||
/// We have high tolerance for this actions: several occurrences are needed for a peer to get
|
||||
/// kicked.
|
||||
/// NOTE: ~10 occurrences will get the peer banned
|
||||
MidToleranceError,
|
||||
/// An error occurred with this peer but it is not necessarily malicious.
|
||||
/// We have high tolerance for this actions: several occurrences are needed for a peer to get
|
||||
/// kicked.
|
||||
/// NOTE: ~50 occurrences will get the peer banned
|
||||
HighToleranceError,
|
||||
}
|
||||
|
||||
/// Service reporting a `PeerAction` for a peer.
|
||||
#[derive(Debug)]
|
||||
pub enum ReportSource {
|
||||
Gossipsub,
|
||||
RPC,
|
||||
Processor,
|
||||
SyncService,
|
||||
PeerManager,
|
||||
}
|
||||
|
||||
impl From<ReportSource> for &'static str {
|
||||
fn from(report_source: ReportSource) -> &'static str {
|
||||
match report_source {
|
||||
ReportSource::Gossipsub => "gossipsub",
|
||||
ReportSource::RPC => "rpc_error",
|
||||
ReportSource::Processor => "processor",
|
||||
ReportSource::SyncService => "sync",
|
||||
ReportSource::PeerManager => "peer_manager",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for PeerAction {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
PeerAction::Fatal => write!(f, "Fatal"),
|
||||
PeerAction::LowToleranceError => write!(f, "Low Tolerance Error"),
|
||||
PeerAction::MidToleranceError => write!(f, "Mid Tolerance Error"),
|
||||
PeerAction::HighToleranceError => write!(f, "High Tolerance Error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The expected state of the peer given the peer's score.
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub(crate) enum ScoreState {
|
||||
/// We are content with the peers performance. We permit connections and messages.
|
||||
Healthy,
|
||||
/// The peer should be disconnected. We allow re-connections if the peer is persistent.
|
||||
Disconnected,
|
||||
/// The peer is banned. We disallow new connections until it's score has decayed into a
|
||||
/// tolerable threshold.
|
||||
Banned,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ScoreState {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ScoreState::Healthy => write!(f, "Healthy"),
|
||||
ScoreState::Banned => write!(f, "Banned"),
|
||||
ScoreState::Disconnected => write!(f, "Disconnected"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A peer's score (perceived potential usefulness).
|
||||
///
|
||||
/// This simplistic version consists of a global score per peer which decays to 0 over time. The
|
||||
/// decay rate applies equally to positive and negative scores.
|
||||
#[derive(PartialEq, Clone, Debug, Serialize)]
|
||||
pub struct RealScore {
|
||||
/// The global score.
|
||||
// NOTE: In the future we may separate this into sub-scores involving the RPC, Gossipsub and
|
||||
// lighthouse.
|
||||
lighthouse_score: f64,
|
||||
gossipsub_score: f64,
|
||||
/// We ignore the negative gossipsub scores of some peers to allow decaying without
|
||||
/// disconnecting.
|
||||
ignore_negative_gossipsub_score: bool,
|
||||
score: f64,
|
||||
/// The time the score was last updated to perform time-based adjustments such as score-decay.
|
||||
#[serde(skip)]
|
||||
last_updated: Instant,
|
||||
}
|
||||
|
||||
impl Default for RealScore {
|
||||
fn default() -> Self {
|
||||
RealScore {
|
||||
lighthouse_score: DEFAULT_SCORE,
|
||||
gossipsub_score: DEFAULT_SCORE,
|
||||
score: DEFAULT_SCORE,
|
||||
last_updated: Instant::now(),
|
||||
ignore_negative_gossipsub_score: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RealScore {
|
||||
/// Access to the underlying score.
|
||||
fn recompute_score(&mut self) {
|
||||
self.score = self.lighthouse_score;
|
||||
if self.lighthouse_score <= MIN_LIGHTHOUSE_SCORE_BEFORE_BAN {
|
||||
//ignore all other scores, i.e. do nothing here
|
||||
} else if self.gossipsub_score >= 0.0 {
|
||||
self.score += self.gossipsub_score * GOSSIPSUB_POSITIVE_SCORE_WEIGHT;
|
||||
} else if !self.ignore_negative_gossipsub_score {
|
||||
self.score += self.gossipsub_score * GOSSIPSUB_NEGATIVE_SCORE_WEIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
fn score(&self) -> f64 {
|
||||
self.score
|
||||
}
|
||||
|
||||
/// Modifies the score based on a peer's action.
|
||||
pub fn apply_peer_action(&mut self, peer_action: PeerAction) {
|
||||
match peer_action {
|
||||
PeerAction::Fatal => self.set_lighthouse_score(MIN_SCORE), // The worst possible score
|
||||
PeerAction::LowToleranceError => self.add(-10.0),
|
||||
PeerAction::MidToleranceError => self.add(-5.0),
|
||||
PeerAction::HighToleranceError => self.add(-1.0),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_lighthouse_score(&mut self, new_score: f64) {
|
||||
self.lighthouse_score = new_score;
|
||||
self.update_state();
|
||||
}
|
||||
|
||||
/// Add an f64 to the score abiding by the limits.
|
||||
fn add(&mut self, score: f64) {
|
||||
let mut new_score = self.lighthouse_score + score;
|
||||
if new_score > MAX_SCORE {
|
||||
new_score = MAX_SCORE;
|
||||
}
|
||||
if new_score < MIN_SCORE {
|
||||
new_score = MIN_SCORE;
|
||||
}
|
||||
|
||||
self.set_lighthouse_score(new_score);
|
||||
}
|
||||
|
||||
fn update_state(&mut self) {
|
||||
let was_not_banned = self.score > MIN_SCORE_BEFORE_BAN;
|
||||
self.recompute_score();
|
||||
if was_not_banned && self.score <= MIN_SCORE_BEFORE_BAN {
|
||||
//we ban this peer for at least BANNED_BEFORE_DECAY seconds
|
||||
self.last_updated += BANNED_BEFORE_DECAY;
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an f64 to the score abiding by the limits.
|
||||
#[cfg(test)]
|
||||
pub fn test_add(&mut self, score: f64) {
|
||||
self.add(score);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
// reset the score
|
||||
pub fn test_reset(&mut self) {
|
||||
self.set_lighthouse_score(0f64);
|
||||
}
|
||||
|
||||
// Set the gossipsub_score to a specific f64.
|
||||
// Used in testing to induce score status changes during a heartbeat.
|
||||
#[cfg(test)]
|
||||
pub fn set_gossipsub_score(&mut self, score: f64) {
|
||||
self.gossipsub_score = score;
|
||||
}
|
||||
|
||||
/// Applies time-based logic such as decay rates to the score.
|
||||
/// This function should be called periodically.
|
||||
pub fn update(&mut self) {
|
||||
self.update_at(Instant::now())
|
||||
}
|
||||
|
||||
/// Applies time-based logic such as decay rates to the score with the given now value.
|
||||
/// This private sub function is mainly used for testing.
|
||||
fn update_at(&mut self, now: Instant) {
|
||||
// Decay the current score
|
||||
// Using exponential decay based on a constant half life.
|
||||
|
||||
// It is important that we use here `checked_duration_since` instead of elapsed, since
|
||||
// we set last_updated to the future when banning peers. Therefore `checked_duration_since`
|
||||
// will return None in this case and the score does not get decayed.
|
||||
if let Some(secs_since_update) = now
|
||||
.checked_duration_since(self.last_updated)
|
||||
.map(|d| d.as_secs())
|
||||
{
|
||||
// e^(-ln(2)/HL*t)
|
||||
let decay_factor = (*HALFLIFE_DECAY * secs_since_update as f64).exp();
|
||||
self.lighthouse_score *= decay_factor;
|
||||
self.last_updated = now;
|
||||
self.update_state();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_gossipsub_score(&mut self, new_score: f64, ignore: bool) {
|
||||
// we only update gossipsub if last_updated is in the past which means either the peer is
|
||||
// not banned or the BANNED_BEFORE_DECAY time is over.
|
||||
if self.last_updated <= Instant::now() {
|
||||
self.gossipsub_score = new_score;
|
||||
self.ignore_negative_gossipsub_score = ignore;
|
||||
self.update_state();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_good_gossipsub_peer(&self) -> bool {
|
||||
self.gossipsub_score >= 0.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Serialize)]
|
||||
pub enum Score {
|
||||
Max,
|
||||
Real(RealScore),
|
||||
}
|
||||
|
||||
impl Default for Score {
|
||||
fn default() -> Self {
|
||||
Self::Real(RealScore::default())
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! apply {
|
||||
( $method:ident $(, $param_name: ident: $param_type: ty)*) => {
|
||||
impl Score {
|
||||
pub fn $method(
|
||||
&mut self, $($param_name: $param_type, )*
|
||||
) {
|
||||
if let Self::Real(score) = self {
|
||||
score.$method($($param_name, )*);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
apply!(apply_peer_action, peer_action: PeerAction);
|
||||
apply!(update);
|
||||
apply!(update_gossipsub_score, new_score: f64, ignore: bool);
|
||||
#[cfg(test)]
|
||||
apply!(test_add, score: f64);
|
||||
#[cfg(test)]
|
||||
apply!(test_reset);
|
||||
#[cfg(test)]
|
||||
apply!(set_gossipsub_score, score: f64);
|
||||
|
||||
impl Score {
|
||||
pub fn score(&self) -> f64 {
|
||||
match self {
|
||||
Self::Max => f64::INFINITY,
|
||||
Self::Real(score) => score.score(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn max_score() -> Self {
|
||||
Self::Max
|
||||
}
|
||||
|
||||
/// Returns the expected state of the peer given it's score.
|
||||
pub(crate) fn state(&self) -> ScoreState {
|
||||
match self.score() {
|
||||
x if x <= MIN_SCORE_BEFORE_BAN => ScoreState::Banned,
|
||||
x if x <= MIN_SCORE_BEFORE_DISCONNECT => ScoreState::Disconnected,
|
||||
_ => ScoreState::Healthy,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_good_gossipsub_peer(&self) -> bool {
|
||||
match self {
|
||||
Self::Max => true,
|
||||
Self::Real(score) => score.is_good_gossipsub_peer(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Score {}
|
||||
|
||||
impl PartialOrd for Score {
|
||||
fn partial_cmp(&self, other: &Score) -> Option<std::cmp::Ordering> {
|
||||
self.score().partial_cmp(&other.score())
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Score {
|
||||
fn cmp(&self, other: &Score) -> std::cmp::Ordering {
|
||||
self.partial_cmp(other).unwrap_or(std::cmp::Ordering::Equal)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Score {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:.2}", self.score())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::float_cmp)]
|
||||
fn test_reputation_change() {
|
||||
let mut score = Score::default();
|
||||
|
||||
// 0 change does not change de reputation
|
||||
//
|
||||
let change = 0.0;
|
||||
score.test_add(change);
|
||||
assert_eq!(score.score(), DEFAULT_SCORE);
|
||||
|
||||
// underflowing change is capped
|
||||
let mut score = Score::default();
|
||||
let change = MIN_SCORE - 50.0;
|
||||
score.test_add(change);
|
||||
assert_eq!(score.score(), MIN_SCORE);
|
||||
|
||||
// overflowing change is capped
|
||||
let mut score = Score::default();
|
||||
let change = MAX_SCORE + 50.0;
|
||||
score.test_add(change);
|
||||
assert_eq!(score.score(), MAX_SCORE);
|
||||
|
||||
// Score adjusts
|
||||
let mut score = Score::default();
|
||||
let change = 1.32;
|
||||
score.test_add(change);
|
||||
assert_eq!(score.score(), DEFAULT_SCORE + change);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::float_cmp)]
|
||||
fn test_ban_time() {
|
||||
let mut score = RealScore::default();
|
||||
let now = Instant::now();
|
||||
|
||||
let change = MIN_SCORE_BEFORE_BAN;
|
||||
score.test_add(change);
|
||||
assert_eq!(score.score(), MIN_SCORE_BEFORE_BAN);
|
||||
|
||||
score.update_at(now + BANNED_BEFORE_DECAY);
|
||||
assert_eq!(score.score(), MIN_SCORE_BEFORE_BAN);
|
||||
|
||||
score.update_at(now + BANNED_BEFORE_DECAY + Duration::from_secs(1));
|
||||
assert!(score.score() > MIN_SCORE_BEFORE_BAN);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_very_negative_gossipsub_score() {
|
||||
let mut score = Score::default();
|
||||
score.update_gossipsub_score(GOSSIPSUB_GREYLIST_THRESHOLD, false);
|
||||
assert!(!score.is_good_gossipsub_peer());
|
||||
assert!(score.score() < 0.0);
|
||||
assert_eq!(score.state(), ScoreState::Healthy);
|
||||
score.test_add(-1.0001);
|
||||
assert_eq!(score.state(), ScoreState::Disconnected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::float_cmp)]
|
||||
fn test_ignored_gossipsub_score() {
|
||||
let mut score = Score::default();
|
||||
score.update_gossipsub_score(GOSSIPSUB_GREYLIST_THRESHOLD, true);
|
||||
assert!(!score.is_good_gossipsub_peer());
|
||||
assert_eq!(score.score(), 0.0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
//! Handles individual sync status for peers.
|
||||
|
||||
use serde::Serialize;
|
||||
use types::{Epoch, Hash256, Slot};
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
/// The current sync status of the peer.
|
||||
pub enum SyncStatus {
|
||||
/// At the current state as our node or ahead of us.
|
||||
Synced { info: SyncInfo },
|
||||
/// The peer has greater knowledge about the canonical chain than we do.
|
||||
Advanced { info: SyncInfo },
|
||||
/// Is behind our current head and not useful for block downloads.
|
||||
Behind { info: SyncInfo },
|
||||
/// This peer is in an incompatible network.
|
||||
IrrelevantPeer,
|
||||
/// Not currently known as a STATUS handshake has not occurred.
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// A relevant peer's sync information.
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct SyncInfo {
|
||||
pub head_slot: Slot,
|
||||
pub head_root: Hash256,
|
||||
pub finalized_epoch: Epoch,
|
||||
pub finalized_root: Hash256,
|
||||
}
|
||||
|
||||
impl std::cmp::PartialEq for SyncStatus {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
matches!(
|
||||
(self, other),
|
||||
(SyncStatus::Synced { .. }, SyncStatus::Synced { .. })
|
||||
| (SyncStatus::Advanced { .. }, SyncStatus::Advanced { .. })
|
||||
| (SyncStatus::Behind { .. }, SyncStatus::Behind { .. })
|
||||
| (SyncStatus::IrrelevantPeer, SyncStatus::IrrelevantPeer)
|
||||
| (SyncStatus::Unknown, SyncStatus::Unknown)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SyncStatus {
|
||||
/// Returns true if the peer has advanced knowledge of the chain.
|
||||
pub fn is_advanced(&self) -> bool {
|
||||
matches!(self, SyncStatus::Advanced { .. })
|
||||
}
|
||||
|
||||
/// Returns true if the peer is up to date with the current chain.
|
||||
pub fn is_synced(&self) -> bool {
|
||||
matches!(self, SyncStatus::Synced { .. })
|
||||
}
|
||||
|
||||
/// Returns true if the peer is behind the current chain.
|
||||
pub fn is_behind(&self) -> bool {
|
||||
matches!(self, SyncStatus::Behind { .. })
|
||||
}
|
||||
|
||||
/// Updates the peer's sync status, returning whether the status transitioned.
|
||||
///
|
||||
/// E.g. returns `true` if the state changed from `Synced` to `Advanced`, but not if
|
||||
/// the status remained `Synced` with different `SyncInfo` within.
|
||||
pub fn update(&mut self, new_state: SyncStatus) -> bool {
|
||||
let changed_status = *self != new_state;
|
||||
*self = new_state;
|
||||
changed_status
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
SyncStatus::Advanced { .. } => "Advanced",
|
||||
SyncStatus::Behind { .. } => "Behind",
|
||||
SyncStatus::Synced { .. } => "Synced",
|
||||
SyncStatus::Unknown => "Unknown",
|
||||
SyncStatus::IrrelevantPeer => "Irrelevant",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SyncStatus {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user