ipfrs_network/
reputation.rs

1//! Peer reputation system for tracking and scoring peer behavior
2//!
3//! This module provides a comprehensive reputation system that tracks peer behavior
4//! over time and calculates reputation scores based on various factors including:
5//! - Transfer success/failure rates
6//! - Response times and latency
7//! - Protocol violations
8//! - Uptime and reliability
9//! - Historical behavior patterns
10//!
11//! # Examples
12//!
13//! ```
14//! use ipfrs_network::reputation::{ReputationManager, ReputationConfig};
15//! use libp2p::PeerId;
16//!
17//! # #[tokio::main]
18//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
19//! let config = ReputationConfig::default();
20//! let mut manager = ReputationManager::new(config.clone());
21//!
22//! // Track successful interaction
23//! let peer_id = PeerId::random();
24//! manager.record_successful_transfer(&peer_id, 1024);
25//! manager.record_low_latency(&peer_id, 50);
26//!
27//! // Get reputation score
28//! if let Some(score) = manager.get_reputation(&peer_id) {
29//!     println!("Peer reputation: {:.2}", score.overall_score(&config));
30//! }
31//!
32//! // Check if peer is trusted
33//! assert!(manager.is_trusted(&peer_id));
34//! # Ok(())
35//! # }
36//! ```
37
38use dashmap::DashMap;
39use libp2p::PeerId;
40use serde::{Deserialize, Serialize};
41use std::time::{Duration, Instant};
42
43/// Configuration for the reputation system
44#[derive(Debug, Clone)]
45pub struct ReputationConfig {
46    /// Minimum score to be considered trusted (0.0-1.0)
47    pub trust_threshold: f64,
48
49    /// Score below which a peer is considered bad (0.0-1.0)
50    pub bad_peer_threshold: f64,
51
52    /// Weight for transfer success rate (0.0-1.0)
53    pub transfer_success_weight: f64,
54
55    /// Weight for latency score (0.0-1.0)
56    pub latency_weight: f64,
57
58    /// Weight for protocol compliance (0.0-1.0)
59    pub protocol_compliance_weight: f64,
60
61    /// Weight for uptime score (0.0-1.0)
62    pub uptime_weight: f64,
63
64    /// Maximum number of peers to track
65    pub max_tracked_peers: usize,
66
67    /// How long to remember peer reputation after last interaction
68    pub retention_period: Duration,
69
70    /// Decay factor for old scores (0.0-1.0, higher = faster decay)
71    pub score_decay_rate: f64,
72
73    /// Exponential moving average alpha for score updates (0.0-1.0)
74    pub ema_alpha: f64,
75}
76
77impl Default for ReputationConfig {
78    fn default() -> Self {
79        Self {
80            trust_threshold: 0.7,
81            bad_peer_threshold: 0.3,
82            transfer_success_weight: 0.4,
83            latency_weight: 0.2,
84            protocol_compliance_weight: 0.2,
85            uptime_weight: 0.2,
86            max_tracked_peers: 10000,
87            retention_period: Duration::from_secs(24 * 3600), // 24 hours
88            score_decay_rate: 0.1,
89            ema_alpha: 0.3,
90        }
91    }
92}
93
94impl ReputationConfig {
95    /// Configuration for strict reputation requirements
96    pub fn strict() -> Self {
97        Self {
98            trust_threshold: 0.85,
99            bad_peer_threshold: 0.4,
100            transfer_success_weight: 0.5,
101            latency_weight: 0.2,
102            protocol_compliance_weight: 0.2,
103            uptime_weight: 0.1,
104            ema_alpha: 0.4,
105            ..Default::default()
106        }
107    }
108
109    /// Configuration for lenient reputation requirements
110    pub fn lenient() -> Self {
111        Self {
112            trust_threshold: 0.5,
113            bad_peer_threshold: 0.2,
114            transfer_success_weight: 0.3,
115            latency_weight: 0.2,
116            protocol_compliance_weight: 0.1,
117            uptime_weight: 0.4,
118            ema_alpha: 0.2,
119            ..Default::default()
120        }
121    }
122
123    /// Configuration optimized for performance-critical applications
124    pub fn performance_focused() -> Self {
125        Self {
126            trust_threshold: 0.75,
127            bad_peer_threshold: 0.35,
128            transfer_success_weight: 0.3,
129            latency_weight: 0.5,
130            protocol_compliance_weight: 0.1,
131            uptime_weight: 0.1,
132            ema_alpha: 0.35,
133            ..Default::default()
134        }
135    }
136}
137
138/// Reputation score for a peer
139#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct ReputationScore {
141    /// Transfer success rate (0.0-1.0)
142    pub transfer_success_rate: f64,
143
144    /// Latency score (0.0-1.0, higher is better)
145    pub latency_score: f64,
146
147    /// Protocol compliance score (0.0-1.0)
148    pub protocol_compliance_score: f64,
149
150    /// Uptime score (0.0-1.0)
151    pub uptime_score: f64,
152
153    /// Number of successful transfers
154    pub successful_transfers: u64,
155
156    /// Number of failed transfers
157    pub failed_transfers: u64,
158
159    /// Number of protocol violations
160    pub protocol_violations: u64,
161
162    /// Average latency in milliseconds
163    pub average_latency_ms: u64,
164
165    /// Total uptime duration (in seconds, serializable)
166    #[serde(
167        serialize_with = "serialize_duration",
168        deserialize_with = "deserialize_duration"
169    )]
170    pub total_uptime: Duration,
171
172    /// Last seen timestamp (skipped in serialization)
173    #[serde(skip, default = "Instant::now")]
174    pub last_seen: Instant,
175
176    /// First seen timestamp (skipped in serialization)
177    #[serde(skip, default = "Instant::now")]
178    pub first_seen: Instant,
179}
180
181fn serialize_duration<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
182where
183    S: serde::Serializer,
184{
185    serializer.serialize_u64(duration.as_secs())
186}
187
188fn deserialize_duration<'de, D>(deserializer: D) -> Result<Duration, D::Error>
189where
190    D: serde::Deserializer<'de>,
191{
192    let secs = u64::deserialize(deserializer)?;
193    Ok(Duration::from_secs(secs))
194}
195
196impl Default for ReputationScore {
197    fn default() -> Self {
198        let now = Instant::now();
199        Self {
200            transfer_success_rate: 1.0, // Start with perfect score
201            latency_score: 1.0,
202            protocol_compliance_score: 1.0,
203            uptime_score: 1.0,
204            successful_transfers: 0,
205            failed_transfers: 0,
206            protocol_violations: 0,
207            average_latency_ms: 0,
208            total_uptime: Duration::from_secs(0),
209            last_seen: now,
210            first_seen: now,
211        }
212    }
213}
214
215impl ReputationScore {
216    /// Calculate overall reputation score using weighted average
217    pub fn overall_score(&self, config: &ReputationConfig) -> f64 {
218        let transfer_score = self.transfer_success_rate * config.transfer_success_weight;
219        let latency_score_weighted = self.latency_score * config.latency_weight;
220        let protocol_score = self.protocol_compliance_score * config.protocol_compliance_weight;
221        let uptime_score_weighted = self.uptime_score * config.uptime_weight;
222
223        transfer_score + latency_score_weighted + protocol_score + uptime_score_weighted
224    }
225
226    /// Update transfer success rate with exponential moving average
227    fn update_transfer_success_rate(&mut self, success: bool, alpha: f64) {
228        let new_value = if success { 1.0 } else { 0.0 };
229        self.transfer_success_rate = alpha * new_value + (1.0 - alpha) * self.transfer_success_rate;
230
231        if success {
232            self.successful_transfers = self.successful_transfers.saturating_add(1);
233        } else {
234            self.failed_transfers = self.failed_transfers.saturating_add(1);
235        }
236    }
237
238    /// Update latency score based on new latency measurement
239    fn update_latency_score(&mut self, latency_ms: u64, alpha: f64) {
240        // Calculate EMA of latency
241        let current_avg = self.average_latency_ms as f64;
242        let new_avg = alpha * (latency_ms as f64) + (1.0 - alpha) * current_avg;
243        self.average_latency_ms = new_avg as u64;
244
245        // Convert latency to score (lower is better)
246        // Score approaches 0 as latency approaches 1000ms, score = 1 at 0ms
247        let normalized_latency = (latency_ms as f64).min(1000.0) / 1000.0;
248        self.latency_score = 1.0 - normalized_latency;
249    }
250
251    /// Record a protocol violation
252    fn record_protocol_violation(&mut self, alpha: f64) {
253        self.protocol_violations = self.protocol_violations.saturating_add(1);
254
255        // Decrease protocol compliance score
256        self.protocol_compliance_score *= 1.0 - alpha;
257    }
258
259    /// Update uptime tracking
260    fn update_uptime(&mut self, connected_duration: Duration) {
261        self.total_uptime += connected_duration;
262
263        // Calculate uptime score based on total time known vs time connected
264        let known_duration = self.last_seen.duration_since(self.first_seen);
265        if known_duration.as_secs() > 0 {
266            self.uptime_score =
267                (self.total_uptime.as_secs_f64() / known_duration.as_secs_f64()).min(1.0);
268        }
269    }
270
271    /// Apply time-based decay to scores
272    fn apply_decay(&mut self, decay_rate: f64) {
273        let decay_factor = 1.0 - decay_rate;
274        self.transfer_success_rate *= decay_factor;
275        self.latency_score *= decay_factor;
276        self.protocol_compliance_score *= decay_factor;
277        self.uptime_score *= decay_factor;
278    }
279}
280
281/// Reputation event types
282#[derive(Debug, Clone, Copy, PartialEq, Eq)]
283pub enum ReputationEvent {
284    /// Successful data transfer
285    SuccessfulTransfer,
286    /// Failed data transfer
287    FailedTransfer,
288    /// Low latency response
289    LowLatency,
290    /// High latency response
291    HighLatency,
292    /// Protocol violation detected
293    ProtocolViolation,
294    /// Peer disconnected gracefully
295    GracefulDisconnect,
296    /// Peer disconnected unexpectedly
297    UnexpectedDisconnect,
298}
299
300/// Reputation manager for tracking peer reputations
301pub struct ReputationManager {
302    config: ReputationConfig,
303    reputations: DashMap<PeerId, ReputationScore>,
304    stats: parking_lot::RwLock<ReputationStats>,
305}
306
307impl ReputationManager {
308    /// Create a new reputation manager
309    pub fn new(config: ReputationConfig) -> Self {
310        Self {
311            config,
312            reputations: DashMap::new(),
313            stats: parking_lot::RwLock::new(ReputationStats::default()),
314        }
315    }
316
317    /// Get reputation score for a peer
318    pub fn get_reputation(&self, peer_id: &PeerId) -> Option<ReputationScore> {
319        self.reputations.get(peer_id).map(|entry| entry.clone())
320    }
321
322    /// Check if a peer is trusted based on their reputation
323    pub fn is_trusted(&self, peer_id: &PeerId) -> bool {
324        self.reputations
325            .get(peer_id)
326            .map(|score| score.overall_score(&self.config) >= self.config.trust_threshold)
327            .unwrap_or(false)
328    }
329
330    /// Check if a peer has a bad reputation
331    pub fn is_bad_peer(&self, peer_id: &PeerId) -> bool {
332        self.reputations
333            .get(peer_id)
334            .map(|score| score.overall_score(&self.config) < self.config.bad_peer_threshold)
335            .unwrap_or(false)
336    }
337
338    /// Get list of trusted peers
339    pub fn get_trusted_peers(&self) -> Vec<PeerId> {
340        self.reputations
341            .iter()
342            .filter(|entry| {
343                entry.value().overall_score(&self.config) >= self.config.trust_threshold
344            })
345            .map(|entry| *entry.key())
346            .collect()
347    }
348
349    /// Get list of bad peers
350    pub fn get_bad_peers(&self) -> Vec<PeerId> {
351        self.reputations
352            .iter()
353            .filter(|entry| {
354                entry.value().overall_score(&self.config) < self.config.bad_peer_threshold
355            })
356            .map(|entry| *entry.key())
357            .collect()
358    }
359
360    /// Record a successful transfer
361    pub fn record_successful_transfer(&mut self, peer_id: &PeerId, _bytes: u64) {
362        let mut score = self.reputations.entry(*peer_id).or_default();
363        score.update_transfer_success_rate(true, self.config.ema_alpha);
364        score.last_seen = Instant::now();
365
366        let mut stats = self.stats.write();
367        stats.successful_events += 1;
368    }
369
370    /// Record a failed transfer
371    pub fn record_failed_transfer(&mut self, peer_id: &PeerId) {
372        let mut score = self.reputations.entry(*peer_id).or_default();
373        score.update_transfer_success_rate(false, self.config.ema_alpha);
374        score.last_seen = Instant::now();
375
376        let mut stats = self.stats.write();
377        stats.failed_events += 1;
378    }
379
380    /// Record low latency response
381    pub fn record_low_latency(&mut self, peer_id: &PeerId, latency_ms: u64) {
382        let mut score = self.reputations.entry(*peer_id).or_default();
383        score.update_latency_score(latency_ms, self.config.ema_alpha);
384        score.last_seen = Instant::now();
385
386        let mut stats = self.stats.write();
387        stats.latency_updates += 1;
388    }
389
390    /// Record a protocol violation
391    pub fn record_protocol_violation(&mut self, peer_id: &PeerId) {
392        let mut score = self.reputations.entry(*peer_id).or_default();
393        score.record_protocol_violation(self.config.ema_alpha);
394        score.last_seen = Instant::now();
395
396        let mut stats = self.stats.write();
397        stats.protocol_violations += 1;
398    }
399
400    /// Update uptime for a peer
401    pub fn update_uptime(&mut self, peer_id: &PeerId, duration: Duration) {
402        let mut score = self.reputations.entry(*peer_id).or_default();
403        score.update_uptime(duration);
404        score.last_seen = Instant::now();
405
406        let mut stats = self.stats.write();
407        stats.uptime_updates += 1;
408    }
409
410    /// Record a reputation event
411    pub fn record_event(&mut self, peer_id: &PeerId, event: ReputationEvent) {
412        match event {
413            ReputationEvent::SuccessfulTransfer => self.record_successful_transfer(peer_id, 0),
414            ReputationEvent::FailedTransfer => self.record_failed_transfer(peer_id),
415            ReputationEvent::LowLatency => self.record_low_latency(peer_id, 50),
416            ReputationEvent::HighLatency => self.record_low_latency(peer_id, 500),
417            ReputationEvent::ProtocolViolation => self.record_protocol_violation(peer_id),
418            ReputationEvent::GracefulDisconnect => {
419                // Maintain current score, just update last_seen
420                if let Some(mut score) = self.reputations.get_mut(peer_id) {
421                    score.last_seen = Instant::now();
422                }
423            }
424            ReputationEvent::UnexpectedDisconnect => {
425                // Penalize slightly for unexpected disconnect
426                self.record_failed_transfer(peer_id);
427            }
428        }
429    }
430
431    /// Apply time-based decay to all reputation scores
432    pub fn apply_decay(&mut self) {
433        for mut entry in self.reputations.iter_mut() {
434            entry.value_mut().apply_decay(self.config.score_decay_rate);
435        }
436    }
437
438    /// Remove stale peer reputations based on retention period
439    pub fn cleanup_stale(&mut self) -> usize {
440        let retention = self.config.retention_period;
441        let now = Instant::now();
442        let mut removed = 0;
443
444        self.reputations.retain(|_, score| {
445            let should_keep = now.duration_since(score.last_seen) < retention;
446            if !should_keep {
447                removed += 1;
448            }
449            should_keep
450        });
451
452        if removed > 0 {
453            let mut stats = self.stats.write();
454            stats.peers_removed += removed as u64;
455        }
456
457        removed
458    }
459
460    /// Get the number of tracked peers
461    pub fn tracked_peer_count(&self) -> usize {
462        self.reputations.len()
463    }
464
465    /// Get reputation statistics
466    pub fn stats(&self) -> ReputationStats {
467        self.stats.read().clone()
468    }
469
470    /// Clear all reputation data
471    pub fn clear(&mut self) {
472        self.reputations.clear();
473        *self.stats.write() = ReputationStats::default();
474    }
475}
476
477/// Statistics about reputation tracking
478#[derive(Debug, Clone, Default, Serialize, Deserialize)]
479pub struct ReputationStats {
480    /// Number of successful events recorded
481    pub successful_events: u64,
482
483    /// Number of failed events recorded
484    pub failed_events: u64,
485
486    /// Number of protocol violations recorded
487    pub protocol_violations: u64,
488
489    /// Number of latency updates
490    pub latency_updates: u64,
491
492    /// Number of uptime updates
493    pub uptime_updates: u64,
494
495    /// Number of peers removed due to staleness
496    pub peers_removed: u64,
497}
498
499#[cfg(test)]
500mod tests {
501    use super::*;
502
503    #[test]
504    fn test_reputation_config_presets() {
505        let strict = ReputationConfig::strict();
506        assert!(strict.trust_threshold > ReputationConfig::default().trust_threshold);
507
508        let lenient = ReputationConfig::lenient();
509        assert!(lenient.trust_threshold < ReputationConfig::default().trust_threshold);
510
511        let perf = ReputationConfig::performance_focused();
512        assert!(perf.latency_weight > ReputationConfig::default().latency_weight);
513    }
514
515    #[test]
516    fn test_reputation_score_default() {
517        let score = ReputationScore::default();
518        assert_eq!(score.transfer_success_rate, 1.0);
519        assert_eq!(score.successful_transfers, 0);
520        assert_eq!(score.failed_transfers, 0);
521    }
522
523    #[test]
524    fn test_reputation_score_overall() {
525        let config = ReputationConfig::default();
526        let score = ReputationScore::default();
527
528        let overall = score.overall_score(&config);
529        assert!(overall > 0.9); // Should be close to 1.0 with default values
530    }
531
532    #[test]
533    fn test_successful_transfer_updates() {
534        let config = ReputationConfig::default();
535        let mut manager = ReputationManager::new(config);
536        let peer = PeerId::random();
537
538        manager.record_successful_transfer(&peer, 1024);
539
540        let score = manager.get_reputation(&peer).unwrap();
541        assert_eq!(score.successful_transfers, 1);
542        assert_eq!(score.failed_transfers, 0);
543    }
544
545    #[test]
546    fn test_failed_transfer_updates() {
547        let config = ReputationConfig::default();
548        let mut manager = ReputationManager::new(config);
549        let peer = PeerId::random();
550
551        manager.record_failed_transfer(&peer);
552
553        let score = manager.get_reputation(&peer).unwrap();
554        assert_eq!(score.failed_transfers, 1);
555        assert!(score.transfer_success_rate < 1.0);
556    }
557
558    #[test]
559    fn test_latency_scoring() {
560        let config = ReputationConfig::default();
561        let mut manager = ReputationManager::new(config);
562        let peer = PeerId::random();
563
564        // Record low latency
565        manager.record_low_latency(&peer, 50);
566        let score = manager.get_reputation(&peer).unwrap();
567        assert!(score.latency_score > 0.9);
568
569        // Record high latency
570        manager.record_low_latency(&peer, 900);
571        let score = manager.get_reputation(&peer).unwrap();
572        assert!(score.latency_score < 0.5);
573    }
574
575    #[test]
576    fn test_protocol_violation() {
577        let config = ReputationConfig::default();
578        let mut manager = ReputationManager::new(config);
579        let peer = PeerId::random();
580
581        manager.record_protocol_violation(&peer);
582
583        let score = manager.get_reputation(&peer).unwrap();
584        assert_eq!(score.protocol_violations, 1);
585        assert!(score.protocol_compliance_score < 1.0);
586    }
587
588    #[test]
589    fn test_is_trusted() {
590        let config = ReputationConfig::default();
591        let mut manager = ReputationManager::new(config);
592        let peer = PeerId::random();
593
594        // New peer starts trusted
595        manager.record_successful_transfer(&peer, 1024);
596        assert!(manager.is_trusted(&peer));
597
598        // Many failures reduce trust
599        for _ in 0..10 {
600            manager.record_failed_transfer(&peer);
601        }
602        assert!(!manager.is_trusted(&peer));
603    }
604
605    #[test]
606    fn test_is_bad_peer() {
607        // Use a config with higher transfer success weight and faster EMA
608        let config = ReputationConfig {
609            transfer_success_weight: 0.9, // Focus on transfer success
610            ema_alpha: 0.5,               // Faster updates
611            latency_weight: 0.05,
612            protocol_compliance_weight: 0.025,
613            uptime_weight: 0.025,
614            ..Default::default()
615        };
616
617        let mut manager = ReputationManager::new(config);
618        let peer = PeerId::random();
619
620        // Record many failures
621        for _ in 0..20 {
622            manager.record_failed_transfer(&peer);
623        }
624
625        assert!(manager.is_bad_peer(&peer));
626    }
627
628    #[test]
629    fn test_get_trusted_peers() {
630        let config = ReputationConfig::default();
631        let mut manager = ReputationManager::new(config);
632
633        let peer1 = PeerId::random();
634        let peer2 = PeerId::random();
635
636        manager.record_successful_transfer(&peer1, 1024);
637        manager.record_successful_transfer(&peer2, 1024);
638
639        let trusted = manager.get_trusted_peers();
640        assert_eq!(trusted.len(), 2);
641    }
642
643    #[test]
644    fn test_get_bad_peers() {
645        // Use a config with higher transfer success weight and faster EMA
646        let config = ReputationConfig {
647            transfer_success_weight: 0.9,
648            ema_alpha: 0.5,
649            latency_weight: 0.05,
650            protocol_compliance_weight: 0.025,
651            uptime_weight: 0.025,
652            ..Default::default()
653        };
654
655        let mut manager = ReputationManager::new(config);
656
657        let peer = PeerId::random();
658
659        for _ in 0..20 {
660            manager.record_failed_transfer(&peer);
661        }
662
663        let bad_peers = manager.get_bad_peers();
664        assert_eq!(bad_peers.len(), 1);
665    }
666
667    #[test]
668    fn test_uptime_tracking() {
669        let config = ReputationConfig::default();
670        let mut manager = ReputationManager::new(config);
671        let peer = PeerId::random();
672
673        manager.update_uptime(&peer, Duration::from_secs(3600));
674
675        let score = manager.get_reputation(&peer).unwrap();
676        assert_eq!(score.total_uptime.as_secs(), 3600);
677    }
678
679    #[test]
680    fn test_reputation_events() {
681        let config = ReputationConfig::default();
682        let mut manager = ReputationManager::new(config);
683        let peer = PeerId::random();
684
685        manager.record_event(&peer, ReputationEvent::SuccessfulTransfer);
686        manager.record_event(&peer, ReputationEvent::LowLatency);
687        manager.record_event(&peer, ReputationEvent::ProtocolViolation);
688
689        let score = manager.get_reputation(&peer).unwrap();
690        assert!(score.successful_transfers > 0);
691        assert!(score.protocol_violations > 0);
692    }
693
694    #[test]
695    fn test_stats_tracking() {
696        let config = ReputationConfig::default();
697        let mut manager = ReputationManager::new(config);
698        let peer = PeerId::random();
699
700        manager.record_successful_transfer(&peer, 1024);
701        manager.record_failed_transfer(&peer);
702        manager.record_protocol_violation(&peer);
703
704        let stats = manager.stats();
705        assert_eq!(stats.successful_events, 1);
706        assert_eq!(stats.failed_events, 1);
707        assert_eq!(stats.protocol_violations, 1);
708    }
709
710    #[test]
711    fn test_tracked_peer_count() {
712        let config = ReputationConfig::default();
713        let mut manager = ReputationManager::new(config);
714
715        for _ in 0..5 {
716            let peer = PeerId::random();
717            manager.record_successful_transfer(&peer, 1024);
718        }
719
720        assert_eq!(manager.tracked_peer_count(), 5);
721    }
722
723    #[test]
724    fn test_clear() {
725        let config = ReputationConfig::default();
726        let mut manager = ReputationManager::new(config);
727        let peer = PeerId::random();
728
729        manager.record_successful_transfer(&peer, 1024);
730        assert_eq!(manager.tracked_peer_count(), 1);
731
732        manager.clear();
733        assert_eq!(manager.tracked_peer_count(), 0);
734    }
735}