truly public forum: auto-discover other instances via RNS announce handler
This commit is contained in:
parent
0ed4ec82ba
commit
3b3e807518
2 changed files with 80 additions and 1 deletions
|
|
@ -378,6 +378,57 @@ def test_sync_peer_discovery():
|
||||||
shutil.rmtree(dir_c)
|
shutil.rmtree(dir_c)
|
||||||
|
|
||||||
|
|
||||||
|
def test_announce_handler():
|
||||||
|
"""Announce handler should auto-discover peers."""
|
||||||
|
dir_a = tempfile.mkdtemp()
|
||||||
|
try:
|
||||||
|
fdb = ForumDB(dir_a)
|
||||||
|
from tinyweb_forum.sync import _ForumAnnounceHandler
|
||||||
|
|
||||||
|
# Mock identity — RNS identity.hash returns bytes
|
||||||
|
class MockIdentity:
|
||||||
|
def __init__(self, h):
|
||||||
|
self._hash_hex = h
|
||||||
|
@property
|
||||||
|
def hash(self):
|
||||||
|
return bytes.fromhex(self._hash_hex)
|
||||||
|
|
||||||
|
local_hex = "aa" * 16
|
||||||
|
peer_hex = "bb" * 16
|
||||||
|
|
||||||
|
handler = _ForumAnnounceHandler(fdb, MockIdentity(local_hex))
|
||||||
|
|
||||||
|
class MockAnnouncedIdentity:
|
||||||
|
def __init__(self, h):
|
||||||
|
self._hash_hex = h
|
||||||
|
@property
|
||||||
|
def hash(self):
|
||||||
|
return bytes.fromhex(self._hash_hex)
|
||||||
|
|
||||||
|
# Announce from a peer
|
||||||
|
handler.received_announce(
|
||||||
|
destination_hash=bytes.fromhex(peer_hex),
|
||||||
|
announced_identity=MockAnnouncedIdentity(peer_hex),
|
||||||
|
app_data=b"tinyweb-forum",
|
||||||
|
)
|
||||||
|
|
||||||
|
known = [r["instance_hash"] for r in fdb.get_synced_instances()]
|
||||||
|
assert peer_hex in known, "peer should be added to sync list"
|
||||||
|
|
||||||
|
# Announce from ourselves should be ignored
|
||||||
|
handler.received_announce(
|
||||||
|
destination_hash=bytes.fromhex(local_hex),
|
||||||
|
announced_identity=MockAnnouncedIdentity(local_hex),
|
||||||
|
app_data=b"tinyweb-forum",
|
||||||
|
)
|
||||||
|
|
||||||
|
known = [r["instance_hash"] for r in fdb.get_synced_instances()]
|
||||||
|
assert known.count(local_hex) == 0, "own announce should be ignored"
|
||||||
|
finally:
|
||||||
|
import shutil
|
||||||
|
shutil.rmtree(dir_a)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
test_sync_thread()
|
test_sync_thread()
|
||||||
test_sync_reply()
|
test_sync_reply()
|
||||||
|
|
@ -388,4 +439,5 @@ if __name__ == "__main__":
|
||||||
test_sync_block_gossip()
|
test_sync_block_gossip()
|
||||||
test_sync_updated_content()
|
test_sync_updated_content()
|
||||||
test_sync_peer_discovery()
|
test_sync_peer_discovery()
|
||||||
|
test_announce_handler()
|
||||||
print("all sync tests passed")
|
print("all sync tests passed")
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,24 @@ SYNC_INTERVAL = 300 # 5 minutes
|
||||||
REQUEST_TIMEOUT = 60
|
REQUEST_TIMEOUT = 60
|
||||||
|
|
||||||
|
|
||||||
|
class _ForumAnnounceHandler:
|
||||||
|
"""Receives announces from other forum instances and auto-discovers them."""
|
||||||
|
|
||||||
|
aspect_filter = FORUM_APP
|
||||||
|
receive_path_responses = False
|
||||||
|
|
||||||
|
def __init__(self, fdb, identity):
|
||||||
|
self.fdb = fdb
|
||||||
|
self.my_hash = identity.hash.hex() if identity else "local"
|
||||||
|
|
||||||
|
def received_announce(self, destination_hash, announced_identity, app_data):
|
||||||
|
if announced_identity is None:
|
||||||
|
return
|
||||||
|
peer_hash = announced_identity.hash.hex()
|
||||||
|
if peer_hash and peer_hash != self.my_hash:
|
||||||
|
self.fdb.add_known_peer(peer_hash)
|
||||||
|
|
||||||
|
|
||||||
class ForumSync:
|
class ForumSync:
|
||||||
def __init__(self, fdb, identity, reticulum, handlers_ref):
|
def __init__(self, fdb, identity, reticulum, handlers_ref):
|
||||||
self.fdb = fdb
|
self.fdb = fdb
|
||||||
|
|
@ -15,6 +33,7 @@ class ForumSync:
|
||||||
self.reticulum = reticulum
|
self.reticulum = reticulum
|
||||||
self.handlers_ref = handlers_ref
|
self.handlers_ref = handlers_ref
|
||||||
self.destination = None
|
self.destination = None
|
||||||
|
self._announce_handler = None
|
||||||
self._running = False
|
self._running = False
|
||||||
self._thread = None
|
self._thread = None
|
||||||
|
|
||||||
|
|
@ -30,13 +49,21 @@ class ForumSync:
|
||||||
response_generator=self._rns_handler,
|
response_generator=self._rns_handler,
|
||||||
allow=RNS.Destination.ALLOW_ALL,
|
allow=RNS.Destination.ALLOW_ALL,
|
||||||
)
|
)
|
||||||
self.destination.announce()
|
self.destination.announce(app_data=FORUM_APP.encode("utf-8"))
|
||||||
|
# Auto-discover other forum instances via announces
|
||||||
|
self._announce_handler = _ForumAnnounceHandler(self.fdb, self.identity)
|
||||||
|
RNS.Transport.register_announce_handler(self._announce_handler)
|
||||||
self._running = True
|
self._running = True
|
||||||
self._thread = threading.Thread(target=self._sync_loop, daemon=True)
|
self._thread = threading.Thread(target=self._sync_loop, daemon=True)
|
||||||
self._thread.start()
|
self._thread.start()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self._running = False
|
self._running = False
|
||||||
|
if self._announce_handler:
|
||||||
|
try:
|
||||||
|
RNS.Transport.deregister_announce_handler(self._announce_handler)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
def _rns_handler(self, path, data, request_id, link_id, remote_identity, requested_at):
|
def _rns_handler(self, path, data, request_id, link_id, remote_identity, requested_at):
|
||||||
if remote_identity:
|
if remote_identity:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue