upleb.uk

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

summaryrefslogtreecommitdiff
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
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.
-rw-r--r--src/main.cpp31
-rw-r--r--src/main_gui.cpp18
-rw-r--r--src/net.cpp115
-rw-r--r--src/net.h19
-rw-r--r--src/nostr.cpp30
-rw-r--r--src/nostr.h8
6 files changed, 2 insertions, 219 deletions
diff --git a/src/main.cpp b/src/main.cpp
index bb317d2..a75bf54 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1877,14 +1877,6 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
1877 pfrom->PushMessage("getblocks", CBlockLocator(pindexBest), uint256(0)); 1877 pfrom->PushMessage("getblocks", CBlockLocator(pindexBest), uint256(0));
1878 } 1878 }
1879 1879
1880 // Introduce ourselves and hand over the .btf peers that answered us.
1881 // Everything in here is self-certifying, so this is safe to send to a
1882 // node we know nothing about, and safe for it to act on.
1883 vector<string> vPexOut;
1884 BtfPexCollect(vPexOut);
1885 if (!vPexOut.empty())
1886 pfrom->PushMessage("btfpeers", vPexOut);
1887
1888 if (LogAcceptsCategory("net")) printf("version addrMe = %s\n", addrMe.ToString().c_str()); 1880 if (LogAcceptsCategory("net")) printf("version addrMe = %s\n", addrMe.ToString().c_str());
1889 } 1881 }
1890 1882
@@ -1896,29 +1888,6 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
1896 } 1888 }
1897 1889
1898 1890
1899 else if (strCommand == "btfpeers")
1900 {
1901 // Peer exchange. Nothing here is taken on trust: BtfPexAccept checks
1902 // every descriptor's signature against the key its .btf address
1903 // decodes to, and drops the rest.
1904 int64 nNow = GetTime();
1905 if (nNow - pfrom->nLastPexRecv < PEX_MIN_INTERVAL)
1906 {
1907 if (LogAcceptsCategory("net"))
1908 printf("btfpeers: ignoring, %s is sending them too fast\n",
1909 pfrom->addr.ToString().c_str());
1910 return true;
1911 }
1912 pfrom->nLastPexRecv = nNow;
1913
1914 vector<string> vDesc;
1915 vRecv >> vDesc;
1916 int nKept = BtfPexAccept(vDesc);
1917 LogPrint("net", "btfpeers: kept %d of %zu descriptor(s) from %s\n",
1918 nKept, vDesc.size(), pfrom->addr.ToString().c_str());
1919 }
1920
1921
1922 else if (strCommand == "inv") 1891 else if (strCommand == "inv")
1923 { 1892 {
1924 vector<CInv> vInv; 1893 vector<CInv> vInv;
diff --git a/src/main_gui.cpp b/src/main_gui.cpp
index edc772d..842b0d5 100644
--- a/src/main_gui.cpp
+++ b/src/main_gui.cpp
@@ -58,7 +58,6 @@ static void PrintUsage()
58 printf(" /connectbtf=PEER_BTF_ADDRESS\n"); 58 printf(" /connectbtf=PEER_BTF_ADDRESS\n");
59 printf(" /rvrelay=HOST:PORT\n"); 59 printf(" /rvrelay=HOST:PORT\n");
60 printf(" /announcerelay=HOST:PORT\n"); 60 printf(" /announcerelay=HOST:PORT\n");
61 printf(" /port=N (P2P listen port, default 8433)\n");
62 printf("\n"); 61 printf("\n");
63 printf("Each option also accepts '-' instead of '/'.\n"); 62 printf("Each option also accepts '-' instead of '/'.\n");
64} 63}
@@ -114,23 +113,6 @@ static void ParseStartupArguments(int argc, char* argv[])
114 string announceRelay = argval2(argc, argv, "/announcerelay", "-announcerelay"); 113 string announceRelay = argval2(argc, argv, "/announcerelay", "-announcerelay");
115 if (!announceRelay.empty()) 114 if (!announceRelay.empty())
116 strBtfAnnounceRelay = announceRelay; 115 strBtfAnnounceRelay = announceRelay;
117
118 // net.cpp has claimed nListenPort was "tunable via /port" since it was
119 // written, but nothing ever read the option. Without it a second node
120 // cannot start on a machine that already runs one -- which is exactly what
121 // testing peer exchange needs.
122 string strPort = argval2(argc, argv, "/port", "-port");
123 if (!strPort.empty())
124 {
125 int nPort = atoi(strPort.c_str());
126 if (nPort <= 0 || nPort > 65535)
127 fprintf(stderr, "Ignoring /port=%s: not a port number\n", strPort.c_str());
128 else
129 {
130 nListenPort = htons((unsigned short)nPort);
131 addrLocalHost.port = nListenPort; // or we advertise a port we never bound
132 }
133 }
134} 116}
135 117
136int main(int argc, char* argv[]) 118int main(int argc, char* argv[])
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{
diff --git a/src/net.h b/src/net.h
index 7d70721..f028c3b 100644
--- a/src/net.h
+++ b/src/net.h
@@ -29,21 +29,6 @@ CNode* ConnectNodeBtfResolved(const string& strBtfAddr, const string& strMeeting
29void ThreadBtfAccept(void* parg); 29void ThreadBtfAccept(void* parg);
30void ThreadBtfConnect(void* parg); 30void ThreadBtfConnect(void* parg);
31extern string strBtfConnect; 31extern string strBtfConnect;
32
33// --- .btf peer exchange ---------------------------------------------------
34// Peers gossip signed descriptors so discovery survives a relay outage. Both
35// caps apply to a single "btfpeers" message; the generic header limit is 256 MB,
36// which is no protection at all for something a stranger can send unprompted.
37static const unsigned int MAX_PEX_DESCRIPTORS = 20;
38static const unsigned int MAX_PEX_DESCRIPTOR_BYTES = 1024;
39// Ignore a peer's exchange more often than this. One announcement per side per
40// connection is the intended traffic; anything faster is someone else's idea.
41static const int64 PEX_MIN_INTERVAL = 60;
42
43// Descriptors to hand a peer: ours first, then peers that answered us.
44void BtfPexCollect(std::vector<std::string>& vDescOut);
45// Verify descriptors a peer sent and remember the good ones. Returns how many.
46int BtfPexAccept(const std::vector<std::string>& vDesc);
47void AbandonRequests(void (*fn)(void*, CDataStream&), void* param1); 32void AbandonRequests(void (*fn)(void*, CDataStream&), void* param1);
48bool AnySubscribed(unsigned int nChannel); 33bool AnySubscribed(unsigned int nChannel);
49void ThreadBitcoinMiner(void* parg); 34void ThreadBitcoinMiner(void* parg);
@@ -482,9 +467,6 @@ public:
482 // publish and subscription 467 // publish and subscription
483 vector<char> vfSubscribe; 468 vector<char> vfSubscribe;
484 469
485 // Last "btfpeers" we accepted from this node, to rate-limit the exchange.
486 int64 nLastPexRecv;
487
488 470
489 CNode(SOCKET hSocketIn, CAddress addrIn, bool fInboundIn=false) 471 CNode(SOCKET hSocketIn, CAddress addrIn, bool fInboundIn=false)
490 { 472 {
@@ -501,7 +483,6 @@ public:
501 fDisconnect = false; 483 fDisconnect = false;
502 nRefCount = 0; 484 nRefCount = 0;
503 nReleaseTime = 0; 485 nReleaseTime = 0;
504 nLastPexRecv = 0;
505 vfSubscribe.assign(256, false); 486 vfSubscribe.assign(256, false);
506 487
507 // Push a version message 488 // Push a version message
diff --git a/src/nostr.cpp b/src/nostr.cpp
index 482a835..ec02c2e 100644
--- a/src/nostr.cpp
+++ b/src/nostr.cpp
@@ -342,36 +342,6 @@ std::string BtfLocalAddress()
342 return g_nostrKey.BtfAddress(); 342 return g_nostrKey.BtfAddress();
343} 343}
344 344
345// This node's own descriptor, signed and self-certifying -- the same blob
346// PublishDescriptor puts on Nostr, but handed straight to a peer instead.
347//
348// Reads strBtfActiveRelay directly rather than going through BtfActiveRelay():
349// only a rendezvous we actually registered at is worth announcing. Handing out
350// a meeting node we are not reachable at would make every peer that receives
351// it burn a dial, and over peer exchange that mistake travels further than it
352// does over Nostr.
353std::string BtfLocalDescriptor()
354{
355 if (!EnsureNostrKey())
356 return "";
357 string meeting;
358 CRITICAL_BLOCK(cs_activeRelay)
359 meeting = strBtfActiveRelay;
360 if (meeting.empty())
361 return "";
362 return btf::SignDescriptor(g_nostrKey.ctx, g_nostrKey.seckey,
363 g_nostrKey.EncPubHex(), meeting, (uint64_t)GetTime());
364}
365
366// The shared secp256k1 context, so net.cpp can verify descriptors that arrive
367// from peers. NULL if the identity could not be loaded.
368void* BtfSecpContext()
369{
370 if (!EnsureNostrKey())
371 return NULL;
372 return (void*)g_nostrKey.ctx;
373}
374
375int GetDiscoveredPeerCount() 345int GetDiscoveredPeerCount()
376{ 346{
377 int n = 0; 347 int n = 0;
diff --git a/src/nostr.h b/src/nostr.h
index b5eb5e3..04149e3 100644
--- a/src/nostr.h
+++ b/src/nostr.h
@@ -82,14 +82,6 @@ bool BtfGetIdentity(unsigned char pubkey[32], unsigned char enc_sk[32]);
82// This node's printable .btf address, or "" if the identity failed to load. 82// This node's printable .btf address, or "" if the identity failed to load.
83std::string BtfLocalAddress(); 83std::string BtfLocalAddress();
84 84
85// This node's own signed descriptor, ready to hand to a peer over the wire.
86// "" until ThreadBtfAccept has registered at a rendezvous.
87std::string BtfLocalDescriptor();
88
89// The shared secp256k1 context, for btf::VerifyDescriptor (which takes it as
90// void* to keep headers free of secp). NULL if the identity failed to load.
91void* BtfSecpContext();
92
93// Resolve a .btf address via the public discovery relays: fetch the owner's 85// Resolve a .btf address via the public discovery relays: fetch the owner's
94// self-certified descriptor and return its meeting node ("host:port") and 86// self-certified descriptor and return its meeting node ("host:port") and
95// x25519 public key for the end-to-end channel. 87// x25519 public key for the end-to-end channel.