upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/nostr.cpp
diff options
context:
space:
mode:
authorbitflash-maintainer <noreply@bitflash.invalid>2026-09-05 22:46:52 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-09-05 22:46:52 +0000
commitceae3d076b5ad11ee84729ae43f14e9312896646 (patch)
tree7e9132aa871fdc01cb95268d2f5ea3bb11853ddf /src/nostr.cpp
parentbe63e83d79e7e00e6b8ca889a70e6a19886aa1d8 (diff)
parent66e0b9158d050c46b1ff2509faeee3f4573cac1c (diff)
Bring main onto the line this project actually ships
main had drifted to a history with no ancestor in common with the tree we build and deploy. It sat at be63e83 (2026-08-09) with 28 commits; the shipped line, deploy/soak-2026-08-21, had 383. The two shared nothing -- git merge-base returns no result at all -- so anyone cloning this repository landed on a default branch that was neither current nor related to the running node. The 28 commits on main were audited before this merge: every one is already present in the tree by content, including the two that are hardest to see because they were rewritten afterwards -- the payout reorg-safety work (PayoutBlockStillOnChain, since evolved to re-check on every retry rather than once at maturity) and the nostr relay certificate verification (SSL_set1_host, now with a Windows root-store path). Nothing on main is lost by this merge. Recorded as a merge rather than a force-push: main only ever moves forward, its old history stays reachable as the first parent, and this is revertible. The tree is taken wholesale from the merged branch, so main's content is now exactly what was built and tested. What this brings onto main: - compactblock: BIP152-style compact block relay ported from upstream 1.2.23, primitives and unit test only, with the RAND_bytes failure path closed and the reconstruction allocation bounded. - upstream/main 17741d9: isolated testnet parameters and genesis, optional 2 MB large pages for RandomX. Mainnet genesis verified byte-identical through upstream's BuildGenesisBlock() refactor. - deploy: every image built on bookworm so binaries run on Debian 12, and the container build now honours the RandomX and libsecp256k1 pins it had been silently ignoring since before 1.2.15. Gates at this tree: 15/15 selftests, compactblock's 17 checks, consensus constants unchanged against e4f898c apart from the pre-existing MAX_BLOCK_SIZE_GEN, mainnet genesis intact, money-log check ok.
Diffstat (limited to 'src/nostr.cpp')
-rw-r--r--src/nostr.cpp256
1 files changed, 177 insertions, 79 deletions
diff --git a/src/nostr.cpp b/src/nostr.cpp
index 3f43302..f44dc08 100644
--- a/src/nostr.cpp
+++ b/src/nostr.cpp
@@ -7,6 +7,10 @@
7#include <openssl/ssl.h> 7#include <openssl/ssl.h>
8#include <openssl/err.h> 8#include <openssl/err.h>
9#include <openssl/x509.h> 9#include <openssl/x509.h>
10#include <openssl/x509v3.h>
11#ifdef _WIN32
12#include <wincrypt.h>
13#endif
10// We link libsecp256k1 statically (without this the header assumes DLL import). 14// We link libsecp256k1 statically (without this the header assumes DLL import).
11// Both makefiles also pass -DSECP256K1_STATIC; the guard keeps that from 15// Both makefiles also pass -DSECP256K1_STATIC; the guard keeps that from
12// warning while leaving this file correct if built without the flag. 16// warning while leaving this file correct if built without the flag.
@@ -26,6 +30,7 @@
26 30
27#include "btfaddr.h" 31#include "btfaddr.h"
28#include "btfchan.h" 32#include "btfchan.h"
33#include "proxy.h"
29 34
30using json = nlohmann::json; 35using json = nlohmann::json;
31 36
@@ -127,6 +132,8 @@ vector<string> vBtfMeetingRelays = {
127bool fBtfRelayPinned = false; 132bool fBtfRelayPinned = false;
128string strBtfActiveRelay; 133string strBtfActiveRelay;
129static CCriticalSection cs_activeRelay; 134static CCriticalSection cs_activeRelay;
135static string strBtfOnionEndpoint;
136static CCriticalSection cs_onionEndpoint;
130 137
131// Only the rendezvous we actually registered at (BtfSetActiveRelay, called from 138// Only the rendezvous we actually registered at (BtfSetActiveRelay, called from
132// ThreadBtfAccept once registration succeeds). Empty until then, on purpose: it 139// ThreadBtfAccept once registration succeeds). Empty until then, on purpose: it
@@ -144,10 +151,43 @@ string BtfActiveRelay()
144 151
145void BtfSetActiveRelay(const string& relay) 152void BtfSetActiveRelay(const string& relay)
146{ 153{
154 // With /rvrelay set, the descriptor must never name anything but the pin.
155 // No caller can reach here with another relay today -- both registration
156 // paths draw their list from BtfAllRelays(), which returns the pin alone --
157 // but the descriptor is the one thing a miner obeys, so the invariant is
158 // enforced at the sink instead of being retrusted to every future caller.
159 if (fBtfRelayPinned &&
160 find(vBtfMeetingRelays.begin(), vBtfMeetingRelays.end(), relay) == vBtfMeetingRelays.end())
161 {
162 LogPrint("pool", "refusing to advertise meeting relay %s: /rvrelay pins %s\n",
163 relay.c_str(),
164 vBtfMeetingRelays.empty() ? "an empty list" : vBtfMeetingRelays[0].c_str());
165 return;
166 }
147 CRITICAL_BLOCK(cs_activeRelay) 167 CRITICAL_BLOCK(cs_activeRelay)
148 strBtfActiveRelay = relay; 168 strBtfActiveRelay = relay;
149} 169}
150 170
171bool BtfSetLocalOnionEndpoint(const string& endpoint, string& errOut)
172{
173 string norm;
174 if (!btf::NormalizeOnionEndpoint(endpoint, norm))
175 {
176 errOut = "expected v3 onion endpoint HOST.onion:PORT";
177 return false;
178 }
179 CRITICAL_BLOCK(cs_onionEndpoint)
180 strBtfOnionEndpoint = norm;
181 return true;
182}
183
184string BtfLocalOnionEndpoint()
185{
186 CRITICAL_BLOCK(cs_onionEndpoint)
187 return strBtfOnionEndpoint;
188 return "";
189}
190
151// A relay this node advertises on Nostr for others to discover (/announcerelay). 191// A relay this node advertises on Nostr for others to discover (/announcerelay).
152string strBtfAnnounceRelay; 192string strBtfAnnounceRelay;
153// Relays learned from other operators' Nostr announcements. 193// Relays learned from other operators' Nostr announcements.
@@ -196,6 +236,18 @@ vector<string> BtfAllRelays()
196 return all; 236 return all;
197} 237}
198 238
239// Relays learned from Nostr, whether or not a pin is in force. Reporting only:
240// it exists so the pool can name what /rvrelay is excluding. Anything choosing
241// a relay to register or dial at must keep using BtfAllRelays().
242vector<string> BtfDiscoveredRelays()
243{
244 vector<string> out;
245 CRITICAL_BLOCK(cs_discoveredRelays)
246 foreach(const string& r, g_discoveredRelays)
247 out.push_back(r);
248 return out;
249}
250
199 251
200// 252//
201// Utilities 253// Utilities
@@ -387,10 +439,16 @@ std::string BtfLocalDescriptor()
387 string meeting; 439 string meeting;
388 CRITICAL_BLOCK(cs_activeRelay) 440 CRITICAL_BLOCK(cs_activeRelay)
389 meeting = strBtfActiveRelay; 441 meeting = strBtfActiveRelay;
442 string onion = BtfLocalOnionEndpoint();
390 if (meeting.empty()) 443 if (meeting.empty())
391 return ""; 444 {
445 if (onion.empty())
446 return "";
447 meeting = "rendezvous-pending";
448 }
392 return btf::SignDescriptor(g_nostrKey.ctx, g_nostrKey.seckey, 449 return btf::SignDescriptor(g_nostrKey.ctx, g_nostrKey.seckey,
393 g_nostrKey.EncPubHex(), meeting, (uint64_t)GetTime()); 450 g_nostrKey.EncPubHex(), meeting, (uint64_t)GetTime(),
451 onion);
394} 452}
395 453
396// The shared secp256k1 context, so net.cpp can verify descriptors that arrive 454// The shared secp256k1 context, so net.cpp can verify descriptors that arrive
@@ -468,6 +526,41 @@ static bool BuildSignedEvent(CNostrKey& key, int kind, const json& tags,
468static const uint64 MAX_WS_FRAME = 1 << 20; // 1 MiB per frame 526static const uint64 MAX_WS_FRAME = 1 << 20; // 1 MiB per frame
469static const uint64 MAX_WS_MESSAGE = 4 << 20; // 4 MiB reassembled 527static const uint64 MAX_WS_MESSAGE = 4 << 20; // 4 MiB reassembled
470 528
529static int BtfConnectedNodeCount()
530{
531 int nNodes = 0;
532 CRITICAL_BLOCK(cs_vNodes)
533 {
534 nNodes = (int)vNodes.size();
535 }
536 return nNodes;
537}
538
539#ifdef _WIN32
540static bool BtfLoadWindowsRootCertificates(SSL_CTX* sslctx)
541{
542 HCERTSTORE hStore = CertOpenSystemStoreA(0, "ROOT");
543 if (!hStore)
544 return false;
545
546 bool fLoaded = false;
547 X509_STORE* x509store = SSL_CTX_get_cert_store(sslctx);
548 PCCERT_CONTEXT pCert = NULL;
549 while ((pCert = CertEnumCertificatesInStore(hStore, pCert)) != NULL)
550 {
551 const unsigned char* p = pCert->pbCertEncoded;
552 X509* x509 = d2i_X509(NULL, &p, pCert->cbCertEncoded);
553 if (!x509)
554 continue;
555 if (X509_STORE_add_cert(x509store, x509) == 1)
556 fLoaded = true;
557 X509_free(x509);
558 }
559 CertCloseStore(hStore, 0);
560 return fLoaded;
561}
562#endif
563
471class CWebSocket 564class CWebSocket
472{ 565{
473public: 566public:
@@ -523,53 +616,8 @@ public:
523 size_t colon = host.find(':'); 616 size_t colon = host.find(':');
524 if (colon != string::npos) { port = atoi(host.substr(colon + 1).c_str()); host = host.substr(0, colon); } 617 if (colon != string::npos) { port = atoi(host.substr(colon + 1).c_str()); host = host.substr(0, colon); }
525 618
526 // Resolve and connect (TCP). AF_UNSPEC so we get both A and AAAA 619 hSocket = BtfConnectSocket(host, (unsigned short)port, SOCK_NOSTR, timeoutSec);
527 // records back and try them in the order the resolver prefers, 620 if (hSocket == INVALID_SOCKET)
528 // rather than hard-failing whenever the IPv4 path to a relay is
529 // filtered/rate-limited (increasingly common for CDN-fronted
530 // relays) while a working IPv6 route exists right there in the
531 // same getaddrinfo() result.
532 struct addrinfo hints, *res = NULL, *rp = NULL;
533 memset(&hints, 0, sizeof(hints));
534 hints.ai_family = AF_UNSPEC;
535 hints.ai_socktype = SOCK_STREAM;
536 char portstr[16]; sprintf(portstr, "%d", port);
537 if (getaddrinfo(host.c_str(), portstr, &hints, &res) != 0 || !res)
538 return error("Nostr: getaddrinfo %s failed", host.c_str());
539
540 bool fConnected = false;
541 for (rp = res; rp != NULL; rp = rp->ai_next)
542 {
543 hSocket = BtfSocketTag(socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol), SOCK_NOSTR);
544 if (hSocket == INVALID_SOCKET)
545 continue;
546
547 // Socket receive/send timeout: Windows takes a DWORD of
548 // milliseconds; POSIX takes a struct timeval.
549#ifdef _WIN32
550 DWORD tv = timeoutSec * 1000;
551 setsockopt(hSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(tv));
552 setsockopt(hSocket, SOL_SOCKET, SO_SNDTIMEO, (char*)&tv, sizeof(tv));
553#else
554 struct timeval tv;
555 tv.tv_sec = timeoutSec;
556 tv.tv_usec = 0;
557 setsockopt(hSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(tv));
558 setsockopt(hSocket, SOL_SOCKET, SO_SNDTIMEO, (char*)&tv, sizeof(tv));
559#endif
560
561 if (connect(hSocket, rp->ai_addr, (int)rp->ai_addrlen) == 0)
562 {
563 fConnected = true;
564 break;
565 }
566
567 BtfCloseSocket(hSocket);
568 hSocket = INVALID_SOCKET;
569 }
570 freeaddrinfo(res);
571
572 if (!fConnected)
573 return error("Nostr: connect %s:%d failed", host.c_str(), port); 621 return error("Nostr: connect %s:%d failed", host.c_str(), port);
574 622
575 // TLS 623 // TLS
@@ -585,22 +633,35 @@ public:
585 // so a MITM cannot forge them, but it could drop the rendezvous 633 // so a MITM cannot forge them, but it could drop the rendezvous
586 // registration (leaving the pool unreachable to miners) or answer 634 // registration (leaving the pool unreachable to miners) or answer
587 // subscriptions with whatever it liked. 635 // subscriptions with whatever it liked.
588 if (!SSL_CTX_set_default_verify_paths(sslctx))
589 return error("Nostr: no system CA store available, refusing %s", host.c_str());
590 SSL_CTX_set_verify(sslctx, SSL_VERIFY_PEER, NULL); 636 SSL_CTX_set_verify(sslctx, SSL_VERIFY_PEER, NULL);
637
638 // OpenSSL's default verify paths are usually empty on Windows, so
639 // the system ROOT store is loaded too -- without it verification
640 // fails closed there and every relay becomes unreachable.
641 bool fTrustLoaded = (SSL_CTX_set_default_verify_paths(sslctx) == 1);
642#ifdef _WIN32
643 fTrustLoaded = BtfLoadWindowsRootCertificates(sslctx) || fTrustLoaded;
644#endif
645 if (!fTrustLoaded)
646 return error("Nostr: failed to load TLS trust store");
647
648 // Floor the protocol version. Verification says who the peer is,
649 // not how weak a cipher suite we will agree to; without this a
650 // relay offering nothing better still gets TLS 1.0.
591 SSL_CTX_set_min_proto_version(sslctx, TLS1_2_VERSION); 651 SSL_CTX_set_min_proto_version(sslctx, TLS1_2_VERSION);
592 652
593 ssl = SSL_new(sslctx); 653 ssl = SSL_new(sslctx);
594 if (!ssl) return false; 654 if (!ssl) return false;
595 SSL_set_fd(ssl, (int)hSocket); 655 SSL_set_fd(ssl, (int)hSocket);
596 SSL_set_tlsext_host_name(ssl, host.c_str()); // SNI 656 if (SSL_set_tlsext_host_name(ssl, host.c_str()) != 1) // SNI
657 return error("Nostr: failed to set TLS SNI for %s", host.c_str());
597 658
598 // Verifying the chain is not enough on its own: without this the 659 // Verifying the chain is not enough on its own: without this the
599 // relay could present any certificate a trusted CA ever issued, 660 // relay could present any certificate a trusted CA ever issued,
600 // for any name. SSL_set1_host binds the check to the host we asked 661 // for any name. SSL_set1_host binds the check to the host we asked
601 // for. 662 // for.
602 if (!SSL_set1_host(ssl, host.c_str())) 663 if (SSL_set1_host(ssl, host.c_str()) != 1)
603 return error("Nostr: cannot pin verification to %s", host.c_str()); 664 return error("Nostr: failed to set TLS hostname for %s", host.c_str());
604 665
605 if (SSL_connect(ssl) != 1) 666 if (SSL_connect(ssl) != 1)
606 { 667 {
@@ -846,17 +907,21 @@ static void PublishDescriptor(CWebSocket& ws, CNostrKey& key)
846 // meeting_node is the rendezvous relay this node's hidden service 907 // meeting_node is the rendezvous relay this node's hidden service
847 // (ThreadBtfAccept in net.cpp) is registered at -- where clients dial us. 908 // (ThreadBtfAccept in net.cpp) is registered at -- where clients dial us.
848 string meeting_node = BtfActiveRelay(); 909 string meeting_node = BtfActiveRelay();
910 string onion = BtfLocalOnionEndpoint();
849 if (meeting_node.empty()) 911 if (meeting_node.empty())
850 { 912 {
851 // Not registered anywhere yet. Publishing now would advertise a meeting 913 if (onion.empty())
852 // node we can't be reached at, and peers would waste dials on it. The 914 {
853 // ThreadNostrSeed loop republishes on every pass, so skipping costs 915 // Not registered anywhere yet, and no direct onion endpoint exists.
854 // nothing but the delay until ThreadBtfAccept registers. 916 // Publishing now would advertise a meeting node we can't be reached at.
855 LogPrint("nostr", "Nostr: skipping descriptor publish, no rendezvous registered yet\n"); 917 LogPrint("nostr", "Nostr: skipping descriptor publish, no rendezvous registered yet\n");
856 return; 918 return;
919 }
920 meeting_node = "rendezvous-pending";
857 } 921 }
858 string desc = btf::SignDescriptor(key.ctx, key.seckey, key.EncPubHex(), 922 string desc = btf::SignDescriptor(key.ctx, key.seckey, key.EncPubHex(),
859 meeting_node, (uint64_t)GetTime()); 923 meeting_node, (uint64_t)GetTime(),
924 onion);
860 if (desc.empty()) 925 if (desc.empty())
861 return; 926 return;
862 json tags = json::array({ json::array({ "d", BTF_DESC_DTAG }) }); 927 json tags = json::array({ json::array({ "d", BTF_DESC_DTAG }) });
@@ -1062,7 +1127,8 @@ static void HandlePoolAnnouncement(const json& ev)
1062// this socket for reuse, and only a completed exchange leaves it in a state 1127// this socket for reuse, and only a completed exchange leaves it in a state
1063// the next caller can trust. 1128// the next caller can trust.
1064static bool ResolveDescriptor(CWebSocket& ws, void* ctx, const string& btfAddr, 1129static bool ResolveDescriptor(CWebSocket& ws, void* ctx, const string& btfAddr,
1065 btf::Descriptor& out, bool& fExchangeOk) 1130 btf::Descriptor& out, bool& fExchangeOk,
1131 string* rawOut = NULL)
1066{ 1132{
1067 fExchangeOk = false; 1133 fExchangeOk = false;
1068 unsigned char pk[32]; 1134 unsigned char pk[32];
@@ -1071,6 +1137,7 @@ static bool ResolveDescriptor(CWebSocket& ws, void* ctx, const string& btfAddr,
1071 json filter = json::object(); 1137 json filter = json::object();
1072 filter["authors"] = json::array({ HexEncode(pk, 32) }); 1138 filter["authors"] = json::array({ HexEncode(pk, 32) });
1073 filter["kinds"] = json::array({ BTF_DESC_KIND }); 1139 filter["kinds"] = json::array({ BTF_DESC_KIND });
1140 filter["#d"] = json::array({ BTF_DESC_DTAG });
1074 filter["limit"] = 1; 1141 filter["limit"] = 1;
1075 json req = json::array({ "REQ", "btf-resolve", filter }); 1142 json req = json::array({ "REQ", "btf-resolve", filter });
1076 if (!ws.SendText(req.dump())) 1143 if (!ws.SendText(req.dump()))
@@ -1097,7 +1164,11 @@ static bool ResolveDescriptor(CWebSocket& ws, void* ctx, const string& btfAddr,
1097 const json& ev = j[2]; 1164 const json& ev = j[2];
1098 if (!fFound && ev.contains("content") && 1165 if (!fFound && ev.contains("content") &&
1099 btf::VerifyDescriptor(ctx, ev["content"].get<string>(), pk, out)) 1166 btf::VerifyDescriptor(ctx, ev["content"].get<string>(), pk, out))
1167 {
1100 fFound = true; 1168 fFound = true;
1169 if (rawOut)
1170 *rawOut = ev["content"].get<string>();
1171 }
1101 // Deliberately keep reading to EOSE rather than returning here. 1172 // Deliberately keep reading to EOSE rather than returning here.
1102 // Returning early leaves the rest of the relay's answer queued on a 1173 // Returning early leaves the rest of the relay's answer queued on a
1103 // socket that goes straight back into the pool, and the next caller 1174 // socket that goes straight back into the pool, and the next caller
@@ -1117,8 +1188,10 @@ static bool ResolveDescriptor(CWebSocket& ws, void* ctx, const string& btfAddr,
1117// public discovery relays for the owner's self-certified descriptor. Used by 1188// public discovery relays for the owner's self-certified descriptor. Used by
1118// ConnectNodeBtf in net.cpp. 1189// ConnectNodeBtf in net.cpp.
1119bool BtfResolve(const std::string& btfAddr, std::string& meetingHostPort, 1190bool BtfResolve(const std::string& btfAddr, std::string& meetingHostPort,
1120 unsigned char enc_pub[32]) 1191 unsigned char enc_pub[32], std::string* onionHostPort)
1121{ 1192{
1193 if (onionHostPort)
1194 onionHostPort->clear();
1122 if (!EnsureNostrKey()) 1195 if (!EnsureNostrKey())
1123 return false; 1196 return false;
1124 for (int i = 0; i < nNostrRelays && !fShutdown; i++) 1197 for (int i = 0; i < nNostrRelays && !fShutdown; i++)
@@ -1146,10 +1219,15 @@ bool BtfResolve(const std::string& btfAddr, std::string& meetingHostPort,
1146 } 1219 }
1147 lease.fOk = fExchangeOk; 1220 lease.fOk = fExchangeOk;
1148 if (d.meeting_node.empty() || d.meeting_node == "rendezvous-pending") 1221 if (d.meeting_node.empty() || d.meeting_node == "rendezvous-pending")
1149 continue; // descriptor predates the owner's relay config 1222 {
1223 if (!onionHostPort || d.onion.empty())
1224 continue; // caller needs rendezvous, or descriptor has no direct path
1225 }
1150 if (!HexDecode(d.enc, enc_pub, 32)) 1226 if (!HexDecode(d.enc, enc_pub, 32))
1151 continue; 1227 continue;
1152 meetingHostPort = d.meeting_node; 1228 meetingHostPort = d.meeting_node;
1229 if (onionHostPort)
1230 *onionHostPort = d.onion;
1153 return true; 1231 return true;
1154 } 1232 }
1155 CATCH_PRINT_EXCEPTION("BtfResolve") 1233 CATCH_PRINT_EXCEPTION("BtfResolve")
@@ -1207,6 +1285,7 @@ bool BtfResolveMany(const std::vector<std::string>& btfAddrs,
1207 json filter = json::object(); 1285 json filter = json::object();
1208 filter["authors"] = authors; 1286 filter["authors"] = authors;
1209 filter["kinds"] = json::array({ BTF_DESC_KIND }); 1287 filter["kinds"] = json::array({ BTF_DESC_KIND });
1288 filter["#d"] = json::array({ BTF_DESC_DTAG });
1210 filter["limit"] = (int)authors.size(); 1289 filter["limit"] = (int)authors.size();
1211 json req = json::array({ "REQ", "btf-resolve-many", filter }); 1290 json req = json::array({ "REQ", "btf-resolve-many", filter });
1212 if (!ws.SendText(req.dump())) 1291 if (!ws.SendText(req.dump()))
@@ -1237,12 +1316,15 @@ bool BtfResolveMany(const std::vector<std::string>& btfAddrs,
1237 btf::Descriptor d; 1316 btf::Descriptor d;
1238 if (!btf::VerifyDescriptor(g_nostrKey.ctx, ev["content"].get<string>(), pk, d)) 1317 if (!btf::VerifyDescriptor(g_nostrKey.ctx, ev["content"].get<string>(), pk, d))
1239 continue; 1318 continue;
1240 if (d.meeting_node.empty() || d.meeting_node == "rendezvous-pending") 1319 if ((d.meeting_node.empty() || d.meeting_node == "rendezvous-pending") &&
1320 d.onion.empty())
1241 continue; 1321 continue;
1242 BtfResolvedPeer rp; 1322 BtfResolvedPeer rp;
1243 if (!HexDecode(d.enc, rp.enc_pub, 32)) 1323 if (!HexDecode(d.enc, rp.enc_pub, 32))
1244 continue; 1324 continue;
1245 rp.meetingHostPort = d.meeting_node; 1325 rp.meetingHostPort = d.meeting_node;
1326 rp.onionHostPort = d.onion;
1327 rp.descriptor = ev["content"].get<string>();
1246 out[ai->second] = rp; 1328 out[ai->second] = rp;
1247 pending.erase(ai->second); 1329 pending.erase(ai->second);
1248 } 1330 }
@@ -1383,7 +1465,7 @@ static bool SeedFromRelay(CNostrKey& key, const string& relay)
1383 else if (createdAt > it->second) it->second = createdAt; // refresh 1465 else if (createdAt > it->second) it->second = createdAt; // refresh
1384 } 1466 }
1385 if (fNew && fDebug) 1467 if (fNew && fDebug)
1386 LogPrint("nostr", "Nostr: discovered .btf peer %s\n", peerAddr.c_str()); 1468 LogPrint("nostrdiscovery", "Nostr: discovered .btf peer %s\n", peerAddr.c_str());
1387 if (++nPeers > 500) break; 1469 if (++nPeers > 500) break;
1388 } 1470 }
1389 else if (type == "EOSE") 1471 else if (type == "EOSE")
@@ -1617,8 +1699,9 @@ static void ConnectDiscoveredBtfPeers()
1617 // plus a random sample, not one or the other. 1699 // plus a random sample, not one or the other.
1618 int64 now = GetTime(); 1700 int64 now = GetTime();
1619 int nBudget = (int)BTF_DIALS_PER_PASS; 1701 int nBudget = (int)BTF_DIALS_PER_PASS;
1702 int nNodeCount = BtfConnectedNodeCount();
1620 { 1703 {
1621 int nRoom = (int)BTF_TARGET_CONN - (int)vNodes.size(); 1704 int nRoom = (int)BTF_TARGET_CONN - nNodeCount;
1622 if (nRoom < nBudget) nBudget = nRoom; 1705 if (nRoom < nBudget) nBudget = nRoom;
1623 } 1706 }
1624 if (nBudget < 0) nBudget = 0; 1707 if (nBudget < 0) nBudget = 0;
@@ -1632,7 +1715,6 @@ static void ConnectDiscoveredBtfPeers()
1632 // Reliability slice: walk the tiered (good -> untried -> bad) order. 1715 // Reliability slice: walk the tiered (good -> untried -> bad) order.
1633 for (const string& addr : peers) 1716 for (const string& addr : peers)
1634 { 1717 {
1635 if ((int)vNodes.size() >= (int)BTF_TARGET_CONN) break;
1636 if ((int)candidates.size() >= nSortedSlots) break; 1718 if ((int)candidates.size() >= nSortedSlots) break;
1637 map<string, int64>::iterator bi = g_btfPeerBackoff.find(addr); 1719 map<string, int64>::iterator bi = g_btfPeerBackoff.find(addr);
1638 if (bi != g_btfPeerBackoff.end() && now < bi->second) 1720 if (bi != g_btfPeerBackoff.end() && now < bi->second)
@@ -1657,7 +1739,7 @@ static void ConnectDiscoveredBtfPeers()
1657 int nWant = min(nRandomSlots, (int)pool.size()); 1739 int nWant = min(nRandomSlots, (int)pool.size());
1658 for (int i = 0; i < nWant; i++) 1740 for (int i = 0; i < nWant; i++)
1659 { 1741 {
1660 if ((int)vNodes.size() + (int)candidates.size() >= (int)BTF_TARGET_CONN) break; 1742 if (nNodeCount + (int)candidates.size() >= (int)BTF_TARGET_CONN) break;
1661 if ((int)candidates.size() >= nBudget) break; 1743 if ((int)candidates.size() >= nBudget) break;
1662 size_t j = i + (size_t)GetRand((uint64)(pool.size() - i)); 1744 size_t j = i + (size_t)GetRand((uint64)(pool.size() - i));
1663 std::swap(pool[i], pool[j]); 1745 std::swap(pool[i], pool[j]);
@@ -1712,20 +1794,20 @@ static void ConnectDiscoveredBtfPeers()
1712 // not the shared Nostr relays, so there's no thundering-herd effect here. 1794 // not the shared Nostr relays, so there's no thundering-herd effect here.
1713 // Each thread writes its result back via shared state guarded by cs_btfPeerState. 1795 // Each thread writes its result back via shared state guarded by cs_btfPeerState.
1714 struct DialCtx { string addr; BtfResolvedPeer rp; }; 1796 struct DialCtx { string addr; BtfResolvedPeer rp; };
1715 vector<DialCtx*> ctxs;
1716 for (const string& addr : toDial) 1797 for (const string& addr : toDial)
1717 { 1798 {
1718 DialCtx* ctx = new DialCtx{addr, resolved[addr]}; 1799 DialCtx* ctx = new DialCtx{addr, resolved[addr]};
1719 ctxs.push_back(ctx); 1800 uintptr_t tid = _beginthread([](void* arg) {
1720 _beginthread([](void* arg) {
1721 DialCtx* ctx = (DialCtx*)arg; 1801 DialCtx* ctx = (DialCtx*)arg;
1722 string addr = ctx->addr; 1802 string addr = ctx->addr;
1723 string meeting = ctx->rp.meetingHostPort; 1803 string meeting = ctx->rp.meetingHostPort;
1804 string onion = ctx->rp.onionHostPort;
1805 string desc = ctx->rp.descriptor;
1724 unsigned char enc_pub[32]; 1806 unsigned char enc_pub[32];
1725 memcpy(enc_pub, ctx->rp.enc_pub, 32); 1807 memcpy(enc_pub, ctx->rp.enc_pub, 32);
1726 delete ctx; 1808 delete ctx;
1727 1809
1728 CNode* pnode = ConnectNodeBtfResolved(addr, meeting, enc_pub); 1810 CNode* pnode = ConnectNodeBtfResolved(addr, meeting, onion, enc_pub, desc);
1729 CRITICAL_BLOCK(cs_btfPeerState) 1811 CRITICAL_BLOCK(cs_btfPeerState)
1730 { 1812 {
1731 if (pnode) 1813 if (pnode)
@@ -1750,6 +1832,21 @@ static void ConnectDiscoveredBtfPeers()
1750 } 1832 }
1751 } 1833 }
1752 }, 0, ctx); 1834 }, 0, ctx);
1835 if (tid == (uintptr_t)-1)
1836 {
1837 LogPrint("nostr", "Nostr: failed to create .btf dial thread for %s\n", addr.c_str());
1838 CRITICAL_BLOCK(cs_btfPeerState)
1839 {
1840 int n = ++g_btfPeerFails[addr];
1841 int64 cap = (n >= BTF_CHRONIC_FAIL_THRESHOLD) ? BTF_BACKOFF_CAP_SECS_DEAD
1842 : BTF_BACKOFF_CAP_SECS;
1843 int64 secs = 30;
1844 for (int k = 1; k < n && secs < cap; k++) secs *= 2;
1845 if (secs > cap) secs = cap;
1846 g_btfPeerBackoff[addr] = GetTime() + secs;
1847 }
1848 delete ctx;
1849 }
1753 } 1850 }
1754 1851
1755 // Wait long enough for all parallel dials to finish (each has a 10s socket 1852 // Wait long enough for all parallel dials to finish (each has a 10s socket
@@ -1772,11 +1869,12 @@ void ThreadNostrSeed(void* parg)
1772 LogPrint("nostr", "Nostr: node pubkey = %s\n", key.PubKeyHex().c_str()); 1869 LogPrint("nostr", "Nostr: node pubkey = %s\n", key.PubKeyHex().c_str());
1773 LogPrint("nostr", "Nostr: node .btf address = %s\n", key.BtfAddress().c_str()); 1870 LogPrint("nostr", "Nostr: node .btf address = %s\n", key.BtfAddress().c_str());
1774 1871
1775 // The self-test below publishes a real descriptor, and PublishDescriptor now 1872 // The self-test below publishes a real descriptor. Rendezvous-only nodes
1776 // refuses to publish before ThreadBtfAccept has registered a rendezvous. Give 1873 // need ThreadBtfAccept to register first; Tor nodes can publish as soon as
1777 // registration a bounded head start so the test measures the publish/resolve 1874 // their signed onion endpoint exists, with rendezvous marked pending.
1778 // chain instead of losing the race against it. 1875 for (int i = 0; i < 60 && !fShutdown &&
1779 for (int i = 0; i < 60 && !fShutdown && BtfActiveRelay().empty(); i++) 1876 BtfActiveRelay().empty() &&
1877 BtfLocalOnionEndpoint().empty(); i++)
1780 Sleep(1000); 1878 Sleep(1000);
1781 1879
1782 // One-time self-test of the rendezvous: publish our descriptor to a relay, 1880 // One-time self-test of the rendezvous: publish our descriptor to a relay,
@@ -1852,7 +1950,7 @@ void ThreadNostrSeed(void* parg)
1852 1950
1853 // Re-discover periodically. Sleep less aggressively when 1951 // Re-discover periodically. Sleep less aggressively when
1854 // we have no connections yet so we find peers faster on startup. 1952 // we have no connections yet so we find peers faster on startup.
1855 int nSleepSecs = vNodes.empty() ? 20 : 60; 1953 int nSleepSecs = (BtfConnectedNodeCount() == 0) ? 20 : 60;
1856 for (int i = 0; i < nSleepSecs && !fShutdown; i++) 1954 for (int i = 0; i < nSleepSecs && !fShutdown; i++)
1857 Sleep(1000); 1955 Sleep(1000);
1858 } 1956 }