elo-node 0.4.4__tar.gz → 0.4.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: elo-node
3
- Version: 0.4.4
3
+ Version: 0.4.5
4
4
  Summary: Elo — malha P2P de mensagens para agentes de IA. Zero infraestrutura.
5
5
  Author: Elo Contributors
6
6
  License: MIT
@@ -78,7 +78,7 @@ class Node:
78
78
  peers: list[str] | None = None,
79
79
  tracker: str = "public",
80
80
  allowlist: list[str] | None = None,
81
- version: str = "0.4.4",
81
+ version: str = "0.4.5",
82
82
  identity: EphemeralIdentity | None = None,
83
83
  verify_peers: bool = True,
84
84
  heartbeat_interval_s: int = 30,
@@ -110,7 +110,7 @@ class Node:
110
110
  self._task_handler: Callable[[Task], Awaitable[dict[str, Any]]] | None = None
111
111
  self._shutdown_event = asyncio.Event()
112
112
  self._pending_results: dict[str, asyncio.Future] = {}
113
- self._pending_queries: dict[str, tuple[asyncio.Future, float]] = {}
113
+ self._pending_queries: dict[str, tuple[list[dict[str, Any]], asyncio.Event, float]] = {}
114
114
 
115
115
  # Cache de pubkeys para verificação de assinatura
116
116
  self._pubkey_cache: dict[str, tuple[Any, float]] = {}
@@ -330,34 +330,41 @@ class Node:
330
330
  return await self.get_known_peers_local()
331
331
 
332
332
  async def discover_peers_network(self, timeout: float = 5.0) -> list[dict[str, Any]]:
333
- """Discover peers on the network via QUERY broadcast with empty capability.
333
+ """Discover peers on the network via QUERY broadcast.
334
334
 
335
- Sends a broadcast QUERY for an empty capability (catch-all) and aggregates
336
- responses for the given timeout period. Returns list of discovered peer dicts.
337
-
338
- Args:
339
- timeout: seconds to wait for responses (default 5.0)
340
-
341
- Returns:
342
- List of dicts with 'addr' and optionally 'node_id'
335
+ Envia QUERY com capability vazia e agrega todas as respostas
336
+ dentro do timeout. Modelo BitTorrent: o tracker retorna todos
337
+ os peers conhecidos, não só quem matcha a capability.
343
338
  """
344
339
  query_id = str(uuid.uuid4())[:8]
345
- future: asyncio.Future = asyncio.get_event_loop().create_future()
346
- self._pending_queries[query_id] = (future, time.time())
340
+ collected: list[dict[str, Any]] = []
341
+ event = asyncio.Event()
342
+ self._pending_queries[query_id] = (collected, event, time.time())
347
343
  discovered: dict[str, dict[str, Any]] = {}
348
344
 
349
345
  # Broadcast QUERY for any capability
350
346
  await self._tcp.broadcast(query_msg("", query_id, ttl=3))
351
347
 
352
348
  try:
353
- result_addr = await asyncio.wait_for(future, timeout=timeout)
354
- if result_addr:
355
- discovered[result_addr] = {"addr": result_addr, "connected": False, "caps": [], "via": "network"}
349
+ await asyncio.wait_for(event.wait(), timeout=timeout)
356
350
  except asyncio.TimeoutError:
357
351
  pass
358
352
  finally:
359
353
  self._pending_queries.pop(query_id, None)
360
354
 
355
+ # Merge collected peers from all responses
356
+ known_addrs: set[str] = set()
357
+ for entry in collected:
358
+ addr = entry.get("addr", "")
359
+ if addr and addr not in discovered:
360
+ discovered[addr] = {
361
+ "addr": addr,
362
+ "caps": entry.get("caps", []),
363
+ "connected": False,
364
+ "via": "network",
365
+ }
366
+ known_addrs.add(addr)
367
+
361
368
  # Merge with locally known peers
362
369
  local = await self.get_known_peers_local()
363
370
  for entry in local:
@@ -460,12 +467,23 @@ class Node:
460
467
  capability = msg.get("capability", "")
461
468
  query_id = msg.get("id", "")
462
469
  ttl = msg.get("ttl", 5)
463
- nodes = []
470
+ nodes: list[dict[str, Any]] = []
471
+
472
+ # Modelo BitTorrent: tracker retorna SEMPRE todos os peers conhecidos,
473
+ # não só quem matcha a capability. Isso permite que qualquer nó
474
+ # descubra a mesh completa com discover_peers_network().
475
+ for addr in self._routing.known_peers:
476
+ info = self._routing.get_peer_caps(addr)
477
+ if addr != peer_addr:
478
+ nodes.append({
479
+ "addr": addr,
480
+ "caps": list(info.get("caps", set())),
481
+ })
482
+
464
483
  if self._tracker.has_capability(capability):
465
484
  # Usa peer_addr (addr real do TCP) em vez de localhost — necessário para WAN/Tailscale
466
485
  nodes.append({"node_id": self._node_id[:12], "addr": peer_addr})
467
- for p in self._routing.find_all_peers_for(capability):
468
- nodes.append({"addr": p})
486
+
469
487
  if nodes:
470
488
  try:
471
489
  await self._tcp.send_to(peer_addr, query_resp_msg(query_id, nodes))
@@ -477,10 +495,11 @@ class Node:
477
495
  async def _on_query_resp(self, peer_addr: str, msg: dict) -> None:
478
496
  query_id = msg.get("id", "")
479
497
  if query_id in self._pending_queries:
480
- future, _ = self._pending_queries[query_id]
498
+ collected, event, _ts = self._pending_queries[query_id]
481
499
  nodes = msg.get("nodes", [])
482
- if nodes and not future.done():
483
- future.set_result(nodes[0].get("addr", ""))
500
+ if nodes:
501
+ collected.extend(nodes)
502
+ event.set()
484
503
 
485
504
  async def _on_interest_update(self, peer_addr: str, msg: dict) -> None:
486
505
  existing = self._routing.get_peer_caps(peer_addr)
@@ -609,15 +628,19 @@ class Node:
609
628
  async def _query_capability(self, capability: str, ttl: int = 5,
610
629
  timeout: float = 5.0) -> str | None:
611
630
  query_id = str(uuid.uuid4())[:8]
612
- future: asyncio.Future = asyncio.get_event_loop().create_future()
613
- self._pending_queries[query_id] = (future, time.time())
631
+ collected: list[dict[str, Any]] = []
632
+ event = asyncio.Event()
633
+ self._pending_queries[query_id] = (collected, event, time.time())
614
634
  await self._tcp.broadcast(query_msg(capability, query_id, ttl))
615
635
  try:
616
- return await asyncio.wait_for(future, timeout=timeout)
636
+ await asyncio.wait_for(event.wait(), timeout=timeout)
617
637
  except asyncio.TimeoutError:
618
- return None
638
+ pass
619
639
  finally:
620
640
  self._pending_queries.pop(query_id, None)
641
+ if collected:
642
+ return collected[0].get("addr", None)
643
+ return None
621
644
 
622
645
  async def _get_caller_pubkey(self, node_id: str) -> Any | None:
623
646
  now = time.time()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: elo-node
3
- Version: 0.4.4
3
+ Version: 0.4.5
4
4
  Summary: Elo — malha P2P de mensagens para agentes de IA. Zero infraestrutura.
5
5
  Author: Elo Contributors
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "elo-node"
7
- version = "0.4.4"
7
+ version = "0.4.5"
8
8
  description = "Elo — malha P2P de mensagens para agentes de IA. Zero infraestrutura."
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes