reth_network_types/peers/
reputation.rs

1//! Peer reputation management
2
3/// The default reputation of a peer
4pub const DEFAULT_REPUTATION: Reputation = 0;
5
6/// The minimal unit we're measuring reputation
7const REPUTATION_UNIT: i32 = -1024;
8
9/// The reputation value below which new connection from/to peers are rejected.
10pub const BANNED_REPUTATION: i32 = 50 * REPUTATION_UNIT;
11
12/// The reputation change to apply to a peer that dropped the connection.
13const REMOTE_DISCONNECT_REPUTATION_CHANGE: i32 = 4 * REPUTATION_UNIT;
14
15/// The reputation change to apply to a peer that we failed to connect to.
16pub const FAILED_TO_CONNECT_REPUTATION_CHANGE: i32 = 25 * REPUTATION_UNIT;
17
18/// The reputation change to apply to a peer that failed to respond in time.
19const TIMEOUT_REPUTATION_CHANGE: i32 = 4 * REPUTATION_UNIT;
20
21/// The reputation change to apply to a peer that sent a bad message.
22const BAD_MESSAGE_REPUTATION_CHANGE: i32 = 16 * REPUTATION_UNIT;
23
24/// The reputation change applies to a peer that has sent a transaction (full or hash) that we
25/// already know about and have already previously received from that peer.
26///
27/// Note: this appears to be quite common in practice, so by default this is 0, which doesn't
28/// apply any changes to the peer's reputation, effectively ignoring it.
29const ALREADY_SEEN_TRANSACTION_REPUTATION_CHANGE: i32 = 0;
30
31/// The reputation change to apply to a peer which violates protocol rules: minimal reputation
32const BAD_PROTOCOL_REPUTATION_CHANGE: i32 = i32::MIN;
33
34/// The reputation change to apply to a peer that sent a bad announcement.
35// todo: current value is a hint, needs to be set properly
36const BAD_ANNOUNCEMENT_REPUTATION_CHANGE: i32 = REPUTATION_UNIT;
37
38/// The maximum reputation change that can be applied to a trusted peer.
39/// This is used to prevent a single bad message from a trusted peer to cause a significant change.
40/// This gives a trusted peer more leeway when interacting with the node, which is useful for in
41/// custom setups. By not setting this to `0` we still allow trusted peer penalization but less than
42/// untrusted peers.
43pub const MAX_TRUSTED_PEER_REPUTATION_CHANGE: Reputation = 2 * REPUTATION_UNIT;
44
45/// Returns `true` if the given reputation is below the [`BANNED_REPUTATION`] threshold
46#[inline]
47pub const fn is_banned_reputation(reputation: i32) -> bool {
48    reputation < BANNED_REPUTATION
49}
50
51/// Returns `true` if the given reputation is below the [`FAILED_TO_CONNECT_REPUTATION_CHANGE`]
52/// threshold
53#[inline]
54pub const fn is_connection_failed_reputation(reputation: i32) -> bool {
55    reputation < FAILED_TO_CONNECT_REPUTATION_CHANGE
56}
57
58/// The type that tracks the reputation score.
59pub type Reputation = i32;
60
61/// Various kinds of reputation changes.
62#[derive(Debug, Copy, Clone, PartialEq, Eq)]
63pub enum ReputationChangeKind {
64    /// Received an unspecific bad message from the peer
65    BadMessage,
66    /// Peer sent a bad block.
67    ///
68    /// Note: this will we only used in pre-merge, pow consensus, since after no more block announcements are sent via devp2p: [EIP-3675](https://eips.ethereum.org/EIPS/eip-3675#devp2p)
69    BadBlock,
70    /// Peer sent a bad transaction message. E.g. Transactions which weren't recoverable.
71    BadTransactions,
72    /// Peer sent a bad announcement message, e.g. invalid transaction type for the configured
73    /// network.
74    BadAnnouncement,
75    /// Peer sent a message that included a hash or transaction that we already received from the
76    /// peer.
77    ///
78    /// According to the [Eth spec](https://github.com/ethereum/devp2p/blob/master/caps/eth.md):
79    ///
80    /// > A node should never send a transaction back to a peer that it can determine already knows
81    /// > of it (either because it was previously sent or because it was informed from this peer
82    /// > originally). This is usually achieved by remembering a set of transaction hashes recently
83    /// > relayed by the peer.
84    AlreadySeenTransaction,
85    /// Peer failed to respond in time.
86    Timeout,
87    /// Peer does not adhere to network protocol rules.
88    BadProtocol,
89    /// Failed to establish a connection to the peer.
90    FailedToConnect,
91    /// Connection dropped by peer.
92    Dropped,
93    /// Reset the reputation to the default value.
94    Reset,
95    /// Apply a reputation change by value
96    Other(Reputation),
97}
98
99impl ReputationChangeKind {
100    /// Returns true if the reputation change is a [`ReputationChangeKind::Reset`].
101    pub const fn is_reset(&self) -> bool {
102        matches!(self, Self::Reset)
103    }
104
105    /// Returns true if the reputation change is [`ReputationChangeKind::Dropped`].
106    pub const fn is_dropped(&self) -> bool {
107        matches!(self, Self::Dropped)
108    }
109}
110
111/// How the [`ReputationChangeKind`] are weighted.
112#[derive(Debug, Clone, PartialEq, Eq)]
113#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
114#[cfg_attr(feature = "serde", serde(default))]
115pub struct ReputationChangeWeights {
116    /// Weight for [`ReputationChangeKind::BadMessage`]
117    pub bad_message: Reputation,
118    /// Weight for [`ReputationChangeKind::BadBlock`]
119    pub bad_block: Reputation,
120    /// Weight for [`ReputationChangeKind::BadTransactions`]
121    pub bad_transactions: Reputation,
122    /// Weight for [`ReputationChangeKind::AlreadySeenTransaction`]
123    pub already_seen_transactions: Reputation,
124    /// Weight for [`ReputationChangeKind::Timeout`]
125    pub timeout: Reputation,
126    /// Weight for [`ReputationChangeKind::BadProtocol`]
127    pub bad_protocol: Reputation,
128    /// Weight for [`ReputationChangeKind::FailedToConnect`]
129    pub failed_to_connect: Reputation,
130    /// Weight for [`ReputationChangeKind::Dropped`]
131    pub dropped: Reputation,
132    /// Weight for [`ReputationChangeKind::BadAnnouncement`]
133    pub bad_announcement: Reputation,
134}
135
136// === impl ReputationChangeWeights ===
137
138impl ReputationChangeWeights {
139    /// Creates a new instance that doesn't penalize any kind of reputation change.
140    pub const fn zero() -> Self {
141        Self {
142            bad_block: 0,
143            bad_transactions: 0,
144            already_seen_transactions: 0,
145            bad_message: 0,
146            timeout: 0,
147            bad_protocol: 0,
148            failed_to_connect: 0,
149            dropped: 0,
150            bad_announcement: 0,
151        }
152    }
153
154    /// Returns the quantifiable [`ReputationChange`] for the given [`ReputationChangeKind`] using
155    /// the configured weights
156    pub fn change(&self, kind: ReputationChangeKind) -> ReputationChange {
157        match kind {
158            ReputationChangeKind::BadMessage => self.bad_message.into(),
159            ReputationChangeKind::BadBlock => self.bad_block.into(),
160            ReputationChangeKind::BadTransactions => self.bad_transactions.into(),
161            ReputationChangeKind::AlreadySeenTransaction => self.already_seen_transactions.into(),
162            ReputationChangeKind::Timeout => self.timeout.into(),
163            ReputationChangeKind::BadProtocol => self.bad_protocol.into(),
164            ReputationChangeKind::FailedToConnect => self.failed_to_connect.into(),
165            ReputationChangeKind::Dropped => self.dropped.into(),
166            ReputationChangeKind::Reset => DEFAULT_REPUTATION.into(),
167            ReputationChangeKind::Other(val) => val.into(),
168            ReputationChangeKind::BadAnnouncement => self.bad_announcement.into(),
169        }
170    }
171}
172
173impl Default for ReputationChangeWeights {
174    fn default() -> Self {
175        Self {
176            bad_block: BAD_MESSAGE_REPUTATION_CHANGE,
177            bad_transactions: BAD_MESSAGE_REPUTATION_CHANGE,
178            already_seen_transactions: ALREADY_SEEN_TRANSACTION_REPUTATION_CHANGE,
179            bad_message: BAD_MESSAGE_REPUTATION_CHANGE,
180            timeout: TIMEOUT_REPUTATION_CHANGE,
181            bad_protocol: BAD_PROTOCOL_REPUTATION_CHANGE,
182            failed_to_connect: FAILED_TO_CONNECT_REPUTATION_CHANGE,
183            dropped: REMOTE_DISCONNECT_REPUTATION_CHANGE,
184            bad_announcement: BAD_ANNOUNCEMENT_REPUTATION_CHANGE,
185        }
186    }
187}
188
189/// Represents a change in a peer's reputation.
190#[derive(Debug, Copy, Clone, Default)]
191pub struct ReputationChange(Reputation);
192
193// === impl ReputationChange ===
194
195impl ReputationChange {
196    /// Helper type for easier conversion
197    #[inline]
198    pub const fn as_i32(self) -> Reputation {
199        self.0
200    }
201}
202
203impl From<ReputationChange> for Reputation {
204    fn from(value: ReputationChange) -> Self {
205        value.0
206    }
207}
208
209impl From<Reputation> for ReputationChange {
210    fn from(value: Reputation) -> Self {
211        Self(value)
212    }
213}
214
215/// Outcomes when a reputation change is applied to a peer
216#[derive(Debug, Clone, Copy)]
217pub enum ReputationChangeOutcome {
218    /// Nothing to do.
219    None,
220    /// Ban the peer.
221    Ban,
222    /// Ban and disconnect
223    DisconnectAndBan,
224    /// Unban the peer
225    Unban,
226}