upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/net.cpp
diff options
context:
space:
mode:
authorBitflash <btf01@proton.me>2026-07-29 12:05:12 -0300
committerGitHub <noreply@github.com>2026-07-29 12:05:12 -0300
commit137a166fe0b95ccdef444041e907358bd26bc226 (patch)
treebab295f18576f5d139bd7746f3c141fbbdfea3e2 /src/net.cpp
parent96a8e42bbc313f96d54377ae5886da84298e61a3 (diff)
Remove peer exchange from main; it was merged unreviewed (#27)
PR #25 was opened, reviewed and approved as a single-file change to net.cpp: the .btf peer cache, +169/-0. I then pushed peer exchange and a /port option onto that same branch while the PR was open, which silently widened it to six files. The merge carried all of it in. Nothing about the peer exchange had been validated. Its two-node test never completed -- node B could not dial node A at all -- so what landed on main is unproven network protocol code that no one agreed to take. This restores main to what #25 actually described. The peer cache stays, with its runtime numbers (36.3s to 2.1s to first peer). #26 stays. What comes out: - the "btfpeers" message, both directions - BtfPexCollect / BtfPexAccept and the per-message caps - CNode::nLastPexRecv - the `desc` field on the cached peer record - BtfLocalDescriptor / BtfSecpContext - /port Peer exchange returns as its own pull request once its test passes. The /port option is worth having on its own -- two nodes cannot share a machine without it, which is what blocked the test -- and should go up separately rather than ride along again. Release v1.2.2 is unaffected: it was built from ba2de82, before either merge.
Diffstat (limited to 'src/net.cpp')
-rw-r--r--src/net.cpp115
1 files changed, 2 insertions, 113 deletions
diff --git a/src/net.cpp b/src/net.cpp
index dfeb4a2..7b2ef83 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -285,13 +285,6 @@ struct CachedBtfPeer
285 string meeting; 285 string meeting;
286 string encHex; 286 string encHex;
287 int64 lastSeen; 287 int64 lastSeen;
288 // The peer's own signed descriptor, as it announced itself. Kept verbatim
289 // because that signature is what lets us hand this peer on to somebody
290 // else: the receiver checks it against the key the address decodes to and
291 // never has to take our word for anything. Empty for entries learned
292 // before peer exchange existed, or resolved through Nostr -- those are
293 // still dialable, just not relayable.
294 string desc;
295}; 288};
296 289
297static string BtfPeerCachePath() { return GetAppDir() + "/btfpeers.json"; } 290static string BtfPeerCachePath() { return GetAppDir() + "/btfpeers.json"; }
@@ -343,8 +336,6 @@ void LoadCachedBtfPeers(vector<CachedBtfPeer>& out)
343 p.meeting = o["meeting"].get<string>(); 336 p.meeting = o["meeting"].get<string>();
344 p.encHex = o["enc"].get<string>(); 337 p.encHex = o["enc"].get<string>();
345 p.lastSeen = o.value("seen", (int64)0); 338 p.lastSeen = o.value("seen", (int64)0);
346 if (o.contains("desc") && o["desc"].is_string())
347 p.desc = o["desc"].get<string>();
348 if (nNow - p.lastSeen > CACHED_BTF_PEER_TTL) continue; // long gone 339 if (nNow - p.lastSeen > CACHED_BTF_PEER_TTL) continue; // long gone
349 out.push_back(p); 340 out.push_back(p);
350 } 341 }
@@ -358,35 +349,22 @@ void LoadCachedBtfPeers(vector<CachedBtfPeer>& out)
358 349
359static CCriticalSection cs_btfPeerCache; 350static CCriticalSection cs_btfPeerCache;
360 351
361// strDesc is the peer's own signed descriptor when we have it (it announced
362// itself over peer exchange), "" when we only resolved it through Nostr. An
363// empty one never clears a descriptor already on file: dialing a peer we first
364// learned about by exchange must not cost us the ability to pass it on.
365static void RememberBtfPeer(const string& strBtfAddr, const string& strMeeting, 352static void RememberBtfPeer(const string& strBtfAddr, const string& strMeeting,
366 const unsigned char enc_pub[32], 353 const unsigned char enc_pub[32])
367 const string& strDesc = string())
368{ 354{
369 CRITICAL_BLOCK(cs_btfPeerCache) 355 CRITICAL_BLOCK(cs_btfPeerCache)
370 { 356 {
371 vector<CachedBtfPeer> peers; 357 vector<CachedBtfPeer> peers;
372 LoadCachedBtfPeers(peers); 358 LoadCachedBtfPeers(peers);
373 359
374 string strKeepDesc = strDesc;
375 for (size_t i = 0; i < peers.size(); i++) 360 for (size_t i = 0; i < peers.size(); i++)
376 if (peers[i].btfAddr == strBtfAddr) 361 if (peers[i].btfAddr == strBtfAddr) { peers.erase(peers.begin() + i); break; }
377 {
378 if (strKeepDesc.empty())
379 strKeepDesc = peers[i].desc;
380 peers.erase(peers.begin() + i);
381 break;
382 }
383 362
384 CachedBtfPeer p; 363 CachedBtfPeer p;
385 p.btfAddr = strBtfAddr; 364 p.btfAddr = strBtfAddr;
386 p.meeting = strMeeting; 365 p.meeting = strMeeting;
387 p.encHex = BytesToHex(enc_pub, 32); 366 p.encHex = BytesToHex(enc_pub, 32);
388 p.lastSeen = GetTime(); 367 p.lastSeen = GetTime();
389 p.desc = strKeepDesc;
390 peers.insert(peers.begin(), p); // most recent first 368 peers.insert(peers.begin(), p); // most recent first
391 369
392 if (peers.size() > MAX_CACHED_BTF_PEERS) 370 if (peers.size() > MAX_CACHED_BTF_PEERS)
@@ -400,8 +378,6 @@ static void RememberBtfPeer(const string& strBtfAddr, const string& strMeeting,
400 o["meeting"] = q.meeting; 378 o["meeting"] = q.meeting;
401 o["enc"] = q.encHex; 379 o["enc"] = q.encHex;
402 o["seen"] = q.lastSeen; 380 o["seen"] = q.lastSeen;
403 if (!q.desc.empty())
404 o["desc"] = q.desc;
405 arr.push_back(o); 381 arr.push_back(o);
406 } 382 }
407 FILE* f = fopen(BtfPeerCachePath().c_str(), "w"); 383 FILE* f = fopen(BtfPeerCachePath().c_str(), "w");
@@ -443,93 +419,6 @@ void ThreadReconnectCachedBtfPeers(void* parg)
443 nConnected, peers.size()); 419 nConnected, peers.size());
444} 420}
445 421
446//
447// .btf peer exchange
448//
449// Until now discovery depended entirely on the Nostr relays: two connected
450// nodes never told each other who else existed, so a relay outage left a
451// running network unable to grow, and a node that found one stale peer had no
452// way to learn there was anything better.
453//
454// What travels here is each peer's own signed descriptor. The receiver checks
455// the Schnorr signature against the key the `.btf` address decodes to, so the
456// sender is trusted for nothing -- forging an entry would need somebody else's
457// secret key. That leaves flooding: valid descriptors for keys the sender
458// generated itself. The caps below bound one message, and the cache prefers
459// peers that actually answered, so a flood costs a Sybil more than it costs us.
460//
461
462void BtfPexCollect(vector<string>& vDescOut)
463{
464 vDescOut.clear();
465
466 // Ours first -- the node on the other end may know nobody but us.
467 string strMine = BtfLocalDescriptor();
468 if (!strMine.empty())
469 vDescOut.push_back(strMine);
470
471 vector<CachedBtfPeer> peers;
472 CRITICAL_BLOCK(cs_btfPeerCache)
473 LoadCachedBtfPeers(peers);
474
475 // Most recently seen first: these answered us, they are not names copied
476 // off a relay listing.
477 foreach(const CachedBtfPeer& p, peers)
478 {
479 if (vDescOut.size() >= MAX_PEX_DESCRIPTORS) break;
480 if (p.desc.empty()) continue; // nothing provable to pass on
481 if (p.desc.size() > MAX_PEX_DESCRIPTOR_BYTES) continue;
482 vDescOut.push_back(p.desc);
483 }
484}
485
486int BtfPexAccept(const vector<string>& vDesc)
487{
488 void* ctx = BtfSecpContext();
489 if (!ctx)
490 return 0;
491
492 string strSelf = BtfLocalAddress();
493 int nKept = 0;
494 unsigned int nSeen = 0;
495
496 foreach(const string& strDesc, vDesc)
497 {
498 if (++nSeen > MAX_PEX_DESCRIPTORS) break;
499 if (strDesc.empty() || strDesc.size() > MAX_PEX_DESCRIPTOR_BYTES) continue;
500
501 // The descriptor names the key it belongs to, and taking that key from
502 // the blob is safe precisely because the signature must verify under
503 // it: nobody can produce a valid descriptor for a key they don't hold.
504 unsigned char pubkey[32];
505 try
506 {
507 nlohmann::json v = nlohmann::json::parse(strDesc);
508 if (!v.is_object() || !v.contains("pubkey") || !v["pubkey"].is_string())
509 continue;
510 if (!HexToBytes(v["pubkey"].get<string>(), pubkey, 32))
511 continue;
512 }
513 catch (...) { continue; } // malformed JSON from an untrusted peer
514
515 btf::Descriptor d;
516 if (!btf::VerifyDescriptor(ctx, strDesc, pubkey, d))
517 continue;
518
519 string strAddr = btf::Address(pubkey);
520 if (strAddr.empty() || strAddr == strSelf)
521 continue; // ourselves, nothing to learn
522
523 unsigned char enc[32];
524 if (!HexToBytes(d.enc, enc, 32))
525 continue;
526
527 RememberBtfPeer(strAddr, d.meeting_node, enc, strDesc);
528 nKept++;
529 }
530 return nKept;
531}
532
533static CNode* ConnectNodeBtfTail(const string& strBtfAddr, const unsigned char pk[32], 422static CNode* ConnectNodeBtfTail(const string& strBtfAddr, const unsigned char pk[32],
534 const string& strMeeting, const unsigned char enc_pub[32]) 423 const string& strMeeting, const unsigned char enc_pub[32])
535{ 424{