Files
neuron-tai/packages/relay/meshnet_relay/peer_registry.py
Dobromir Popov a2258d3df4 feat(us-017): P2P gossip, NAT-traversal relay node, and TLS
- 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>
2026-06-29 17:58:16 +03:00

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)