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-06 01:53:03 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-09-06 01:53:03 +0000
commitef9f06d43fc6577c15df2256abbb25b20d4227b8 (patch)
tree7e9132aa871fdc01cb95268d2f5ea3bb11853ddf /src/nostr.cpp
parentbe63e83d79e7e00e6b8ca889a70e6a19886aa1d8 (diff)
parentceae3d076b5ad11ee84729ae43f14e9312896646 (diff)
Merge branch 'chore/main-catchup-2026-08-26' into 'main'HEADmain
Bring main onto the line this project actually ships (incl. the 1.2.23 compactblock port) Closes #6, #7, #8, #11, #35, #34, #29, #28, #27, and #16 See merge request !16
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 }