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.
16const 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/// The type that tracks the reputation score.
52pub type Reputation = i32;
53
54/// Various kinds of reputation changes.
55#[derive(Debug, Copy, Clone, PartialEq, Eq)]
56pub enum ReputationChangeKind {
57    /// Received an unspecific bad message from the peer
58    BadMessage,
59    /// Peer sent a bad block.
60    ///
61    /// 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)
62    BadBlock,
63    /// Peer sent a bad transaction message. E.g. Transactions which weren't recoverable.
64    BadTransactions,
65    /// Peer sent a bad announcement message, e.g. invalid transaction type for the configured
66    /// network.
67    BadAnnouncement,
68    /// Peer sent a message that included a hash or transaction that we already received from the
69    /// peer.
70    ///
71    /// According to the [Eth spec](https://github.com/ethereum/devp2p/blob/master/caps/eth.md):
72    ///
73    /// > A node should never send a transaction back to a peer that it can determine already knows
74    /// > of it (either because it was previously sent or because it was informed from this peer
75    /// > originally). This is usually achieved by remembering a set of transaction hashes recently
76    /// > relayed by the peer.
77    AlreadySeenTransaction,
78    /// Peer failed to respond in time.
79    Timeout,
80    /// Peer does not adhere to network protocol rules.
81    BadProtocol,
82    /// Failed to establish a connection to the peer.
83    FailedToConnect,
84    /// Connection dropped by peer.
85    Dropped,
86    /// Reset the reputation to the default value.
87    Reset,
88    /// Apply a reputation change by value
89    Other(Reputation),
90}
91
92impl ReputationChangeKind {
93    /// Returns true if the reputation change is a [`ReputationChangeKind::Reset`].
94    pub const fn is_reset(&self) -> bool {
95        matches!(self, Self::Reset)
96    }
97
98    /// Returns true if the reputation change is [`ReputationChangeKind::Dropped`].
99    pub const fn is_dropped(&self) -> bool {
100        matches!(self, Self::Dropped)
101    }
102}
103
104/// How the [`ReputationChangeKind`] are weighted.
105#[derive(Debug, Clone, PartialEq, Eq)]
106#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
107#[cfg_attr(feature = "serde", serde(default))]
108pub struct ReputationChangeWeights {
109    /// Weight for [`ReputationChangeKind::BadMessage`]
110    pub bad_message: Reputation,
111    /// Weight for [`ReputationChangeKind::BadBlock`]
112    pub bad_block: Reputation,
113    /// Weight for [`ReputationChangeKind::BadTransactions`]
114    pub bad_transactions: Reputation,
115    /// Weight for [`ReputationChangeKind::AlreadySeenTransaction`]
116    pub already_seen_transactions: Reputation,
117    /// Weight for [`ReputationChangeKind::Timeout`]
118    pub timeout: Reputation,
119    /// Weight for [`ReputationChangeKind::BadProtocol`]
120    pub bad_protocol: Reputation,
121    /// Weight for [`ReputationChangeKind::FailedToConnect`]
122    pub failed_to_connect: Reputation,
123    /// Weight for [`ReputationChangeKind::Dropped`]
124    pub dropped: Reputation,
125    /// Weight for [`ReputationChangeKind::BadAnnouncement`]
126    pub bad_announcement: Reputation,
127}
128
129// === impl ReputationChangeWeights ===
130
131impl ReputationChangeWeights {
132    /// Creates a new instance that doesn't penalize any kind of reputation change.
133    pub const fn zero() -> Self {
134        Self {
135            bad_block: 0,
136            bad_transactions: 0,
137            already_seen_transactions: 0,
138            bad_message: 0,
139            timeout: 0,
140            bad_protocol: 0,
141            failed_to_connect: 0,
142            dropped: 0,
143            bad_announcement: 0,
144        }
145    }
146
147    /// Returns the quantifiable [`ReputationChange`] for the given [`ReputationChangeKind`] using
148    /// the configured weights
149    pub fn change(&self, kind: ReputationChangeKind) -> ReputationChange {
150        match kind {
151            ReputationChangeKind::BadMessage => self.bad_message.into(),
152            ReputationChangeKind::BadBlock => self.bad_block.into(),
153            ReputationChangeKind::BadTransactions => self.bad_transactions.into(),
154            ReputationChangeKind::AlreadySeenTransaction => self.already_seen_transactions.into(),
155            ReputationChangeKind::Timeout => self.timeout.into(),
156            ReputationChangeKind::BadProtocol => self.bad_protocol.into(),
157            ReputationChangeKind::FailedToConnect => self.failed_to_connect.into(),
158            ReputationChangeKind::Dropped => self.dropped.into(),
159            ReputationChangeKind::Reset => DEFAULT_REPUTATION.into(),
160            ReputationChangeKind::Other(val) => val.into(),
161            ReputationChangeKind::BadAnnouncement => self.bad_announcement.into(),
162        }
163    }
164}
165
166impl Default for ReputationChangeWeights {
167    fn default() -> Self {
168        Self {
169            bad_block: BAD_MESSAGE_REPUTATION_CHANGE,
170            bad_transactions: BAD_MESSAGE_REPUTATION_CHANGE,
171            already_seen_transactions: ALREADY_SEEN_TRANSACTION_REPUTATION_CHANGE,
172            bad_message: BAD_MESSAGE_REPUTATION_CHANGE,
173            timeout: TIMEOUT_REPUTATION_CHANGE,
174            bad_protocol: BAD_PROTOCOL_REPUTATION_CHANGE,
175            failed_to_connect: FAILED_TO_CONNECT_REPUTATION_CHANGE,
176            dropped: REMOTE_DISCONNECT_REPUTATION_CHANGE,
177            bad_announcement: BAD_ANNOUNCEMENT_REPUTATION_CHANGE,
178        }
179    }
180}
181
182/// Represents a change in a peer's reputation.
183#[derive(Debug, Copy, Clone, Default)]
184pub struct ReputationChange(Reputation);
185
186// === impl ReputationChange ===
187
188impl ReputationChange {
189    /// Helper type for easier conversion
190    #[inline]
191    pub const fn as_i32(self) -> Reputation {
192        self.0
193    }
194}
195
196impl From<ReputationChange> for Reputation {
197    fn from(value: ReputationChange) -> Self {
198        value.0
199    }
200}
201
202impl From<Reputation> for ReputationChange {
203    fn from(value: Reputation) -> Self {
204        Self(value)
205    }
206}
207
208/// Outcomes when a reputation change is applied to a peer
209#[derive(Debug, Clone, Copy)]
210pub enum ReputationChangeOutcome {
211    /// Nothing to do.
212    None,
213    /// Ban the peer.
214    Ban,
215    /// Ban and disconnect
216    DisconnectAndBan,
217    /// Unban the peer
218    Unban,
219}