Skip to main content

reth_network_types/peers/
mod.rs

1pub mod addr;
2pub mod config;
3pub mod kind;
4pub mod reputation;
5pub mod state;
6
7pub use config::{ConnectionsConfig, PeersConfig};
8pub use reputation::{Reputation, ReputationChange, ReputationChangeKind, ReputationChangeWeights};
9
10use alloy_eip2124::ForkId;
11use reth_network_peers::{NodeRecord, PeerId};
12use std::time::{Duration, Instant};
13use tracing::trace;
14
15use crate::{
16    is_banned_reputation, PeerAddr, PeerConnectionState, PeerKind, ReputationChangeOutcome,
17    DEFAULT_REPUTATION,
18};
19
20/// Tracks info about a single peer.
21#[derive(Debug, Clone)]
22pub struct Peer {
23    /// Where to reach the peer.
24    pub addr: PeerAddr,
25    /// Reputation of the peer.
26    pub reputation: i32,
27    /// The state of the connection, if any.
28    pub state: PeerConnectionState,
29    /// When the current session was established.
30    pub connected_at: Option<Instant>,
31    /// The [`ForkId`] that the peer announced via discovery.
32    pub fork_id: Option<Box<ForkId>>,
33    /// Whether the entry should be removed after an existing session was terminated.
34    pub remove_after_disconnect: bool,
35    /// The kind of peer
36    pub kind: PeerKind,
37    /// Whether the peer is currently backed off.
38    pub backed_off: bool,
39    /// Counts number of times the peer was backed off due to a severe
40    /// [`BackoffKind`](crate::BackoffKind).
41    pub severe_backoff_counter: u8,
42}
43
44// === impl Peer ===
45
46impl Peer {
47    /// Returns a new peer for given [`PeerAddr`].
48    pub fn new(addr: PeerAddr) -> Self {
49        Self::with_state(addr, Default::default())
50    }
51
52    /// Returns a new trusted peer for given [`PeerAddr`].
53    pub fn trusted(addr: PeerAddr) -> Self {
54        Self { kind: PeerKind::Trusted, ..Self::new(addr) }
55    }
56
57    /// Returns the reputation of the peer
58    pub const fn reputation(&self) -> i32 {
59        self.reputation
60    }
61
62    /// Marks this peer as connected.
63    pub fn mark_connected(&mut self) {
64        self.connected_at = Some(Instant::now());
65    }
66
67    /// Clears the current connection timestamp.
68    pub const fn mark_disconnected(&mut self) {
69        self.connected_at = None;
70    }
71
72    /// Returns `true` if the current connection has been active for at least `min_uptime`.
73    pub fn connected_for_at_least(&self, now: Instant, min_uptime: Duration) -> bool {
74        self.connected_at
75            .is_some_and(|connected_at| now.saturating_duration_since(connected_at) >= min_uptime)
76    }
77
78    /// Returns a new peer for given [`PeerAddr`] and [`PeerConnectionState`].
79    pub fn with_state(addr: PeerAddr, state: PeerConnectionState) -> Self {
80        Self {
81            addr,
82            state,
83            connected_at: None,
84            reputation: DEFAULT_REPUTATION,
85            fork_id: None,
86            remove_after_disconnect: false,
87            kind: Default::default(),
88            backed_off: false,
89            severe_backoff_counter: 0,
90        }
91    }
92
93    /// Returns a new peer for given [`PeerAddr`] and [`PeerKind`].
94    pub fn with_kind(addr: PeerAddr, kind: PeerKind) -> Self {
95        Self { kind, ..Self::new(addr) }
96    }
97
98    /// Resets the reputation of the peer to the default value. This always returns
99    /// [`ReputationChangeOutcome::None`].
100    pub const fn reset_reputation(&mut self) -> ReputationChangeOutcome {
101        self.reputation = DEFAULT_REPUTATION;
102
103        ReputationChangeOutcome::None
104    }
105
106    /// Applies a reputation change to the peer and returns what action should be taken.
107    pub fn apply_reputation(
108        &mut self,
109        reputation: i32,
110        kind: ReputationChangeKind,
111    ) -> ReputationChangeOutcome {
112        let previous = self.reputation;
113        // we add reputation since negative reputation change decrease total reputation
114        self.reputation = previous.saturating_add(reputation);
115
116        trace!(target: "net::peers", reputation=%self.reputation, banned=%self.is_banned(), ?kind, "applied reputation change");
117
118        if self.state.is_connected() && self.is_banned() {
119            self.state.disconnect();
120            return ReputationChangeOutcome::DisconnectAndBan
121        }
122
123        if self.is_banned() && !is_banned_reputation(previous) {
124            return ReputationChangeOutcome::Ban
125        }
126
127        if !self.is_banned() && is_banned_reputation(previous) {
128            return ReputationChangeOutcome::Unban
129        }
130
131        ReputationChangeOutcome::None
132    }
133
134    /// Returns true if the peer's reputation is below the banned threshold.
135    #[inline]
136    pub const fn is_banned(&self) -> bool {
137        is_banned_reputation(self.reputation)
138    }
139
140    /// Returns `true` if peer is banned.
141    #[inline]
142    pub const fn is_backed_off(&self) -> bool {
143        self.backed_off
144    }
145
146    /// Unbans the peer by resetting its reputation
147    #[inline]
148    pub const fn unban(&mut self) {
149        self.reputation = DEFAULT_REPUTATION
150    }
151
152    /// Returns whether this peer is trusted
153    #[inline]
154    pub const fn is_trusted(&self) -> bool {
155        matches!(self.kind, PeerKind::Trusted)
156    }
157
158    /// Returns whether this peer is static
159    #[inline]
160    pub const fn is_static(&self) -> bool {
161        matches!(self.kind, PeerKind::Static)
162    }
163}
164
165/// Peer info persisted to disk.
166///
167/// Contains richer metadata than a plain [`NodeRecord`], preserving the peer's kind, fork ID,
168/// and reputation across restarts.
169#[derive(Debug, Clone, PartialEq, Eq)]
170#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
171pub struct PersistedPeerInfo {
172    /// The node record (id, address, ports).
173    pub record: NodeRecord,
174    /// The kind of peer.
175    pub kind: PeerKind,
176    /// The [`ForkId`] that the peer announced via discovery.
177    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
178    pub fork_id: Option<ForkId>,
179    /// The peer's reputation at the time of persisting.
180    pub reputation: i32,
181}
182
183impl PersistedPeerInfo {
184    /// Returns the peer id.
185    pub const fn peer_id(&self) -> PeerId {
186        self.record.id
187    }
188
189    /// Converts a legacy [`NodeRecord`] into a [`PersistedPeerInfo`] with default metadata.
190    pub const fn from_node_record(record: NodeRecord) -> Self {
191        Self { record, kind: PeerKind::Basic, fork_id: None, reputation: DEFAULT_REPUTATION }
192    }
193}