1use serde::{Deserialize, Serialize};
38use std::collections::HashMap;
39use std::time::{Duration, SystemTime};
40
41#[derive(Debug, Clone)]
43pub struct ReputationConfig {
44 pub initial_score: f64,
46
47 pub min_score: f64,
49
50 pub max_score: f64,
52
53 pub decay_rate: f64,
55
56 pub success_weight: f64,
58
59 pub failure_weight: f64,
61
62 pub latency_weight: f64,
64
65 pub max_decay_duration: Duration,
67}
68
69impl Default for ReputationConfig {
70 fn default() -> Self {
71 Self {
72 initial_score: 0.5,
73 min_score: 0.0,
74 max_score: 1.0,
75 decay_rate: 0.1,
76 success_weight: 0.01,
77 failure_weight: 0.05,
78 latency_weight: 0.001,
79 max_decay_duration: Duration::from_secs(7 * 24 * 3600), }
81 }
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct PeerReputation {
87 pub peer_id: String,
89
90 pub score: f64,
92
93 pub successes: u64,
95
96 pub failures: u64,
98
99 pub bytes_transferred: u64,
101
102 pub avg_latency_ms: f64,
104
105 pub last_seen: SystemTime,
107
108 pub first_seen: SystemTime,
110
111 pub is_banned: bool,
113}
114
115impl PeerReputation {
116 fn new(peer_id: String, initial_score: f64) -> Self {
118 let now = SystemTime::now();
119 Self {
120 peer_id,
121 score: initial_score,
122 successes: 0,
123 failures: 0,
124 bytes_transferred: 0,
125 avg_latency_ms: 0.0,
126 last_seen: now,
127 first_seen: now,
128 is_banned: false,
129 }
130 }
131
132 #[must_use]
134 #[inline]
135 pub fn success_rate(&self) -> f64 {
136 let total = self.successes + self.failures;
137 if total == 0 {
138 0.0
139 } else {
140 self.successes as f64 / total as f64
141 }
142 }
143
144 #[must_use]
146 #[inline]
147 pub const fn total_interactions(&self) -> u64 {
148 self.successes + self.failures
149 }
150
151 #[must_use]
153 #[inline]
154 pub const fn is_trusted(&self, threshold: f64) -> bool {
155 !self.is_banned && self.score >= threshold
156 }
157
158 #[must_use]
160 #[inline]
161 pub fn age_seconds(&self) -> u64 {
162 SystemTime::now()
163 .duration_since(self.first_seen)
164 .unwrap_or(Duration::from_secs(0))
165 .as_secs()
166 }
167
168 #[must_use]
170 #[inline]
171 pub fn time_since_last_seen(&self) -> u64 {
172 SystemTime::now()
173 .duration_since(self.last_seen)
174 .unwrap_or(Duration::from_secs(0))
175 .as_secs()
176 }
177}
178
179pub struct ReputationTracker {
181 config: ReputationConfig,
183
184 peers: HashMap<String, PeerReputation>,
186
187 last_decay: SystemTime,
189}
190
191impl ReputationTracker {
192 #[must_use]
194 pub fn new(config: ReputationConfig) -> Self {
195 Self {
196 config,
197 peers: HashMap::new(),
198 last_decay: SystemTime::now(),
199 }
200 }
201
202 fn get_or_create(&mut self, peer_id: String) -> &mut PeerReputation {
204 self.peers
205 .entry(peer_id.clone())
206 .or_insert_with(|| PeerReputation::new(peer_id, self.config.initial_score))
207 }
208
209 pub fn record_success(&mut self, peer_id: String, bytes_transferred: u64) {
211 let success_weight = self.config.success_weight;
212 let max_score = self.config.max_score;
213
214 let peer = self.get_or_create(peer_id);
215 peer.successes += 1;
216 peer.bytes_transferred += bytes_transferred;
217 peer.last_seen = SystemTime::now();
218
219 peer.score = (peer.score + success_weight).min(max_score);
221 }
222
223 pub fn record_failure(&mut self, peer_id: String, penalty: u64) {
225 let failure_weight = self.config.failure_weight;
226 let min_score = self.config.min_score;
227
228 let peer = self.get_or_create(peer_id);
229 peer.failures += 1;
230 peer.last_seen = SystemTime::now();
231
232 let penalty_score = failure_weight * (penalty as f64 / 100.0);
234 peer.score = (peer.score - penalty_score).max(min_score);
235
236 if peer.score <= min_score {
238 peer.is_banned = true;
239 }
240 }
241
242 pub fn record_latency(&mut self, peer_id: String, latency_ms: u64) {
244 let latency_weight = self.config.latency_weight;
245 let max_score = self.config.max_score;
246
247 let peer = self.get_or_create(peer_id);
248
249 let alpha = 0.3; peer.avg_latency_ms = alpha * latency_ms as f64 + (1.0 - alpha) * peer.avg_latency_ms;
252
253 let latency_factor = 1.0 - (latency_ms as f64 / 1000.0).min(1.0);
255 let latency_bonus = latency_weight * latency_factor;
256 peer.score = (peer.score + latency_bonus).min(max_score);
257 }
258
259 #[must_use]
261 #[inline]
262 pub fn get_reputation(&mut self, peer_id: &str) -> f64 {
263 self.apply_decay();
264 self.peers
265 .get(peer_id)
266 .map(|p| p.score)
267 .unwrap_or(self.config.initial_score)
268 }
269
270 #[must_use]
272 #[inline]
273 pub fn get_peer_data(&mut self, peer_id: &str) -> Option<&PeerReputation> {
274 self.apply_decay();
275 self.peers.get(peer_id)
276 }
277
278 #[must_use]
280 #[inline]
281 pub fn get_trusted_peers(&mut self, threshold: f64) -> Vec<String> {
282 self.apply_decay();
283 self.peers
284 .values()
285 .filter(|p| p.is_trusted(threshold))
286 .map(|p| p.peer_id.clone())
287 .collect()
288 }
289
290 #[must_use]
292 #[inline]
293 pub fn get_top_peers(&mut self, n: usize) -> Vec<String> {
294 self.apply_decay();
295 let mut peers: Vec<_> = self.peers.values().collect();
296 peers.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap());
297 peers.iter().take(n).map(|p| p.peer_id.clone()).collect()
298 }
299
300 #[must_use]
302 #[inline]
303 pub fn is_banned(&self, peer_id: &str) -> bool {
304 self.peers
305 .get(peer_id)
306 .map(|p| p.is_banned)
307 .unwrap_or(false)
308 }
309
310 #[inline]
312 pub fn ban_peer(&mut self, peer_id: String) {
313 let min_score = self.config.min_score;
314 let peer = self.get_or_create(peer_id);
315 peer.is_banned = true;
316 peer.score = min_score;
317 }
318
319 #[inline]
321 pub fn unban_peer(&mut self, peer_id: &str) {
322 if let Some(peer) = self.peers.get_mut(peer_id) {
323 peer.is_banned = false;
324 peer.score = self.config.initial_score;
325 }
326 }
327
328 fn apply_decay(&mut self) {
330 let now = SystemTime::now();
331 let elapsed = now
332 .duration_since(self.last_decay)
333 .unwrap_or(Duration::from_secs(0));
334
335 if elapsed < Duration::from_secs(3600) {
336 return;
338 }
339
340 let hours = elapsed.as_secs_f64() / 3600.0;
341 let decay_factor = self.config.decay_rate * hours;
342
343 for peer in self.peers.values_mut() {
344 let diff = peer.score - self.config.initial_score;
346 peer.score -= diff * decay_factor;
347 peer.score = peer
348 .score
349 .clamp(self.config.min_score, self.config.max_score);
350 }
351
352 self.last_decay = now;
353 }
354
355 pub fn cleanup_old_peers(&mut self, max_age_secs: u64) {
357 let now = SystemTime::now();
358 self.peers.retain(|_, peer| {
359 let age = now
360 .duration_since(peer.last_seen)
361 .unwrap_or(Duration::from_secs(0))
362 .as_secs();
363 age < max_age_secs
364 });
365 }
366
367 #[must_use]
368 pub fn get_stats(&self) -> ReputationStats {
370 let total_peers = self.peers.len();
371 let banned_peers = self.peers.values().filter(|p| p.is_banned).count();
372 let avg_score = if total_peers > 0 {
373 self.peers.values().map(|p| p.score).sum::<f64>() / total_peers as f64
374 } else {
375 0.0
376 };
377
378 ReputationStats {
379 total_peers,
380 banned_peers,
381 trusted_peers: self.peers.values().filter(|p| p.is_trusted(0.7)).count(),
382 avg_score,
383 total_interactions: self.peers.values().map(|p| p.total_interactions()).sum(),
384 }
385 }
386
387 #[must_use]
389 #[inline]
390 pub fn get_all_peer_ids(&self) -> Vec<String> {
391 self.peers.keys().cloned().collect()
392 }
393
394 #[must_use]
396 #[inline]
397 pub fn peer_count(&self) -> usize {
398 self.peers.len()
399 }
400}
401
402#[derive(Debug, Clone, Serialize, Deserialize)]
404pub struct ReputationStats {
405 pub total_peers: usize,
407
408 pub banned_peers: usize,
410
411 pub trusted_peers: usize,
413
414 pub avg_score: f64,
416
417 pub total_interactions: u64,
419}
420
421#[cfg(test)]
422mod tests {
423 use super::*;
424
425 #[test]
426 fn test_reputation_config_default() {
427 let config = ReputationConfig::default();
428 assert_eq!(config.initial_score, 0.5);
429 assert_eq!(config.min_score, 0.0);
430 assert_eq!(config.max_score, 1.0);
431 }
432
433 #[test]
434 fn test_peer_reputation_new() {
435 let peer = PeerReputation::new("peer1".to_string(), 0.5);
436 assert_eq!(peer.peer_id, "peer1");
437 assert_eq!(peer.score, 0.5);
438 assert_eq!(peer.successes, 0);
439 assert_eq!(peer.failures, 0);
440 assert!(!peer.is_banned);
441 }
442
443 #[test]
444 fn test_record_success() {
445 let config = ReputationConfig::default();
446 let mut tracker = ReputationTracker::new(config);
447
448 tracker.record_success("peer1".to_string(), 1024);
449 let score = tracker.get_reputation("peer1");
450
451 assert!(score > 0.5);
452 let peer = tracker.get_peer_data("peer1").unwrap();
453 assert_eq!(peer.successes, 1);
454 assert_eq!(peer.bytes_transferred, 1024);
455 }
456
457 #[test]
458 fn test_record_failure() {
459 let config = ReputationConfig::default();
460 let mut tracker = ReputationTracker::new(config);
461
462 tracker.record_failure("peer1".to_string(), 100);
463 let score = tracker.get_reputation("peer1");
464
465 assert!(score < 0.5);
466 let peer = tracker.get_peer_data("peer1").unwrap();
467 assert_eq!(peer.failures, 1);
468 }
469
470 #[test]
471 fn test_record_latency() {
472 let config = ReputationConfig::default();
473 let mut tracker = ReputationTracker::new(config);
474
475 tracker.record_latency("peer1".to_string(), 50);
476 let peer = tracker.get_peer_data("peer1").unwrap();
477
478 assert!(peer.avg_latency_ms > 0.0);
479 }
480
481 #[test]
482 fn test_ban_peer() {
483 let config = ReputationConfig::default();
484 let mut tracker = ReputationTracker::new(config);
485
486 tracker.ban_peer("peer1".to_string());
487 assert!(tracker.is_banned("peer1"));
488
489 tracker.unban_peer("peer1");
490 assert!(!tracker.is_banned("peer1"));
491 }
492
493 #[test]
494 fn test_get_trusted_peers() {
495 let config = ReputationConfig::default();
496 let mut tracker = ReputationTracker::new(config);
497
498 tracker.record_success("peer1".to_string(), 1024);
499 tracker.record_success("peer1".to_string(), 1024);
500 tracker.record_success("peer1".to_string(), 1024);
501
502 tracker.record_failure("peer2".to_string(), 100);
503
504 let trusted = tracker.get_trusted_peers(0.5);
505 assert!(trusted.contains(&"peer1".to_string()));
506 }
507
508 #[test]
509 fn test_get_top_peers() {
510 let config = ReputationConfig::default();
511 let mut tracker = ReputationTracker::new(config);
512
513 tracker.record_success("peer1".to_string(), 1024);
514 tracker.record_success("peer2".to_string(), 2048);
515 tracker.record_success("peer2".to_string(), 2048); tracker.record_failure("peer3".to_string(), 50);
517
518 let top = tracker.get_top_peers(2);
519 assert_eq!(top.len(), 2);
520 assert_eq!(top[0], "peer2"); }
522
523 #[test]
524 fn test_success_rate() {
525 let config = ReputationConfig::default();
526 let mut tracker = ReputationTracker::new(config);
527
528 tracker.record_success("peer1".to_string(), 1024);
529 tracker.record_success("peer1".to_string(), 1024);
530 tracker.record_failure("peer1".to_string(), 50);
531
532 let peer = tracker.get_peer_data("peer1").unwrap();
533 assert!((peer.success_rate() - 0.666).abs() < 0.01);
534 }
535
536 #[test]
537 fn test_cleanup_old_peers() {
538 let config = ReputationConfig::default();
539 let mut tracker = ReputationTracker::new(config);
540
541 tracker.record_success("peer1".to_string(), 1024);
542 assert_eq!(tracker.peer_count(), 1);
543
544 tracker.cleanup_old_peers(0);
545 assert_eq!(tracker.peer_count(), 0);
546 }
547
548 #[test]
549 fn test_reputation_stats() {
550 let config = ReputationConfig::default();
551 let mut tracker = ReputationTracker::new(config);
552
553 tracker.record_success("peer1".to_string(), 1024);
554 tracker.record_failure("peer2".to_string(), 50);
555
556 let stats = tracker.get_stats();
557 assert_eq!(stats.total_peers, 2);
558 assert_eq!(stats.total_interactions, 2);
559 }
560
561 #[test]
562 fn test_auto_ban_low_score() {
563 let config = ReputationConfig::default();
564 let mut tracker = ReputationTracker::new(config);
565
566 for _ in 0..20 {
568 tracker.record_failure("peer1".to_string(), 100);
569 }
570
571 assert!(tracker.is_banned("peer1"));
572 }
573}