A Python port of the Invisible Internet Project (I2P)
1"""Peer score calculators.
2
3Ported from net.i2p.router.peermanager.CapacityCalculator etc.
4"""
5
6from __future__ import annotations
7
8from typing import TYPE_CHECKING
9
10if TYPE_CHECKING:
11 from i2p_peer.profile import PeerProfile
12
13
14class CapacityCalculator:
15 """Compute tunnel-building capacity score from peer history."""
16
17 @staticmethod
18 def calc(profile: PeerProfile) -> float:
19 """Capacity = weighted combination of tunnel success rate and send rate.
20
21 Range: 0.0 to 1.0.
22
23 Weighting:
24 - Tunnel build success: 40%
25 - Send success: 30%
26 - DB operation success: 20%
27 - Uptime/activity: 10%
28 """
29 tunnel_total = profile.tunnel_builds_succeeded + profile.tunnel_builds_failed
30 send_total = profile.send_success_count + profile.send_failure_count
31 db_total = (profile.db_store_success_count + profile.db_store_failure_count +
32 profile.db_lookup_success_count + profile.db_lookup_failure_count)
33
34 # If no history at all, return 0
35 if tunnel_total == 0 and send_total == 0 and db_total == 0:
36 return 0.0
37
38 tunnel_score = profile.tunnel_success_rate if tunnel_total > 0 else 0.0
39 send_score = profile.send_success_rate if send_total > 0 else 0.0
40 db_score = profile.db_success_rate if db_total > 0 else 0.0
41
42 # Activity score: 1.0 if heard from recently, 0.0 otherwise
43 activity_score = 1.0 if profile.last_heard_from > 0.0 else 0.0
44
45 # Weight components that have data
46 total_weight = 0.0
47 weighted_sum = 0.0
48
49 if tunnel_total > 0:
50 weighted_sum += tunnel_score * 0.4
51 total_weight += 0.4
52 if send_total > 0:
53 weighted_sum += send_score * 0.3
54 total_weight += 0.3
55 if db_total > 0:
56 weighted_sum += db_score * 0.2
57 total_weight += 0.2
58
59 weighted_sum += activity_score * 0.1
60 total_weight += 0.1
61
62 if total_weight == 0.0:
63 return 0.0
64
65 return min(1.0, max(0.0, weighted_sum / total_weight))
66
67
68class SpeedCalculator:
69 """Compute speed score from latency measurements."""
70
71 # Reference latency: 1000ms. Latency at or below gets high score.
72 REFERENCE_LATENCY_MS = 1000.0
73
74 @staticmethod
75 def calc(profile: PeerProfile) -> float:
76 """Speed = inverse of average latency, normalized 0.0 to 1.0.
77
78 Lower latency = higher speed score.
79 Uses formula: score = reference / (reference + avg_latency)
80 """
81 avg = profile.average_latency
82 if avg <= 0.0:
83 return 0.0
84 # Sigmoid-like normalization: ref / (ref + latency)
85 # 50ms -> 1000/1050 = 0.952
86 # 500ms -> 1000/1500 = 0.667
87 # 1000ms -> 1000/2000 = 0.5
88 # 5000ms -> 1000/6000 = 0.167
89 return min(1.0, max(0.0,
90 SpeedCalculator.REFERENCE_LATENCY_MS /
91 (SpeedCalculator.REFERENCE_LATENCY_MS + avg)))
92
93
94class IntegrationCalculator:
95 """Compute integration score (how well-connected the peer is)."""
96
97 @staticmethod
98 def calc(profile: PeerProfile) -> float:
99 """Integration = DB store/lookup success rate.
100
101 High integration = good floodfill candidate.
102 """
103 return profile.db_success_rate