A Python port of the Invisible Internet Project (I2P)
at main 152 lines 5.3 kB view raw
1"""Peer profile organizer — classifies and selects peers. 2 3Ported from net.i2p.router.peermanager.ProfileOrganizer. 4""" 5 6from __future__ import annotations 7 8import random 9import time 10from enum import Enum 11from typing import TYPE_CHECKING 12 13if TYPE_CHECKING: 14 from i2p_peer.profile import PeerProfile 15 16 17class PeerTier(Enum): 18 FAST = "fast" # High speed + high capacity 19 HIGH_CAPACITY = "high_cap" # High capacity, any speed 20 STANDARD = "standard" # Meets minimum thresholds 21 FAILING = "failing" # Below minimum thresholds 22 BANNED = "banned" # Explicitly banned 23 24 25# Tier ordering for comparison (lower index = better tier) 26_TIER_ORDER = [PeerTier.FAST, PeerTier.HIGH_CAPACITY, PeerTier.STANDARD, 27 PeerTier.FAILING, PeerTier.BANNED] 28 29 30class ProfileOrganizer: 31 """Classifies peers into tiers and selects peers for tunnel building. 32 33 Tier thresholds (configurable): 34 - FAST: capacity >= 0.8 AND speed >= 0.7 35 - HIGH_CAPACITY: capacity >= 0.6 36 - STANDARD: capacity >= 0.3 37 - FAILING: capacity < 0.3 38 - BANNED: explicitly banned 39 """ 40 41 # Configurable thresholds 42 FAST_CAPACITY_THRESHOLD = 0.8 43 FAST_SPEED_THRESHOLD = 0.7 44 HIGH_CAP_THRESHOLD = 0.6 45 STANDARD_THRESHOLD = 0.3 46 MIN_ESTABLISHED_FOR_FAST = 10 # Min tunnel builds before eligible for fast tier 47 48 def __init__(self): 49 self._profiles: dict[bytes, PeerProfile] = {} 50 self._tiers: dict[PeerTier, set[bytes]] = {t: set() for t in PeerTier} 51 52 def add_profile(self, profile: PeerProfile) -> None: 53 """Add or update a peer profile and reclassify.""" 54 h = profile.router_hash 55 # Remove from old tier if present 56 self._remove_from_tiers(h) 57 self._profiles[h] = profile 58 tier = self.classify(profile) 59 self._tiers[tier].add(h) 60 61 def remove_profile(self, router_hash: bytes) -> None: 62 self._remove_from_tiers(router_hash) 63 self._profiles.pop(router_hash, None) 64 65 def get_profile(self, router_hash: bytes) -> PeerProfile | None: 66 return self._profiles.get(router_hash) 67 68 def classify(self, profile: PeerProfile) -> PeerTier: 69 """Determine which tier a peer belongs to.""" 70 if profile.is_currently_banned: 71 return PeerTier.BANNED 72 73 total_builds = profile.tunnel_builds_succeeded + profile.tunnel_builds_failed 74 75 if (profile.capacity >= self.FAST_CAPACITY_THRESHOLD and 76 profile.speed >= self.FAST_SPEED_THRESHOLD and 77 total_builds >= self.MIN_ESTABLISHED_FOR_FAST): 78 return PeerTier.FAST 79 80 if profile.capacity >= self.HIGH_CAP_THRESHOLD: 81 return PeerTier.HIGH_CAPACITY 82 83 if profile.capacity >= self.STANDARD_THRESHOLD: 84 return PeerTier.STANDARD 85 86 return PeerTier.FAILING 87 88 def reclassify_all(self) -> None: 89 """Reclassify all peers (call periodically).""" 90 self._tiers = {t: set() for t in PeerTier} 91 for h, profile in self._profiles.items(): 92 tier = self.classify(profile) 93 self._tiers[tier].add(h) 94 95 def select_fast_peers(self, count: int, exclude: set[bytes] | None = None) -> list[bytes]: 96 """Select peers from FAST tier for tunnel building.""" 97 return self._select_from_tier(PeerTier.FAST, count, exclude) 98 99 def select_high_capacity_peers(self, count: int, exclude: set[bytes] | None = None) -> list[bytes]: 100 """Select from HIGH_CAPACITY tier.""" 101 return self._select_from_tier(PeerTier.HIGH_CAPACITY, count, exclude) 102 103 def select_peers(self, count: int, min_tier: PeerTier = PeerTier.STANDARD, 104 exclude: set[bytes] | None = None) -> list[bytes]: 105 """Select peers at or above minimum tier.""" 106 exclude = exclude or set() 107 min_idx = _TIER_ORDER.index(min_tier) 108 eligible_tiers = _TIER_ORDER[:min_idx + 1] 109 candidates = [] 110 for tier in eligible_tiers: 111 for h in self._tiers[tier]: 112 if h not in exclude: 113 candidates.append(h) 114 random.shuffle(candidates) 115 return candidates[:count] 116 117 def get_tier(self, router_hash: bytes) -> PeerTier: 118 for tier, hashes in self._tiers.items(): 119 if router_hash in hashes: 120 return tier 121 return PeerTier.FAILING 122 123 @property 124 def fast_count(self) -> int: 125 return len(self._tiers[PeerTier.FAST]) 126 127 @property 128 def high_capacity_count(self) -> int: 129 return len(self._tiers[PeerTier.HIGH_CAPACITY]) 130 131 @property 132 def standard_count(self) -> int: 133 return len(self._tiers[PeerTier.STANDARD]) 134 135 @property 136 def failing_count(self) -> int: 137 return len(self._tiers[PeerTier.FAILING]) 138 139 @property 140 def total_count(self) -> int: 141 return len(self._profiles) 142 143 def _remove_from_tiers(self, router_hash: bytes) -> None: 144 for tier_set in self._tiers.values(): 145 tier_set.discard(router_hash) 146 147 def _select_from_tier(self, tier: PeerTier, count: int, 148 exclude: set[bytes] | None = None) -> list[bytes]: 149 exclude = exclude or set() 150 candidates = [h for h in self._tiers[tier] if h not in exclude] 151 random.shuffle(candidates) 152 return candidates[:count]