elo-node 0.4.2__tar.gz → 0.4.4__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.
- {elo_node-0.4.2 → elo_node-0.4.4}/PKG-INFO +1 -1
- {elo_node-0.4.2 → elo_node-0.4.4}/elo/__init__.py +1 -1
- {elo_node-0.4.2 → elo_node-0.4.4}/elo/node.py +119 -4
- {elo_node-0.4.2 → elo_node-0.4.4}/elo/transport/protocol.py +6 -2
- {elo_node-0.4.2 → elo_node-0.4.4}/elo_node.egg-info/PKG-INFO +1 -1
- {elo_node-0.4.2 → elo_node-0.4.4}/pyproject.toml +1 -1
- {elo_node-0.4.2 → elo_node-0.4.4}/tests/test_unit.py +1 -1
- {elo_node-0.4.2 → elo_node-0.4.4}/README.md +0 -0
- {elo_node-0.4.2 → elo_node-0.4.4}/elo/__main__.py +0 -0
- {elo_node-0.4.2 → elo_node-0.4.4}/elo/security.py +0 -0
- {elo_node-0.4.2 → elo_node-0.4.4}/elo/transport/__init__.py +0 -0
- {elo_node-0.4.2 → elo_node-0.4.4}/elo/transport/routing.py +0 -0
- {elo_node-0.4.2 → elo_node-0.4.4}/elo/transport/tcp.py +0 -0
- {elo_node-0.4.2 → elo_node-0.4.4}/elo/transport/tracker.py +0 -0
- {elo_node-0.4.2 → elo_node-0.4.4}/elo/types.py +0 -0
- {elo_node-0.4.2 → elo_node-0.4.4}/elo_node.egg-info/SOURCES.txt +0 -0
- {elo_node-0.4.2 → elo_node-0.4.4}/elo_node.egg-info/dependency_links.txt +0 -0
- {elo_node-0.4.2 → elo_node-0.4.4}/elo_node.egg-info/entry_points.txt +0 -0
- {elo_node-0.4.2 → elo_node-0.4.4}/elo_node.egg-info/requires.txt +0 -0
- {elo_node-0.4.2 → elo_node-0.4.4}/elo_node.egg-info/top_level.txt +0 -0
- {elo_node-0.4.2 → elo_node-0.4.4}/setup.cfg +0 -0
- {elo_node-0.4.2 → elo_node-0.4.4}/tests/test_security.py +0 -0
|
@@ -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.
|
|
81
|
+
version: str = "0.4.4",
|
|
82
82
|
identity: EphemeralIdentity | None = None,
|
|
83
83
|
verify_peers: bool = True,
|
|
84
84
|
heartbeat_interval_s: int = 30,
|
|
@@ -253,6 +253,10 @@ class Node:
|
|
|
253
253
|
except Exception as e:
|
|
254
254
|
return Result.make_error(task_id, "SEND_ERROR", str(e))
|
|
255
255
|
|
|
256
|
+
# Bug 2: Fallback via tracker antes de desistir
|
|
257
|
+
if not peer:
|
|
258
|
+
return await self.send_task_via_tracker("", target_node, capability, payload, ttl_s=ttl_s)
|
|
259
|
+
|
|
256
260
|
return Result.make_error(task_id, "NO_PEER", f"No peer for: {capability}")
|
|
257
261
|
|
|
258
262
|
async def send_task_async(self, target_node: str, capability: str,
|
|
@@ -292,10 +296,11 @@ class Node:
|
|
|
292
296
|
|
|
293
297
|
# ── descoberta ─────────────────────────────────────────────
|
|
294
298
|
|
|
295
|
-
async def
|
|
296
|
-
"""Return all known peers with capabilities.
|
|
299
|
+
async def get_known_peers_local(self) -> list[dict[str, Any]]:
|
|
300
|
+
"""Return all locally-known peers with capabilities (no network discovery).
|
|
297
301
|
|
|
298
302
|
Merges data from TCP connections (live) and InterestTable (registered).
|
|
303
|
+
Does NOT perform any network queries — only returns what's already known.
|
|
299
304
|
"""
|
|
300
305
|
result: dict[str, dict[str, Any]] = {}
|
|
301
306
|
|
|
@@ -314,6 +319,56 @@ class Node:
|
|
|
314
319
|
|
|
315
320
|
return list(result.values())
|
|
316
321
|
|
|
322
|
+
# Alias de compatibilidade — o método anterior chamava-se discover_peers()
|
|
323
|
+
async def discover_peers(self) -> list[dict[str, Any]]:
|
|
324
|
+
"""[DEPRECATED] Use get_known_peers_local() ou discover_peers_network().
|
|
325
|
+
|
|
326
|
+
Este método apenas consolida peers localmente conhecidos (TCP + InterestTable).
|
|
327
|
+
Não faz descoberta ativa de rede. Prefira discover_peers_network() para
|
|
328
|
+
descoberta via broadcast, ou get_known_peers() para peers com handshake completo.
|
|
329
|
+
"""
|
|
330
|
+
return await self.get_known_peers_local()
|
|
331
|
+
|
|
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.
|
|
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'
|
|
343
|
+
"""
|
|
344
|
+
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())
|
|
347
|
+
discovered: dict[str, dict[str, Any]] = {}
|
|
348
|
+
|
|
349
|
+
# Broadcast QUERY for any capability
|
|
350
|
+
await self._tcp.broadcast(query_msg("", query_id, ttl=3))
|
|
351
|
+
|
|
352
|
+
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"}
|
|
356
|
+
except asyncio.TimeoutError:
|
|
357
|
+
pass
|
|
358
|
+
finally:
|
|
359
|
+
self._pending_queries.pop(query_id, None)
|
|
360
|
+
|
|
361
|
+
# Merge with locally known peers
|
|
362
|
+
local = await self.get_known_peers_local()
|
|
363
|
+
for entry in local:
|
|
364
|
+
addr = entry["addr"]
|
|
365
|
+
if addr not in discovered:
|
|
366
|
+
discovered[addr] = entry
|
|
367
|
+
else:
|
|
368
|
+
discovered[addr].update(entry)
|
|
369
|
+
|
|
370
|
+
return list(discovered.values())
|
|
371
|
+
|
|
317
372
|
def get_known_peers(self) -> list[dict[str, Any]]:
|
|
318
373
|
"""Return peers registered in InterestTable (completed HELLO handshake).
|
|
319
374
|
|
|
@@ -355,6 +410,18 @@ class Node:
|
|
|
355
410
|
await self._on_hello(peer_addr, msg)
|
|
356
411
|
elif msg_type == MessageType.HELLO_ACK:
|
|
357
412
|
await self._on_hello(peer_addr, msg)
|
|
413
|
+
# Bug 1: Se HELLO_ACK contém known_peers, conectar-se a eles
|
|
414
|
+
known_peers = msg.get("known_peers")
|
|
415
|
+
if known_peers:
|
|
416
|
+
hello = hello_msg(self._node_id, self._tracker.get_public_caps(),
|
|
417
|
+
list(self._routing.local_interests),
|
|
418
|
+
self._tracker.visibility, self._version)
|
|
419
|
+
for peer_info in known_peers:
|
|
420
|
+
addr = peer_info.get("addr", "")
|
|
421
|
+
if addr and addr != peer_addr and addr not in self._tcp.peer_addresses:
|
|
422
|
+
asyncio.create_task(
|
|
423
|
+
self._tcp.connect_to_peer(addr, hello_payload=hello)
|
|
424
|
+
)
|
|
358
425
|
elif msg_type == MessageType.QUERY:
|
|
359
426
|
await self._on_query(peer_addr, msg)
|
|
360
427
|
elif msg_type == MessageType.QUERY_RESP:
|
|
@@ -376,8 +443,14 @@ class Node:
|
|
|
376
443
|
interests = msg.get("interests", [])
|
|
377
444
|
self._routing.register_peer(peer_addr, caps, interests)
|
|
378
445
|
|
|
446
|
+
# Se for tracker (public/private), inclui peers conhecidos no ACK
|
|
447
|
+
known_peers = None
|
|
448
|
+
if msg.get("type") == MessageType.HELLO and self._tracker.visibility in ("public", "private"):
|
|
449
|
+
known_peers = self.get_known_peers()
|
|
450
|
+
|
|
379
451
|
ack = hello_ack_msg(self._node_id, self._tracker.get_caps_for_peer(node_id),
|
|
380
|
-
list(self._routing.local_interests), self._tracker.visibility
|
|
452
|
+
list(self._routing.local_interests), self._tracker.visibility,
|
|
453
|
+
known_peers=known_peers)
|
|
381
454
|
try:
|
|
382
455
|
await self._tcp.send_to(peer_addr, ack)
|
|
383
456
|
except Exception:
|
|
@@ -479,6 +552,48 @@ class Node:
|
|
|
479
552
|
async def _on_bye(self, peer_addr: str, msg: dict) -> None:
|
|
480
553
|
self._routing.remove_peer(peer_addr)
|
|
481
554
|
|
|
555
|
+
# ── relay via tracker ───────────────────────────────────
|
|
556
|
+
|
|
557
|
+
async def send_task_via_tracker(
|
|
558
|
+
self,
|
|
559
|
+
tracker_node: str,
|
|
560
|
+
target: str,
|
|
561
|
+
capability: str,
|
|
562
|
+
payload: dict[str, Any],
|
|
563
|
+
*,
|
|
564
|
+
ttl_s: int = 60,
|
|
565
|
+
) -> Result:
|
|
566
|
+
"""Send a task via tracker relay (for peers behind NAT/Docker).
|
|
567
|
+
|
|
568
|
+
Args:
|
|
569
|
+
tracker_node: node_id or addr of tracker (empty = auto)
|
|
570
|
+
target: node_id prefix or name of the destination
|
|
571
|
+
capability: capability to invoke on destination
|
|
572
|
+
payload: task payload
|
|
573
|
+
"""
|
|
574
|
+
task_id = str(uuid.uuid4())
|
|
575
|
+
relay_payload = {
|
|
576
|
+
"target": target,
|
|
577
|
+
"capability": capability,
|
|
578
|
+
"payload": payload,
|
|
579
|
+
"ttl_s": ttl_s,
|
|
580
|
+
}
|
|
581
|
+
task_dict = p2p_task_msg(
|
|
582
|
+
task_id, tracker_node or "", self._node_id, "relay", relay_payload
|
|
583
|
+
)
|
|
584
|
+
task_dict.pop("signature", None)
|
|
585
|
+
task_dict["signature"] = self._identity.sign(task_dict)
|
|
586
|
+
|
|
587
|
+
peer = tracker_node or self._routing.find_peer_for("relay")
|
|
588
|
+
if peer:
|
|
589
|
+
try:
|
|
590
|
+
await self._tcp.send_to(peer, task_dict)
|
|
591
|
+
return await self._wait_for_result(task_id, peer)
|
|
592
|
+
except Exception as e:
|
|
593
|
+
return Result.make_error(task_id, "RELAY_ERROR", str(e))
|
|
594
|
+
|
|
595
|
+
return Result.make_error(task_id, "NO_TRACKER", "No tracker peer found")
|
|
596
|
+
|
|
482
597
|
# ── helpers ───────────────────────────────────────────────
|
|
483
598
|
|
|
484
599
|
async def _wait_for_result(self, task_id: str, peer_addr: str) -> Result:
|
|
@@ -89,14 +89,18 @@ def hello_msg(node_id: str, caps: dict, interests: list[str],
|
|
|
89
89
|
|
|
90
90
|
|
|
91
91
|
def hello_ack_msg(node_id: str, caps: dict, interests: list[str],
|
|
92
|
-
tracker: str = "public"
|
|
93
|
-
|
|
92
|
+
tracker: str = "public",
|
|
93
|
+
known_peers: list[dict] | None = None) -> dict:
|
|
94
|
+
msg: dict[str, Any] = {
|
|
94
95
|
"type": MessageType.HELLO_ACK,
|
|
95
96
|
"node_id": node_id,
|
|
96
97
|
"caps": caps,
|
|
97
98
|
"interests": interests,
|
|
98
99
|
"tracker": tracker,
|
|
99
100
|
}
|
|
101
|
+
if known_peers is not None:
|
|
102
|
+
msg["known_peers"] = known_peers
|
|
103
|
+
return msg
|
|
100
104
|
|
|
101
105
|
|
|
102
106
|
def query_msg(capability: str, query_id: str, ttl: int = 5) -> dict:
|
|
@@ -136,7 +136,7 @@ class TestNodeConstruction:
|
|
|
136
136
|
node = Node("test-node")
|
|
137
137
|
assert node._name == "test-node"
|
|
138
138
|
assert node._port == 7878
|
|
139
|
-
assert node._version == "0.4.
|
|
139
|
+
assert node._version == "0.4.4"
|
|
140
140
|
assert node.connected is False
|
|
141
141
|
assert node.node_id is not None
|
|
142
142
|
assert len(node.node_id) > 20
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|