55//! implements automatic banning for excessive misbehavior, and provides reputation
66//! decay over time for recovery.
77
8- use serde:: { Deserialize , Serialize } ;
8+ use serde:: { Deserialize , Deserializer , Serialize } ;
99use std:: collections:: HashMap ;
1010use std:: net:: SocketAddr ;
1111use std:: sync:: Arc ;
@@ -14,9 +14,6 @@ use tokio::sync::RwLock;
1414
1515use crate :: storage:: { PeerStorage , PersistentPeerStorage } ;
1616
17- /// Maximum misbehavior score before a peer is banned
18- const MAX_MISBEHAVIOR_SCORE : i32 = 100 ;
19-
2017/// Misbehavior score thresholds for different violations
2118pub mod misbehavior_scores {
2219 /// Invalid message format or protocol violation
@@ -80,20 +77,71 @@ const DECAY_INTERVAL: Duration = Duration::from_secs(60 * 60); // 1 hour
8077/// Amount to decay reputation score per interval
8178const DECAY_AMOUNT : i32 = 5 ;
8279
80+ /// Maximum misbehavior score before a peer is banned
81+ const MAX_MISBEHAVIOR_SCORE : i32 = 100 ;
82+
8383/// Minimum score (most positive reputation)
84- const MIN_SCORE : i32 = -50 ;
84+ const MIN_MISBEHAVIOR_SCORE : i32 = -50 ;
85+
86+ const MAX_BAN_COUNT : u32 = 1000 ;
87+
88+ const MAX_ACTION_COUNT : u64 = 1_000_000 ;
8589
8690fn default_instant ( ) -> Instant {
8791 Instant :: now ( )
8892}
8993
94+ fn clamp_peer_score < ' de , D > ( deserializer : D ) -> Result < i32 , D :: Error >
95+ where
96+ D : Deserializer < ' de > ,
97+ {
98+ let mut v = i32:: deserialize ( deserializer) ?;
99+
100+ if v < MIN_MISBEHAVIOR_SCORE {
101+ log:: warn!( "Peer has invalid score {v}, clamping to min {MIN_MISBEHAVIOR_SCORE}" ) ;
102+ v = MIN_MISBEHAVIOR_SCORE
103+ } else if v > MAX_MISBEHAVIOR_SCORE {
104+ log:: warn!( "Peer has invalid score {v}, clamping to max {MAX_MISBEHAVIOR_SCORE}" ) ;
105+ v = MAX_MISBEHAVIOR_SCORE
106+ }
107+
108+ Ok ( v)
109+ }
110+
111+ fn clamp_peer_ban_count < ' de , D > ( deserializer : D ) -> Result < u32 , D :: Error >
112+ where
113+ D : Deserializer < ' de > ,
114+ {
115+ let mut v = u32:: deserialize ( deserializer) ?;
116+
117+ if v > MAX_BAN_COUNT {
118+ log:: warn!( "Peer has excessive ban count {v}, clamping to {MAX_BAN_COUNT}" ) ;
119+ v = MAX_BAN_COUNT
120+ }
121+
122+ Ok ( v)
123+ }
124+
125+ fn clamp_peer_connection_attempts < ' de , D > ( deserializer : D ) -> Result < u64 , D :: Error >
126+ where
127+ D : Deserializer < ' de > ,
128+ {
129+ let mut v = u64:: deserialize ( deserializer) ?;
130+
131+ v = v. min ( MAX_ACTION_COUNT ) ;
132+
133+ Ok ( v)
134+ }
135+
90136/// Peer reputation entry
91137#[ derive( Debug , Clone , Serialize , Deserialize ) ]
92138pub struct PeerReputation {
93139 /// Current misbehavior score
140+ #[ serde( deserialize_with = "clamp_peer_score" ) ]
94141 pub score : i32 ,
95142
96143 /// Number of times this peer has been banned
144+ #[ serde( deserialize_with = "clamp_peer_ban_count" ) ]
97145 pub ban_count : u32 ,
98146
99147 /// Time when the peer was banned (if currently banned)
@@ -111,6 +159,7 @@ pub struct PeerReputation {
111159 pub negative_actions : u64 ,
112160
113161 /// Connection count
162+ #[ serde( deserialize_with = "clamp_peer_connection_attempts" ) ]
114163 pub connection_attempts : u64 ,
115164
116165 /// Successful connection count
@@ -167,7 +216,7 @@ impl PeerReputation {
167216 // Cap at a reasonable maximum to avoid excessive decay
168217 let intervals_i32 = intervals. min ( i32:: MAX as u64 ) as i32 ;
169218 let decay = intervals_i32. saturating_mul ( DECAY_AMOUNT ) ;
170- self . score = ( self . score - decay) . max ( MIN_SCORE ) ;
219+ self . score = ( self . score - decay) . max ( MIN_MISBEHAVIOR_SCORE ) ;
171220 self . last_update = now;
172221 }
173222
@@ -231,7 +280,7 @@ impl PeerReputationManager {
231280 // Update score
232281 let old_score = reputation. score ;
233282 reputation. score =
234- ( reputation. score + score_change) . clamp ( MIN_SCORE , MAX_MISBEHAVIOR_SCORE ) ;
283+ ( reputation. score + score_change) . clamp ( MIN_MISBEHAVIOR_SCORE , MAX_MISBEHAVIOR_SCORE ) ;
235284
236285 // Track positive/negative actions
237286 if score_change > 0 {
@@ -417,52 +466,13 @@ impl PeerReputationManager {
417466 let mut skipped_count = 0 ;
418467
419468 for ( addr, mut reputation) in data {
420- // Validate score is within expected range
421- if reputation. score < MIN_SCORE {
422- log:: warn!(
423- "Peer {} has invalid score {} (below minimum), clamping to {}" ,
424- addr,
425- reputation. score,
426- MIN_SCORE
427- ) ;
428- reputation. score = MIN_SCORE
429- } else if reputation. score > MAX_MISBEHAVIOR_SCORE {
430- log:: warn!(
431- "Peer {} has invalid score {} (above maximum), clamping to {}" ,
432- addr,
433- reputation. score,
434- MAX_MISBEHAVIOR_SCORE
435- ) ;
436- reputation. score = MAX_MISBEHAVIOR_SCORE
437- }
438-
439- // Validate ban count is reasonable (max 1000 bans)
440- const MAX_BAN_COUNT : u32 = 1000 ;
441- if reputation. ban_count > MAX_BAN_COUNT {
442- log:: warn!(
443- "Peer {} has excessive ban count {}, clamping to {}" ,
444- addr,
445- reputation. ban_count,
446- MAX_BAN_COUNT
447- ) ;
448- reputation. ban_count = MAX_BAN_COUNT
449- }
450-
451- // Validate action counts are reasonable (max 1 million actions)
452- const MAX_ACTION_COUNT : u64 = 1_000_000 ;
453- reputation. positive_actions = reputation. positive_actions . min ( MAX_ACTION_COUNT ) ;
454- reputation. negative_actions = reputation. negative_actions . min ( MAX_ACTION_COUNT ) ;
455- reputation. connection_attempts = reputation. connection_attempts . min ( MAX_ACTION_COUNT ) ;
456- reputation. successful_connections =
457- reputation. successful_connections . min ( MAX_ACTION_COUNT ) ;
458-
459469 // Validate successful connections don't exceed attempts
460470 reputation. successful_connections =
461471 reputation. successful_connections . min ( reputation. connection_attempts ) ;
462472
463473 // Skip entry if data appears corrupted
464- if reputation. positive_actions == MAX_ACTION_COUNT
465- || reputation. negative_actions == MAX_ACTION_COUNT
474+ if reputation. positive_actions > MAX_ACTION_COUNT
475+ || reputation. negative_actions > MAX_ACTION_COUNT
466476 {
467477 log:: warn!( "Skipping peer {} with potentially corrupted action counts" , addr) ;
468478 skipped_count += 1 ;
0 commit comments