Skip to content

Commit 2d4550e

Browse files
committed
peer reputation deserialization values clamp moved into serde pipeline
1 parent 3c95a98 commit 2d4550e

2 files changed

Lines changed: 58 additions & 90 deletions

File tree

dash-spv/peers/reputations.json

Lines changed: 0 additions & 42 deletions
This file was deleted.

dash-spv/src/network/reputation.rs

Lines changed: 58 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
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};
99
use std::collections::HashMap;
1010
use std::net::SocketAddr;
1111
use std::sync::Arc;
@@ -14,9 +14,6 @@ use tokio::sync::RwLock;
1414

1515
use 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
2118
pub 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
8178
const 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

8690
fn 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)]
92138
pub 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

Comments
 (0)