- packages/p2p: identity (peer_id from sha256 of RSA pubkey), TLS cert generation with SHA-256 fingerprint, GossipClient (WSS PubSub with per-topic handlers, dedup by msg_id, auto-reconnect), MdnsDiscovery (zeroconf optional dependency, graceful no-op fallback) - packages/relay: new meshnet-relay package — RelayServer (asyncio + websockets) with gossip fanout hub, circuit relay proxy for NAT traversal, peer registry; meshnet-relay CLI - packages/p2p/relay_bootstrap.json: team relay bootstrap list - Tracker: _NodeEntry gains relay_addr, cert_fingerprint, peer_id; both register and heartbeat handlers read and store these optional fields - docs/adr/0010 already written (previous commit) - conftest.py: packages/relay added to sys.path - 18 new tests; 115 passed total, 1 skipped (no regressions) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
61 lines
1.7 KiB
Python
61 lines
1.7 KiB
Python
"""In-memory registry of connected gossip peers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import threading
|
|
import time
|
|
from dataclasses import dataclass, field
|
|
from typing import Any
|
|
|
|
|
|
@dataclass
|
|
class PeerEntry:
|
|
peer_id: str
|
|
addr: str
|
|
ws: Any # websockets.WebSocketServerProtocol
|
|
connected_at: float = field(default_factory=time.monotonic)
|
|
last_seen: float = field(default_factory=time.monotonic)
|
|
|
|
|
|
class PeerRegistry:
|
|
def __init__(self):
|
|
self._peers: dict[str, PeerEntry] = {}
|
|
self._lock = threading.Lock()
|
|
|
|
def register(self, peer_id: str, addr: str, ws) -> None:
|
|
with self._lock:
|
|
self._peers[peer_id] = PeerEntry(peer_id=peer_id, addr=addr, ws=ws)
|
|
|
|
def unregister(self, peer_id: str) -> None:
|
|
with self._lock:
|
|
self._peers.pop(peer_id, None)
|
|
|
|
def touch(self, peer_id: str) -> None:
|
|
with self._lock:
|
|
if peer_id in self._peers:
|
|
self._peers[peer_id].last_seen = time.monotonic()
|
|
|
|
def get(self, peer_id: str) -> PeerEntry | None:
|
|
with self._lock:
|
|
return self._peers.get(peer_id)
|
|
|
|
def all_except(self, peer_id: str) -> list[PeerEntry]:
|
|
with self._lock:
|
|
return [e for pid, e in self._peers.items() if pid != peer_id]
|
|
|
|
def list_peers(self) -> list[dict]:
|
|
with self._lock:
|
|
return [
|
|
{
|
|
"peer_id": e.peer_id,
|
|
"addr": e.addr,
|
|
"connected_at": e.connected_at,
|
|
"last_seen": e.last_seen,
|
|
}
|
|
for e in self._peers.values()
|
|
]
|
|
|
|
def __len__(self) -> int:
|
|
with self._lock:
|
|
return len(self._peers)
|