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}