auto peer discovery: gossip known peers during sync, discover from_hash automatically
This commit is contained in:
parent
d201fa0bc9
commit
0ed4ec82ba
4 changed files with 87 additions and 1 deletions
|
|
@ -334,6 +334,50 @@ def test_sync_updated_content():
|
||||||
shutil.rmtree(dir_b)
|
shutil.rmtree(dir_b)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sync_peer_discovery():
|
||||||
|
"""Peers should discover each other through sync gossip."""
|
||||||
|
dir_a = tempfile.mkdtemp()
|
||||||
|
dir_b = tempfile.mkdtemp()
|
||||||
|
dir_c = tempfile.mkdtemp()
|
||||||
|
try:
|
||||||
|
db_a, ha = make_handler(dir_a)
|
||||||
|
db_b, hb = make_handler(dir_b)
|
||||||
|
db_c, hc = make_handler(dir_c)
|
||||||
|
|
||||||
|
now = "2026-06-05T12:00:00"
|
||||||
|
db_a.create_thread("t1", "From A", "", "", "", "aaa", "", now)
|
||||||
|
db_b.create_thread("t2", "From B", "", "", "", "bbb", "", now)
|
||||||
|
db_c.create_thread("t3", "From C", "", "", "", "ccc", "", now)
|
||||||
|
|
||||||
|
# Seed: A knows B, B knows C
|
||||||
|
db_a.add_known_peer("bbb")
|
||||||
|
db_b.add_known_peer("ccc")
|
||||||
|
# C doesn't know anyone yet
|
||||||
|
|
||||||
|
# B syncs with C — B sends its known peers (ccc's hash not in B's known peers since B is talking to C)
|
||||||
|
# Actually, B knows C (bbb -> ccc), so when B sends sync request to C,
|
||||||
|
# B includes known_peers. C learns about B.
|
||||||
|
hc.handle_sync_request({
|
||||||
|
"query": {},
|
||||||
|
"threads": [dict(db_b.get_thread("t2"))],
|
||||||
|
"posts": [], "upvotes": [],
|
||||||
|
"from_hash": "bbb",
|
||||||
|
"blocks": {"mine": [], "peers": []},
|
||||||
|
"retractions": [],
|
||||||
|
"known_peers": ["aaa"], # B tells C about A
|
||||||
|
})
|
||||||
|
|
||||||
|
# C should now know about B (from auto-discovery of from_hash) and A (from known_peers)
|
||||||
|
known = [r["instance_hash"] for r in db_c.get_synced_instances()]
|
||||||
|
assert "bbb" in known, "C should auto-discover B from from_hash"
|
||||||
|
assert "aaa" in known, "C should discover A from known_peers gossip"
|
||||||
|
finally:
|
||||||
|
import shutil
|
||||||
|
shutil.rmtree(dir_a)
|
||||||
|
shutil.rmtree(dir_b)
|
||||||
|
shutil.rmtree(dir_c)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
test_sync_thread()
|
test_sync_thread()
|
||||||
test_sync_reply()
|
test_sync_reply()
|
||||||
|
|
@ -343,4 +387,5 @@ if __name__ == "__main__":
|
||||||
test_sync_bidirectional()
|
test_sync_bidirectional()
|
||||||
test_sync_block_gossip()
|
test_sync_block_gossip()
|
||||||
test_sync_updated_content()
|
test_sync_updated_content()
|
||||||
|
test_sync_peer_discovery()
|
||||||
print("all sync tests passed")
|
print("all sync tests passed")
|
||||||
|
|
|
||||||
|
|
@ -246,6 +246,28 @@ class ForumDB:
|
||||||
finally:
|
finally:
|
||||||
self.return_db(db)
|
self.return_db(db)
|
||||||
|
|
||||||
|
def add_known_peer(self, instance_hash):
|
||||||
|
"""Add a discovered peer to the sync list (auto-discovery)."""
|
||||||
|
db = self.get_db()
|
||||||
|
try:
|
||||||
|
db.execute(
|
||||||
|
"INSERT OR IGNORE INTO synced_instances (instance_hash) VALUES (?)",
|
||||||
|
(instance_hash,),
|
||||||
|
)
|
||||||
|
db.commit()
|
||||||
|
finally:
|
||||||
|
self.return_db(db)
|
||||||
|
|
||||||
|
def get_all_known_hashes(self):
|
||||||
|
"""Get all known instance hashes for peer discovery gossip."""
|
||||||
|
db = self.get_db()
|
||||||
|
try:
|
||||||
|
return [r["instance_hash"] for r in db.execute(
|
||||||
|
"SELECT instance_hash FROM synced_instances"
|
||||||
|
).fetchall()]
|
||||||
|
finally:
|
||||||
|
self.return_db(db)
|
||||||
|
|
||||||
def upsert_synced_instance(self, instance_hash, name=""):
|
def upsert_synced_instance(self, instance_hash, name=""):
|
||||||
db = self.get_db()
|
db = self.get_db()
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -604,6 +604,14 @@ class ForumHandlers:
|
||||||
if r.get("id") and r.get("type") and r.get("author") and r.get("at"):
|
if r.get("id") and r.get("type") and r.get("author") and r.get("at"):
|
||||||
self.fdb.merge_retraction(r["id"], r["type"], r["author"], r["at"])
|
self.fdb.merge_retraction(r["id"], r["type"], r["author"], r["at"])
|
||||||
|
|
||||||
|
# Auto-discover the peer that synced with us and their known peers
|
||||||
|
from_hash = data.get("from_hash", "")
|
||||||
|
if from_hash and from_hash not in blocked:
|
||||||
|
self.fdb.add_known_peer(from_hash)
|
||||||
|
for peer_hash in data.get("known_peers", []):
|
||||||
|
if peer_hash and peer_hash != from_hash and peer_hash not in blocked:
|
||||||
|
self.fdb.add_known_peer(peer_hash)
|
||||||
|
|
||||||
my_blocks = list(blocked)
|
my_blocks = list(blocked)
|
||||||
my_peer_blocks = self.fdb.get_peer_block_list()
|
my_peer_blocks = self.fdb.get_peer_block_list()
|
||||||
threads, posts, upvote_threads = [], [], []
|
threads, posts, upvote_threads = [], [], []
|
||||||
|
|
@ -616,6 +624,8 @@ class ForumHandlers:
|
||||||
retracted = [{"id": cid, "type": ct, "author": ai, "at": ra}
|
retracted = [{"id": cid, "type": ct, "author": ai, "at": ra}
|
||||||
for cid, ct, ai, ra in self.fdb.get_raw_retractions()]
|
for cid, ct, ai, ra in self.fdb.get_raw_retractions()]
|
||||||
|
|
||||||
|
known_peers = [h for h in self.fdb.get_all_known_hashes() if h != from_hash]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"content_type": "application/json",
|
"content_type": "application/json",
|
||||||
|
|
@ -625,6 +635,7 @@ class ForumHandlers:
|
||||||
"upvote_threads": upvote_threads,
|
"upvote_threads": upvote_threads,
|
||||||
"blocks": {"mine": my_blocks, "peers": my_peer_blocks},
|
"blocks": {"mine": my_blocks, "peers": my_peer_blocks},
|
||||||
"retractions": retracted,
|
"retractions": retracted,
|
||||||
|
"known_peers": known_peers,
|
||||||
}),
|
}),
|
||||||
"headers": {},
|
"headers": {},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -116,14 +116,18 @@ class ForumSync:
|
||||||
retracted = [{"id": cid, "type": ct, "author": ai, "at": ra}
|
retracted = [{"id": cid, "type": ct, "author": ai, "at": ra}
|
||||||
for cid, ct, ai, ra in self.fdb.get_raw_retractions()]
|
for cid, ct, ai, ra in self.fdb.get_raw_retractions()]
|
||||||
|
|
||||||
|
my_hash = self.identity.hash.hex() if self.identity else "local"
|
||||||
|
known_peers = [h for h in self.fdb.get_all_known_hashes() if h != instance_hash and h != my_hash]
|
||||||
|
|
||||||
request_data = {
|
request_data = {
|
||||||
"query": {"since": [since]} if since else {},
|
"query": {"since": [since]} if since else {},
|
||||||
"threads": threads,
|
"threads": threads,
|
||||||
"posts": posts,
|
"posts": posts,
|
||||||
"upvotes": upvotes,
|
"upvotes": upvotes,
|
||||||
"from_hash": self.identity.hash.hex() if self.identity else "local",
|
"from_hash": my_hash,
|
||||||
"blocks": {"mine": my_blocks, "peers": my_peer_blocks},
|
"blocks": {"mine": my_blocks, "peers": my_peer_blocks},
|
||||||
"retractions": retracted,
|
"retractions": retracted,
|
||||||
|
"known_peers": known_peers,
|
||||||
}
|
}
|
||||||
|
|
||||||
receipt = link.request("/forum", data=request_data, timeout=REQUEST_TIMEOUT)
|
receipt = link.request("/forum", data=request_data, timeout=REQUEST_TIMEOUT)
|
||||||
|
|
@ -162,6 +166,10 @@ class ForumSync:
|
||||||
for r in data.get("retractions", []):
|
for r in data.get("retractions", []):
|
||||||
if r.get("id") and r.get("type") and r.get("author") and r.get("at"):
|
if r.get("id") and r.get("type") and r.get("author") and r.get("at"):
|
||||||
self.fdb.merge_retraction(r["id"], r["type"], r["author"], r["at"])
|
self.fdb.merge_retraction(r["id"], r["type"], r["author"], r["at"])
|
||||||
|
# Discover new peers from gossip
|
||||||
|
for peer_hash in data.get("known_peers", []):
|
||||||
|
if peer_hash and peer_hash != my_hash and peer_hash != instance_hash:
|
||||||
|
self.fdb.add_known_peer(peer_hash)
|
||||||
now = time.strftime("%Y-%m-%dT%H:%M:%S")
|
now = time.strftime("%Y-%m-%dT%H:%M:%S")
|
||||||
self.fdb.set_last_sync(instance_hash, now)
|
self.fdb.set_last_sync(instance_hash, now)
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue