1use dashmap::DashMap;
39use libp2p::PeerId;
40use serde::{Deserialize, Serialize};
41use std::time::{Duration, Instant};
42
43#[derive(Debug, Clone)]
45pub struct ReputationConfig {
46 pub trust_threshold: f64,
48
49 pub bad_peer_threshold: f64,
51
52 pub transfer_success_weight: f64,
54
55 pub latency_weight: f64,
57
58 pub protocol_compliance_weight: f64,
60
61 pub uptime_weight: f64,
63
64 pub max_tracked_peers: usize,
66
67 pub retention_period: Duration,
69
70 pub score_decay_rate: f64,
72
73 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), score_decay_rate: 0.1,
89 ema_alpha: 0.3,
90 }
91 }
92}
93
94impl ReputationConfig {
95 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 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 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#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct ReputationScore {
141 pub transfer_success_rate: f64,
143
144 pub latency_score: f64,
146
147 pub protocol_compliance_score: f64,
149
150 pub uptime_score: f64,
152
153 pub successful_transfers: u64,
155
156 pub failed_transfers: u64,
158
159 pub protocol_violations: u64,
161
162 pub average_latency_ms: u64,
164
165 #[serde(
167 serialize_with = "serialize_duration",
168 deserialize_with = "deserialize_duration"
169 )]
170 pub total_uptime: Duration,
171
172 #[serde(skip, default = "Instant::now")]
174 pub last_seen: Instant,
175
176 #[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, 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 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 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 fn update_latency_score(&mut self, latency_ms: u64, alpha: f64) {
240 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 let normalized_latency = (latency_ms as f64).min(1000.0) / 1000.0;
248 self.latency_score = 1.0 - normalized_latency;
249 }
250
251 fn record_protocol_violation(&mut self, alpha: f64) {
253 self.protocol_violations = self.protocol_violations.saturating_add(1);
254
255 self.protocol_compliance_score *= 1.0 - alpha;
257 }
258
259 fn update_uptime(&mut self, connected_duration: Duration) {
261 self.total_uptime += connected_duration;
262
263 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
283pub enum ReputationEvent {
284 SuccessfulTransfer,
286 FailedTransfer,
288 LowLatency,
290 HighLatency,
292 ProtocolViolation,
294 GracefulDisconnect,
296 UnexpectedDisconnect,
298}
299
300pub struct ReputationManager {
302 config: ReputationConfig,
303 reputations: DashMap<PeerId, ReputationScore>,
304 stats: parking_lot::RwLock<ReputationStats>,
305}
306
307impl ReputationManager {
308 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 pub fn get_reputation(&self, peer_id: &PeerId) -> Option<ReputationScore> {
319 self.reputations.get(peer_id).map(|entry| entry.clone())
320 }
321
322 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 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 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 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 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 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 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 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 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 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 if let Some(mut score) = self.reputations.get_mut(peer_id) {
421 score.last_seen = Instant::now();
422 }
423 }
424 ReputationEvent::UnexpectedDisconnect => {
425 self.record_failed_transfer(peer_id);
427 }
428 }
429 }
430
431 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 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 pub fn tracked_peer_count(&self) -> usize {
462 self.reputations.len()
463 }
464
465 pub fn stats(&self) -> ReputationStats {
467 self.stats.read().clone()
468 }
469
470 pub fn clear(&mut self) {
472 self.reputations.clear();
473 *self.stats.write() = ReputationStats::default();
474 }
475}
476
477#[derive(Debug, Clone, Default, Serialize, Deserialize)]
479pub struct ReputationStats {
480 pub successful_events: u64,
482
483 pub failed_events: u64,
485
486 pub protocol_violations: u64,
488
489 pub latency_updates: u64,
491
492 pub uptime_updates: u64,
494
495 pub peers_removed: u64,
497}
498
499use std::collections::HashMap;
504use std::sync::atomic::{AtomicU64, Ordering};
505use std::sync::RwLock;
506
507#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
509pub enum PeerTier {
510 Banned,
512 Untrusted,
514 Neutral,
516 Good,
518 Trusted,
520}
521
522#[derive(Debug, Clone, Copy, PartialEq)]
524pub enum PeerReputationEvent {
525 BlockDelivered {
527 latency_ms: u64,
529 },
530 BlockFailed,
532 InvalidBlock,
534 DhtResponseValid {
536 latency_ms: u64,
538 },
539 DhtResponseEmpty,
541 ConnectionFailed,
543 ProtocolViolation,
545}
546
547impl PeerReputationEvent {
548 pub fn score_delta(&self) -> f64 {
559 match self {
560 PeerReputationEvent::BlockDelivered { latency_ms } => {
561 let bonus = if *latency_ms < 100 { 0.5 } else { 0.0 };
562 1.0 + bonus
563 }
564 PeerReputationEvent::BlockFailed => -1.0,
565 PeerReputationEvent::InvalidBlock => -10.0,
566 PeerReputationEvent::DhtResponseValid { latency_ms } => {
567 let bonus = if *latency_ms < 200 { 0.25 } else { 0.0 };
568 0.5 + bonus
569 }
570 PeerReputationEvent::DhtResponseEmpty => -0.25,
571 PeerReputationEvent::ConnectionFailed => -2.0,
572 PeerReputationEvent::ProtocolViolation => -15.0,
573 }
574 }
575}
576
577#[derive(Debug, Clone)]
579pub struct PeerReputation {
580 pub peer_id: String,
582 pub score: f64,
584 pub event_count: u64,
586 pub last_event_at: Option<Instant>,
588 pub created_at: Instant,
590}
591
592impl PeerReputation {
593 pub fn new(peer_id: impl Into<String>) -> Self {
595 Self {
596 peer_id: peer_id.into(),
597 score: 0.0,
598 event_count: 0,
599 last_event_at: None,
600 created_at: Instant::now(),
601 }
602 }
603
604 pub fn record_event(&mut self, event: PeerReputationEvent) {
606 self.score = (self.score + event.score_delta()).clamp(-100.0, 100.0);
607 self.event_count = self.event_count.saturating_add(1);
608 self.last_event_at = Some(Instant::now());
609 }
610
611 pub fn is_banned(&self) -> bool {
613 self.score <= -50.0
614 }
615
616 pub fn is_trusted(&self) -> bool {
618 self.score >= 50.0
619 }
620
621 pub fn decay(&mut self, elapsed: Duration) {
630 let factor = 1.0 - 0.001 * elapsed.as_secs_f64().min(100.0);
631 self.score *= factor;
632 }
633
634 pub fn tier(&self) -> PeerTier {
636 if self.score <= -50.0 {
637 PeerTier::Banned
638 } else if self.score <= -20.0 {
639 PeerTier::Untrusted
640 } else if self.score < 20.0 {
641 PeerTier::Neutral
642 } else if self.score < 50.0 {
643 PeerTier::Good
644 } else {
645 PeerTier::Trusted
646 }
647 }
648}
649
650#[derive(Debug, Default)]
652pub struct PeerReputationStats {
653 pub total_events: AtomicU64,
655 pub total_bans: AtomicU64,
657 pub total_unbans: AtomicU64,
659}
660
661impl PeerReputationStats {
662 pub fn new() -> Self {
664 Self::default()
665 }
666
667 pub fn snapshot(&self) -> PeerReputationStatsSnapshot {
669 PeerReputationStatsSnapshot {
670 total_events: self.total_events.load(Ordering::Relaxed),
671 total_bans: self.total_bans.load(Ordering::Relaxed),
672 total_unbans: self.total_unbans.load(Ordering::Relaxed),
673 }
674 }
675}
676
677#[derive(Debug, Clone, Copy, PartialEq, Eq)]
679pub struct PeerReputationStatsSnapshot {
680 pub total_events: u64,
682 pub total_bans: u64,
684 pub total_unbans: u64,
686}
687
688pub struct PeerReputationTracker {
697 peers: RwLock<HashMap<String, PeerReputation>>,
698 stats: PeerReputationStats,
699}
700
701impl Default for PeerReputationTracker {
702 fn default() -> Self {
703 Self::new()
704 }
705}
706
707impl PeerReputationTracker {
708 pub fn new() -> Self {
710 Self {
711 peers: RwLock::new(HashMap::new()),
712 stats: PeerReputationStats::new(),
713 }
714 }
715
716 pub fn record_event(&self, peer_id: &str, event: PeerReputationEvent) {
722 let mut guard = self.peers.write().unwrap_or_else(|e| e.into_inner());
723
724 let peer = guard
725 .entry(peer_id.to_owned())
726 .or_insert_with(|| PeerReputation::new(peer_id));
727
728 let was_banned = peer.is_banned();
729 peer.record_event(event);
730 let now_banned = peer.is_banned();
731
732 self.stats.total_events.fetch_add(1, Ordering::Relaxed);
733
734 if !was_banned && now_banned {
735 self.stats.total_bans.fetch_add(1, Ordering::Relaxed);
736 } else if was_banned && !now_banned {
737 self.stats.total_unbans.fetch_add(1, Ordering::Relaxed);
738 }
739 }
740
741 pub fn get_score(&self, peer_id: &str) -> f64 {
743 let guard = self.peers.read().unwrap_or_else(|e| e.into_inner());
744 guard.get(peer_id).map(|p| p.score).unwrap_or(0.0)
745 }
746
747 pub fn get_tier(&self, peer_id: &str) -> PeerTier {
751 let guard = self.peers.read().unwrap_or_else(|e| e.into_inner());
752 guard
753 .get(peer_id)
754 .map(|p| p.tier())
755 .unwrap_or(PeerTier::Neutral)
756 }
757
758 pub fn is_banned(&self, peer_id: &str) -> bool {
760 let guard = self.peers.read().unwrap_or_else(|e| e.into_inner());
761 guard.get(peer_id).map(|p| p.is_banned()).unwrap_or(false)
762 }
763
764 pub fn banned_peers(&self) -> Vec<String> {
766 let guard = self.peers.read().unwrap_or_else(|e| e.into_inner());
767 guard
768 .values()
769 .filter(|p| p.is_banned())
770 .map(|p| p.peer_id.clone())
771 .collect()
772 }
773
774 pub fn trusted_peers(&self) -> Vec<String> {
776 let guard = self.peers.read().unwrap_or_else(|e| e.into_inner());
777 guard
778 .values()
779 .filter(|p| p.is_trusted())
780 .map(|p| p.peer_id.clone())
781 .collect()
782 }
783
784 pub fn top_peers(&self, n: usize) -> Vec<(String, f64)> {
786 let guard = self.peers.read().unwrap_or_else(|e| e.into_inner());
787
788 let mut entries: Vec<(String, f64)> = guard
789 .values()
790 .map(|p| (p.peer_id.clone(), p.score))
791 .collect();
792
793 entries.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
794 entries.truncate(n);
795 entries
796 }
797
798 pub fn apply_decay(&self, elapsed: Duration) {
800 let mut guard = self.peers.write().unwrap_or_else(|e| e.into_inner());
801 for peer in guard.values_mut() {
802 peer.decay(elapsed);
803 }
804 }
805
806 pub fn remove_peer(&self, peer_id: &str) {
809 let mut guard = self.peers.write().unwrap_or_else(|e| e.into_inner());
810 guard.remove(peer_id);
811 }
812
813 pub fn peer_count(&self) -> usize {
815 let guard = self.peers.read().unwrap_or_else(|e| e.into_inner());
816 guard.len()
817 }
818
819 pub fn stats(&self) -> &PeerReputationStats {
821 &self.stats
822 }
823}
824
825#[cfg(test)]
830mod tracker_tests {
831 use super::{PeerReputation, PeerReputationEvent, PeerReputationTracker, PeerTier};
832 use std::time::Duration;
833
834 fn make_tracker() -> PeerReputationTracker {
835 PeerReputationTracker::new()
836 }
837
838 #[test]
843 fn test_block_delivered_high_latency_delta() {
844 let delta = PeerReputationEvent::BlockDelivered { latency_ms: 200 }.score_delta();
845 assert!((delta - 1.0).abs() < f64::EPSILON);
846 }
847
848 #[test]
849 fn test_block_delivered_low_latency_bonus() {
850 let delta = PeerReputationEvent::BlockDelivered { latency_ms: 50 }.score_delta();
851 assert!((delta - 1.5).abs() < f64::EPSILON);
852 }
853
854 #[test]
855 fn test_block_failed_delta() {
856 assert!((PeerReputationEvent::BlockFailed.score_delta() - (-1.0)).abs() < f64::EPSILON);
857 }
858
859 #[test]
860 fn test_invalid_block_delta() {
861 assert!((PeerReputationEvent::InvalidBlock.score_delta() - (-10.0)).abs() < f64::EPSILON);
862 }
863
864 #[test]
865 fn test_dht_valid_slow_delta() {
866 let delta = PeerReputationEvent::DhtResponseValid { latency_ms: 500 }.score_delta();
867 assert!((delta - 0.5).abs() < f64::EPSILON);
868 }
869
870 #[test]
871 fn test_dht_valid_fast_bonus() {
872 let delta = PeerReputationEvent::DhtResponseValid { latency_ms: 100 }.score_delta();
873 assert!((delta - 0.75).abs() < f64::EPSILON);
874 }
875
876 #[test]
877 fn test_dht_empty_delta() {
878 assert!(
879 (PeerReputationEvent::DhtResponseEmpty.score_delta() - (-0.25)).abs() < f64::EPSILON
880 );
881 }
882
883 #[test]
884 fn test_connection_failed_delta() {
885 assert!(
886 (PeerReputationEvent::ConnectionFailed.score_delta() - (-2.0)).abs() < f64::EPSILON
887 );
888 }
889
890 #[test]
891 fn test_protocol_violation_delta() {
892 assert!(
893 (PeerReputationEvent::ProtocolViolation.score_delta() - (-15.0)).abs() < f64::EPSILON
894 );
895 }
896
897 #[test]
902 fn test_score_starts_at_zero() {
903 let rep = PeerReputation::new("peer-A");
904 assert_eq!(rep.score, 0.0);
905 assert_eq!(rep.event_count, 0);
906 assert!(rep.last_event_at.is_none());
907 }
908
909 #[test]
910 fn test_score_clamped_at_positive_100() {
911 let mut rep = PeerReputation::new("peer-B");
912 for _ in 0..200 {
914 rep.record_event(PeerReputationEvent::BlockDelivered { latency_ms: 10 });
915 }
916 assert!(
917 rep.score <= 100.0,
918 "score should not exceed 100.0, got {}",
919 rep.score
920 );
921 assert!((rep.score - 100.0).abs() < f64::EPSILON);
922 }
923
924 #[test]
925 fn test_score_clamped_at_negative_100() {
926 let mut rep = PeerReputation::new("peer-C");
927 for _ in 0..8 {
929 rep.record_event(PeerReputationEvent::ProtocolViolation);
930 }
931 assert!(rep.score >= -100.0, "score must not go below -100.0");
932 assert!((rep.score - (-100.0)).abs() < f64::EPSILON);
933 }
934
935 #[test]
936 fn test_multiple_events_accumulate() {
937 let mut rep = PeerReputation::new("peer-D");
938 rep.record_event(PeerReputationEvent::BlockDelivered { latency_ms: 50 }); rep.record_event(PeerReputationEvent::DhtResponseValid { latency_ms: 100 }); rep.record_event(PeerReputationEvent::BlockFailed); assert!((rep.score - 1.25).abs() < 1e-9);
943 assert_eq!(rep.event_count, 3);
944 assert!(rep.last_event_at.is_some());
945 }
946
947 #[test]
948 fn test_is_banned_threshold() {
949 let mut rep = PeerReputation::new("peer-E");
950 rep.score = -50.0;
952 assert!(rep.is_banned());
953 rep.score = -49.9;
954 assert!(!rep.is_banned());
955 rep.score = -100.0;
956 assert!(rep.is_banned());
957 }
958
959 #[test]
960 fn test_is_trusted_threshold() {
961 let mut rep = PeerReputation::new("peer-F");
962 rep.score = 50.0;
963 assert!(rep.is_trusted());
964 rep.score = 49.9;
965 assert!(!rep.is_trusted());
966 rep.score = 100.0;
967 assert!(rep.is_trusted());
968 }
969
970 #[test]
971 fn test_peer_tier_classification() {
972 let mut rep = PeerReputation::new("peer-G");
973
974 rep.score = -60.0;
975 assert_eq!(rep.tier(), PeerTier::Banned);
976
977 rep.score = -50.0;
978 assert_eq!(rep.tier(), PeerTier::Banned);
979
980 rep.score = -35.0;
981 assert_eq!(rep.tier(), PeerTier::Untrusted);
982
983 rep.score = -20.0;
984 assert_eq!(rep.tier(), PeerTier::Untrusted);
985
986 rep.score = -0.1;
987 assert_eq!(rep.tier(), PeerTier::Neutral);
988
989 rep.score = 0.0;
990 assert_eq!(rep.tier(), PeerTier::Neutral);
991
992 rep.score = 10.0;
993 assert_eq!(rep.tier(), PeerTier::Neutral);
994
995 rep.score = 20.0;
996 assert_eq!(rep.tier(), PeerTier::Good);
997
998 rep.score = 49.9;
999 assert_eq!(rep.tier(), PeerTier::Good);
1000
1001 rep.score = 50.0;
1002 assert_eq!(rep.tier(), PeerTier::Trusted);
1003
1004 rep.score = 100.0;
1005 assert_eq!(rep.tier(), PeerTier::Trusted);
1006 }
1007
1008 #[test]
1009 fn test_decay_reduces_positive_score() {
1010 let mut rep = PeerReputation::new("peer-H");
1011 rep.score = 50.0;
1012 let before = rep.score;
1013 rep.decay(Duration::from_secs(10));
1014 assert!(rep.score < before, "decay should reduce a positive score");
1015 assert!(
1016 rep.score > 0.0,
1017 "score should remain positive after small decay"
1018 );
1019 }
1020
1021 #[test]
1022 fn test_decay_moves_negative_score_toward_zero() {
1023 let mut rep = PeerReputation::new("peer-I");
1024 rep.score = -50.0;
1025 let before = rep.score;
1026 rep.decay(Duration::from_secs(10));
1027 assert!(
1029 rep.score > before,
1030 "decay should move a negative score toward 0"
1031 );
1032 assert!(rep.score < 0.0, "score should still be negative");
1033 }
1034
1035 #[test]
1040 fn test_unknown_peer_returns_zero() {
1041 let tracker = make_tracker();
1042 assert_eq!(tracker.get_score("nobody"), 0.0);
1043 assert_eq!(tracker.get_tier("nobody"), PeerTier::Neutral);
1044 assert!(!tracker.is_banned("nobody"));
1045 }
1046
1047 #[test]
1048 fn test_record_event_creates_peer() {
1049 let tracker = make_tracker();
1050 tracker.record_event(
1051 "alice",
1052 PeerReputationEvent::BlockDelivered { latency_ms: 50 },
1053 );
1054 assert_eq!(tracker.peer_count(), 1);
1055 assert!(tracker.get_score("alice") > 0.0);
1056 }
1057
1058 #[test]
1059 fn test_banned_peers_list() {
1060 let tracker = make_tracker();
1061 for _ in 0..4 {
1063 tracker.record_event("bad-peer", PeerReputationEvent::ProtocolViolation);
1064 }
1065 tracker.record_event(
1067 "good-peer",
1068 PeerReputationEvent::BlockDelivered { latency_ms: 50 },
1069 );
1070
1071 let banned = tracker.banned_peers();
1072 assert!(banned.contains(&"bad-peer".to_owned()));
1073 assert!(!banned.contains(&"good-peer".to_owned()));
1074 }
1075
1076 #[test]
1077 fn test_trusted_peers_list() {
1078 let tracker = make_tracker();
1079 for _ in 0..55 {
1081 tracker.record_event(
1082 "star-peer",
1083 PeerReputationEvent::BlockDelivered { latency_ms: 200 },
1084 );
1085 }
1086 tracker.record_event("meh-peer", PeerReputationEvent::DhtResponseEmpty);
1087
1088 let trusted = tracker.trusted_peers();
1089 assert!(trusted.contains(&"star-peer".to_owned()));
1090 assert!(!trusted.contains(&"meh-peer".to_owned()));
1091 }
1092
1093 #[test]
1094 fn test_top_peers_ordering() {
1095 let tracker = make_tracker();
1096
1097 for _ in 0..10 {
1098 tracker.record_event(
1099 "alpha",
1100 PeerReputationEvent::BlockDelivered { latency_ms: 50 },
1101 );
1102 }
1103 for _ in 0..5 {
1104 tracker.record_event(
1105 "beta",
1106 PeerReputationEvent::BlockDelivered { latency_ms: 50 },
1107 );
1108 }
1109 tracker.record_event("gamma", PeerReputationEvent::BlockFailed);
1110
1111 let top = tracker.top_peers(3);
1112 assert_eq!(top.len(), 3);
1113 assert_eq!(top[0].0, "alpha");
1114 assert!(top[0].1 >= top[1].1);
1115 assert!(top[1].1 >= top[2].1);
1116 }
1117
1118 #[test]
1119 fn test_top_peers_fewer_than_n() {
1120 let tracker = make_tracker();
1121 tracker.record_event(
1122 "only-one",
1123 PeerReputationEvent::BlockDelivered { latency_ms: 200 },
1124 );
1125 let top = tracker.top_peers(10);
1126 assert_eq!(top.len(), 1);
1127 }
1128
1129 #[test]
1130 fn test_apply_decay_all_peers() {
1131 let tracker = make_tracker();
1132 for _ in 0..30 {
1133 tracker.record_event(
1134 "peer-x",
1135 PeerReputationEvent::BlockDelivered { latency_ms: 200 },
1136 );
1137 }
1138 let score_before = tracker.get_score("peer-x");
1139 tracker.apply_decay(Duration::from_secs(60));
1140 let score_after = tracker.get_score("peer-x");
1141 assert!(
1142 score_after < score_before,
1143 "decay must reduce a positive score"
1144 );
1145 }
1146
1147 #[test]
1148 fn test_remove_peer() {
1149 let tracker = make_tracker();
1150 tracker.record_event(
1151 "temp",
1152 PeerReputationEvent::BlockDelivered { latency_ms: 200 },
1153 );
1154 assert_eq!(tracker.peer_count(), 1);
1155 tracker.remove_peer("temp");
1156 assert_eq!(tracker.peer_count(), 0);
1157 tracker.remove_peer("nonexistent");
1159 assert_eq!(tracker.peer_count(), 0);
1160 }
1161
1162 #[test]
1163 fn test_stats_total_events() {
1164 let tracker = make_tracker();
1165 tracker.record_event(
1166 "p1",
1167 PeerReputationEvent::BlockDelivered { latency_ms: 200 },
1168 );
1169 tracker.record_event("p1", PeerReputationEvent::BlockFailed);
1170 tracker.record_event("p2", PeerReputationEvent::DhtResponseEmpty);
1171 let snap = tracker.stats().snapshot();
1172 assert_eq!(snap.total_events, 3);
1173 }
1174
1175 #[test]
1176 fn test_stats_ban_counter() {
1177 let tracker = make_tracker();
1178 for _ in 0..4 {
1180 tracker.record_event("villain", PeerReputationEvent::ProtocolViolation);
1181 }
1182 let snap = tracker.stats().snapshot();
1183 assert_eq!(snap.total_bans, 1);
1184 }
1185}
1186
1187#[cfg(test)]
1188mod tests {
1189 use super::*;
1190
1191 #[test]
1192 fn test_reputation_config_presets() {
1193 let strict = ReputationConfig::strict();
1194 assert!(strict.trust_threshold > ReputationConfig::default().trust_threshold);
1195
1196 let lenient = ReputationConfig::lenient();
1197 assert!(lenient.trust_threshold < ReputationConfig::default().trust_threshold);
1198
1199 let perf = ReputationConfig::performance_focused();
1200 assert!(perf.latency_weight > ReputationConfig::default().latency_weight);
1201 }
1202
1203 #[test]
1204 fn test_reputation_score_default() {
1205 let score = ReputationScore::default();
1206 assert_eq!(score.transfer_success_rate, 1.0);
1207 assert_eq!(score.successful_transfers, 0);
1208 assert_eq!(score.failed_transfers, 0);
1209 }
1210
1211 #[test]
1212 fn test_reputation_score_overall() {
1213 let config = ReputationConfig::default();
1214 let score = ReputationScore::default();
1215
1216 let overall = score.overall_score(&config);
1217 assert!(overall > 0.9); }
1219
1220 #[test]
1221 fn test_successful_transfer_updates() {
1222 let config = ReputationConfig::default();
1223 let mut manager = ReputationManager::new(config);
1224 let peer = PeerId::random();
1225
1226 manager.record_successful_transfer(&peer, 1024);
1227
1228 let score = manager
1229 .get_reputation(&peer)
1230 .expect("test: get_reputation should return Some after recording successful transfer");
1231 assert_eq!(score.successful_transfers, 1);
1232 assert_eq!(score.failed_transfers, 0);
1233 }
1234
1235 #[test]
1236 fn test_failed_transfer_updates() {
1237 let config = ReputationConfig::default();
1238 let mut manager = ReputationManager::new(config);
1239 let peer = PeerId::random();
1240
1241 manager.record_failed_transfer(&peer);
1242
1243 let score = manager
1244 .get_reputation(&peer)
1245 .expect("test: get_reputation should return Some after recording failed transfer");
1246 assert_eq!(score.failed_transfers, 1);
1247 assert!(score.transfer_success_rate < 1.0);
1248 }
1249
1250 #[test]
1251 fn test_latency_scoring() {
1252 let config = ReputationConfig::default();
1253 let mut manager = ReputationManager::new(config);
1254 let peer = PeerId::random();
1255
1256 manager.record_low_latency(&peer, 50);
1258 let score = manager
1259 .get_reputation(&peer)
1260 .expect("test: get_reputation should return Some after recording low latency");
1261 assert!(score.latency_score > 0.9);
1262
1263 manager.record_low_latency(&peer, 900);
1265 let score = manager
1266 .get_reputation(&peer)
1267 .expect("test: get_reputation should return Some after recording high latency");
1268 assert!(score.latency_score < 0.5);
1269 }
1270
1271 #[test]
1272 fn test_protocol_violation() {
1273 let config = ReputationConfig::default();
1274 let mut manager = ReputationManager::new(config);
1275 let peer = PeerId::random();
1276
1277 manager.record_protocol_violation(&peer);
1278
1279 let score = manager
1280 .get_reputation(&peer)
1281 .expect("test: get_reputation should return Some after recording protocol violation");
1282 assert_eq!(score.protocol_violations, 1);
1283 assert!(score.protocol_compliance_score < 1.0);
1284 }
1285
1286 #[test]
1287 fn test_is_trusted() {
1288 let config = ReputationConfig::default();
1289 let mut manager = ReputationManager::new(config);
1290 let peer = PeerId::random();
1291
1292 manager.record_successful_transfer(&peer, 1024);
1294 assert!(manager.is_trusted(&peer));
1295
1296 for _ in 0..10 {
1298 manager.record_failed_transfer(&peer);
1299 }
1300 assert!(!manager.is_trusted(&peer));
1301 }
1302
1303 #[test]
1304 fn test_is_bad_peer() {
1305 let config = ReputationConfig {
1307 transfer_success_weight: 0.9, ema_alpha: 0.5, latency_weight: 0.05,
1310 protocol_compliance_weight: 0.025,
1311 uptime_weight: 0.025,
1312 ..Default::default()
1313 };
1314
1315 let mut manager = ReputationManager::new(config);
1316 let peer = PeerId::random();
1317
1318 for _ in 0..20 {
1320 manager.record_failed_transfer(&peer);
1321 }
1322
1323 assert!(manager.is_bad_peer(&peer));
1324 }
1325
1326 #[test]
1327 fn test_get_trusted_peers() {
1328 let config = ReputationConfig::default();
1329 let mut manager = ReputationManager::new(config);
1330
1331 let peer1 = PeerId::random();
1332 let peer2 = PeerId::random();
1333
1334 manager.record_successful_transfer(&peer1, 1024);
1335 manager.record_successful_transfer(&peer2, 1024);
1336
1337 let trusted = manager.get_trusted_peers();
1338 assert_eq!(trusted.len(), 2);
1339 }
1340
1341 #[test]
1342 fn test_get_bad_peers() {
1343 let config = ReputationConfig {
1345 transfer_success_weight: 0.9,
1346 ema_alpha: 0.5,
1347 latency_weight: 0.05,
1348 protocol_compliance_weight: 0.025,
1349 uptime_weight: 0.025,
1350 ..Default::default()
1351 };
1352
1353 let mut manager = ReputationManager::new(config);
1354
1355 let peer = PeerId::random();
1356
1357 for _ in 0..20 {
1358 manager.record_failed_transfer(&peer);
1359 }
1360
1361 let bad_peers = manager.get_bad_peers();
1362 assert_eq!(bad_peers.len(), 1);
1363 }
1364
1365 #[test]
1366 fn test_uptime_tracking() {
1367 let config = ReputationConfig::default();
1368 let mut manager = ReputationManager::new(config);
1369 let peer = PeerId::random();
1370
1371 manager.update_uptime(&peer, Duration::from_secs(3600));
1372
1373 let score = manager
1374 .get_reputation(&peer)
1375 .expect("test: get_reputation should return Some after updating uptime");
1376 assert_eq!(score.total_uptime.as_secs(), 3600);
1377 }
1378
1379 #[test]
1380 fn test_reputation_events() {
1381 let config = ReputationConfig::default();
1382 let mut manager = ReputationManager::new(config);
1383 let peer = PeerId::random();
1384
1385 manager.record_event(&peer, ReputationEvent::SuccessfulTransfer);
1386 manager.record_event(&peer, ReputationEvent::LowLatency);
1387 manager.record_event(&peer, ReputationEvent::ProtocolViolation);
1388
1389 let score = manager
1390 .get_reputation(&peer)
1391 .expect("test: get_reputation should return Some after recording events");
1392 assert!(score.successful_transfers > 0);
1393 assert!(score.protocol_violations > 0);
1394 }
1395
1396 #[test]
1397 fn test_stats_tracking() {
1398 let config = ReputationConfig::default();
1399 let mut manager = ReputationManager::new(config);
1400 let peer = PeerId::random();
1401
1402 manager.record_successful_transfer(&peer, 1024);
1403 manager.record_failed_transfer(&peer);
1404 manager.record_protocol_violation(&peer);
1405
1406 let stats = manager.stats();
1407 assert_eq!(stats.successful_events, 1);
1408 assert_eq!(stats.failed_events, 1);
1409 assert_eq!(stats.protocol_violations, 1);
1410 }
1411
1412 #[test]
1413 fn test_tracked_peer_count() {
1414 let config = ReputationConfig::default();
1415 let mut manager = ReputationManager::new(config);
1416
1417 for _ in 0..5 {
1418 let peer = PeerId::random();
1419 manager.record_successful_transfer(&peer, 1024);
1420 }
1421
1422 assert_eq!(manager.tracked_peer_count(), 5);
1423 }
1424
1425 #[test]
1426 fn test_clear() {
1427 let config = ReputationConfig::default();
1428 let mut manager = ReputationManager::new(config);
1429 let peer = PeerId::random();
1430
1431 manager.record_successful_transfer(&peer, 1024);
1432 assert_eq!(manager.tracked_peer_count(), 1);
1433
1434 manager.clear();
1435 assert_eq!(manager.tracked_peer_count(), 0);
1436 }
1437}