diff options
Diffstat (limited to 'src/nostr.cpp')
| -rw-r--r-- | src/nostr.cpp | 150 |
1 files changed, 129 insertions, 21 deletions
diff --git a/src/nostr.cpp b/src/nostr.cpp index 35fde43..f44dc08 100644 --- a/src/nostr.cpp +++ b/src/nostr.cpp | |||
| @@ -6,6 +6,7 @@ | |||
| 6 | #include "headers.h" | 6 | #include "headers.h" |
| 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/x509v3.h> | 10 | #include <openssl/x509v3.h> |
| 10 | #ifdef _WIN32 | 11 | #ifdef _WIN32 |
| 11 | #include <wincrypt.h> | 12 | #include <wincrypt.h> |
| @@ -82,9 +83,20 @@ static CCriticalSection cs_btfPeers; | |||
| 82 | // independent relays. Kept only one Cloudflare-fronted relay (damus.io, the | 83 | // independent relays. Kept only one Cloudflare-fronted relay (damus.io, the |
| 83 | // most established) and filled the rest with relays on different hosts | 84 | // most established) and filled the rest with relays on different hosts |
| 84 | // (Hetzner, various independents) so no single upstream provider is a | 85 | // (Hetzner, various independents) so no single upstream provider is a |
| 85 | // single point of failure for peer discovery. 7 relays lands inside the | 86 | // single point of failure for peer discovery. 8 relays lands inside the |
| 86 | // commonly recommended 5-10 range for redundancy without keeping an | 87 | // commonly recommended 5-10 range for redundancy without keeping an |
| 87 | // excessive number of persistent sockets open per node. | 88 | // excessive number of persistent sockets open per node. |
| 89 | // | ||
| 90 | // Four entries were removed after probing all twelve from two independent | ||
| 91 | // networks (a residential line and the pool's VPS, over both v4 and v6): | ||
| 92 | // relay.current.fyi, relay.nostr.bg -- no A record, and the AAAA for both | ||
| 93 | // lands on the same parked host (2a04:c300:1000::381) serving a | ||
| 94 | // certificate that fails verification. The domains are gone. | ||
| 95 | // relay.nostr.band, nostr.fmt.wiz.biz -- TCP connects, TLS handshake never | ||
| 96 | // completes, from both networks. Whether they are down or refusing | ||
| 97 | // non-browser clients, this node cannot use them. | ||
| 98 | // A dead relay is not free: each one holds a connect/retry slot and delays | ||
| 99 | // discovery, so carrying it costs latency for no redundancy. | ||
| 88 | const char* pszNostrRelays[] = { | 100 | const char* pszNostrRelays[] = { |
| 89 | "wss://relay.damus.io", | 101 | "wss://relay.damus.io", |
| 90 | "wss://nos.lol", | 102 | "wss://nos.lol", |
| @@ -97,6 +109,8 @@ const char* pszNostrRelays[] = { | |||
| 97 | "wss://offchain.pub", | 109 | "wss://offchain.pub", |
| 98 | "wss://nostr.mom", | 110 | "wss://nostr.mom", |
| 99 | "wss://relay.primal.net", | 111 | "wss://relay.primal.net", |
| 112 | "wss://nostr.oxtr.dev", | ||
| 113 | "wss://relay.nos.social", | ||
| 100 | }; | 114 | }; |
| 101 | const int nNostrRelays = ARRAYLEN(pszNostrRelays); | 115 | const int nNostrRelays = ARRAYLEN(pszNostrRelays); |
| 102 | 116 | ||
| @@ -110,6 +124,12 @@ vector<string> vBtfMeetingRelays = { | |||
| 110 | "31.44.4.249:8434", // New Jersey, US | 124 | "31.44.4.249:8434", // New Jersey, US |
| 111 | "90.156.222.107:8434", // Almaty, KZ | 125 | "90.156.222.107:8434", // Almaty, KZ |
| 112 | }; | 126 | }; |
| 127 | // True when the operator pinned the relay list with /rvrelay: discovered | ||
| 128 | // relays must NOT be merged in (BtfAllRelays), otherwise one accept thread | ||
| 129 | // per discovered relay is spawned and each one optimistically overwrites the | ||
| 130 | // advertised meeting node (BtfSetActiveRelay) before registering -- a | ||
| 131 | // discovered but unreachable relay then flaps the published .btf descriptor. | ||
| 132 | bool fBtfRelayPinned = false; | ||
| 113 | string strBtfActiveRelay; | 133 | string strBtfActiveRelay; |
| 114 | static CCriticalSection cs_activeRelay; | 134 | static CCriticalSection cs_activeRelay; |
| 115 | static string strBtfOnionEndpoint; | 135 | static string strBtfOnionEndpoint; |
| @@ -131,6 +151,19 @@ string BtfActiveRelay() | |||
| 131 | 151 | ||
| 132 | void BtfSetActiveRelay(const string& relay) | 152 | void BtfSetActiveRelay(const string& relay) |
| 133 | { | 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 | } | ||
| 134 | CRITICAL_BLOCK(cs_activeRelay) | 167 | CRITICAL_BLOCK(cs_activeRelay) |
| 135 | strBtfActiveRelay = relay; | 168 | strBtfActiveRelay = relay; |
| 136 | } | 169 | } |
| @@ -166,7 +199,7 @@ static map<string, BtfPoolAnnouncement> g_discoveredPools; | |||
| 166 | static CCriticalSection cs_discoveredPools; | 199 | static CCriticalSection cs_discoveredPools; |
| 167 | 200 | ||
| 168 | // Basic sanity: "host:port" with a numeric 1..65535 port and a plausible host. | 201 | // Basic sanity: "host:port" with a numeric 1..65535 port and a plausible host. |
| 169 | static bool LooksLikeRelay(const string& r) | 202 | bool LooksLikeRelay(const string& r) |
| 170 | { | 203 | { |
| 171 | size_t colon = r.rfind(':'); | 204 | size_t colon = r.rfind(':'); |
| 172 | if (colon == string::npos || colon == 0 || colon + 1 >= r.size()) | 205 | if (colon == string::npos || colon == 0 || colon + 1 >= r.size()) |
| @@ -194,6 +227,8 @@ static bool LooksLikeRelay(const string& r) | |||
| 194 | vector<string> BtfAllRelays() | 227 | vector<string> BtfAllRelays() |
| 195 | { | 228 | { |
| 196 | vector<string> all = vBtfMeetingRelays; | 229 | vector<string> all = vBtfMeetingRelays; |
| 230 | if (fBtfRelayPinned) | ||
| 231 | return all; | ||
| 197 | CRITICAL_BLOCK(cs_discoveredRelays) | 232 | CRITICAL_BLOCK(cs_discoveredRelays) |
| 198 | foreach(const string& r, g_discoveredRelays) | 233 | foreach(const string& r, g_discoveredRelays) |
| 199 | if (find(all.begin(), all.end(), r) == all.end()) | 234 | if (find(all.begin(), all.end(), r) == all.end()) |
| @@ -201,6 +236,18 @@ vector<string> BtfAllRelays() | |||
| 201 | return all; | 236 | return all; |
| 202 | } | 237 | } |
| 203 | 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(). | ||
| 242 | vector<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 | |||
| 204 | 251 | ||
| 205 | // | 252 | // |
| 206 | // Utilities | 253 | // Utilities |
| @@ -578,26 +625,52 @@ public: | |||
| 578 | { | 625 | { |
| 579 | sslctx = SSL_CTX_new(TLS_client_method()); | 626 | sslctx = SSL_CTX_new(TLS_client_method()); |
| 580 | if (!sslctx) return false; | 627 | if (!sslctx) return false; |
| 628 | |||
| 629 | // SSL_CTX_new defaults to SSL_VERIFY_NONE with no trust store | ||
| 630 | // loaded, so until now every wss:// relay connection was accepted | ||
| 631 | // without looking at the certificate at all -- any on-path | ||
| 632 | // attacker could impersonate a relay. Our own events are signed, | ||
| 633 | // so a MITM cannot forge them, but it could drop the rendezvous | ||
| 634 | // registration (leaving the pool unreachable to miners) or answer | ||
| 635 | // subscriptions with whatever it liked. | ||
| 581 | 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. | ||
| 582 | bool fTrustLoaded = (SSL_CTX_set_default_verify_paths(sslctx) == 1); | 641 | bool fTrustLoaded = (SSL_CTX_set_default_verify_paths(sslctx) == 1); |
| 583 | #ifdef _WIN32 | 642 | #ifdef _WIN32 |
| 584 | fTrustLoaded = BtfLoadWindowsRootCertificates(sslctx) || fTrustLoaded; | 643 | fTrustLoaded = BtfLoadWindowsRootCertificates(sslctx) || fTrustLoaded; |
| 585 | #endif | 644 | #endif |
| 586 | if (!fTrustLoaded) | 645 | if (!fTrustLoaded) |
| 587 | return error("Nostr: failed to load TLS trust store"); | 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. | ||
| 651 | SSL_CTX_set_min_proto_version(sslctx, TLS1_2_VERSION); | ||
| 652 | |||
| 588 | ssl = SSL_new(sslctx); | 653 | ssl = SSL_new(sslctx); |
| 589 | if (!ssl) return false; | 654 | if (!ssl) return false; |
| 590 | SSL_set_fd(ssl, (int)hSocket); | 655 | SSL_set_fd(ssl, (int)hSocket); |
| 591 | if (SSL_set_tlsext_host_name(ssl, host.c_str()) != 1) | 656 | if (SSL_set_tlsext_host_name(ssl, host.c_str()) != 1) // SNI |
| 592 | return error("Nostr: failed to set TLS SNI for %s", host.c_str()); | 657 | return error("Nostr: failed to set TLS SNI for %s", host.c_str()); |
| 658 | |||
| 659 | // Verifying the chain is not enough on its own: without this the | ||
| 660 | // relay could present any certificate a trusted CA ever issued, | ||
| 661 | // for any name. SSL_set1_host binds the check to the host we asked | ||
| 662 | // for. | ||
| 593 | if (SSL_set1_host(ssl, host.c_str()) != 1) | 663 | if (SSL_set1_host(ssl, host.c_str()) != 1) |
| 594 | return error("Nostr: failed to set TLS hostname for %s", host.c_str()); | 664 | return error("Nostr: failed to set TLS hostname for %s", host.c_str()); |
| 665 | |||
| 595 | if (SSL_connect(ssl) != 1) | 666 | if (SSL_connect(ssl) != 1) |
| 667 | { | ||
| 668 | long nVerify = SSL_get_verify_result(ssl); | ||
| 669 | if (nVerify != X509_V_OK) | ||
| 670 | return error("Nostr: certificate for %s rejected: %s", | ||
| 671 | host.c_str(), X509_verify_cert_error_string(nVerify)); | ||
| 596 | return error("Nostr: TLS handshake with %s failed", host.c_str()); | 672 | return error("Nostr: TLS handshake with %s failed", host.c_str()); |
| 597 | long nVerify = SSL_get_verify_result(ssl); | 673 | } |
| 598 | if (nVerify != X509_V_OK) | ||
| 599 | return error("Nostr: TLS certificate verification failed for %s: %s", | ||
| 600 | host.c_str(), X509_verify_cert_error_string(nVerify)); | ||
| 601 | } | 674 | } |
| 602 | 675 | ||
| 603 | // WebSocket handshake (HTTP Upgrade) | 676 | // WebSocket handshake (HTTP Upgrade) |
| @@ -1049,9 +1122,15 @@ static void HandlePoolAnnouncement(const json& ev) | |||
| 1049 | 1122 | ||
| 1050 | // Resolve a `.btf` address via a relay: fetch the descriptor published by that | 1123 | // Resolve a `.btf` address via a relay: fetch the descriptor published by that |
| 1051 | // address's key and verify it self-certifies under the decoded pubkey. | 1124 | // address's key and verify it self-certifies under the decoded pubkey. |
| 1125 | // fExchangeOk reports whether the relay actually finished the conversation | ||
| 1126 | // (an EOSE arrived) as opposed to the socket dying mid-read. The caller pools | ||
| 1127 | // this socket for reuse, and only a completed exchange leaves it in a state | ||
| 1128 | // the next caller can trust. | ||
| 1052 | static bool ResolveDescriptor(CWebSocket& ws, void* ctx, const string& btfAddr, | 1129 | static bool ResolveDescriptor(CWebSocket& ws, void* ctx, const string& btfAddr, |
| 1053 | btf::Descriptor& out, string* rawOut = NULL) | 1130 | btf::Descriptor& out, bool& fExchangeOk, |
| 1131 | string* rawOut = NULL) | ||
| 1054 | { | 1132 | { |
| 1133 | fExchangeOk = false; | ||
| 1055 | unsigned char pk[32]; | 1134 | unsigned char pk[32]; |
| 1056 | if (!btf::ParseAddress(btfAddr, pk)) | 1135 | if (!btf::ParseAddress(btfAddr, pk)) |
| 1057 | return false; | 1136 | return false; |
| @@ -1063,30 +1142,46 @@ static bool ResolveDescriptor(CWebSocket& ws, void* ctx, const string& btfAddr, | |||
| 1063 | json req = json::array({ "REQ", "btf-resolve", filter }); | 1142 | json req = json::array({ "REQ", "btf-resolve", filter }); |
| 1064 | if (!ws.SendText(req.dump())) | 1143 | if (!ws.SendText(req.dump())) |
| 1065 | return false; | 1144 | return false; |
| 1145 | bool fFound = false; | ||
| 1066 | for (;;) | 1146 | for (;;) |
| 1067 | { | 1147 | { |
| 1068 | string msg; | 1148 | string msg; |
| 1069 | if (!ws.RecvText(msg)) | 1149 | if (!ws.RecvText(msg)) |
| 1070 | break; | 1150 | return fFound; // socket died mid-exchange; fExchangeOk stays false |
| 1071 | json j; | 1151 | json j; |
| 1072 | try { j = json::parse(msg); } catch (...) { continue; } | 1152 | try { j = json::parse(msg); } catch (...) { continue; } |
| 1073 | if (!j.is_array() || j.empty() || !j[0].is_string()) continue; | 1153 | if (!j.is_array() || j.empty() || !j[0].is_string()) continue; |
| 1074 | string t = j[0].get<string>(); | 1154 | string t = j[0].get<string>(); |
| 1155 | // Every relay message names the subscription it belongs to. This socket | ||
| 1156 | // is pooled and reused, so a message left over from an earlier exchange | ||
| 1157 | // is the ordinary case here, not an exotic one -- and an unmatched EOSE | ||
| 1158 | // ends this query before it has begun, which is indistinguishable from | ||
| 1159 | // "that descriptor does not exist". Check the id. | ||
| 1160 | if (j.size() >= 2 && j[1].is_string() && j[1].get<string>() != "btf-resolve") | ||
| 1161 | continue; | ||
| 1075 | if (t == "EVENT" && j.size() >= 3) | 1162 | if (t == "EVENT" && j.size() >= 3) |
| 1076 | { | 1163 | { |
| 1077 | const json& ev = j[2]; | 1164 | const json& ev = j[2]; |
| 1078 | if (ev.contains("content") && | 1165 | if (!fFound && ev.contains("content") && |
| 1079 | btf::VerifyDescriptor(ctx, ev["content"].get<string>(), pk, out)) | 1166 | btf::VerifyDescriptor(ctx, ev["content"].get<string>(), pk, out)) |
| 1080 | { | 1167 | { |
| 1168 | fFound = true; | ||
| 1081 | if (rawOut) | 1169 | if (rawOut) |
| 1082 | *rawOut = ev["content"].get<string>(); | 1170 | *rawOut = ev["content"].get<string>(); |
| 1083 | return true; | ||
| 1084 | } | 1171 | } |
| 1172 | // Deliberately keep reading to EOSE rather than returning here. | ||
| 1173 | // Returning early leaves the rest of the relay's answer queued on a | ||
| 1174 | // socket that goes straight back into the pool, and the next caller | ||
| 1175 | // reads our leftovers as its own -- one message out of step, for | ||
| 1176 | // the life of the process. | ||
| 1085 | } | 1177 | } |
| 1086 | else if (t == "EOSE") | 1178 | else if (t == "EOSE") |
| 1179 | { | ||
| 1180 | fExchangeOk = true; // relay said "that is all": socket is clean | ||
| 1087 | break; | 1181 | break; |
| 1182 | } | ||
| 1088 | } | 1183 | } |
| 1089 | return false; | 1184 | return fFound; |
| 1090 | } | 1185 | } |
| 1091 | 1186 | ||
| 1092 | // Resolve a `.btf` address into its rendezvous coordinates by querying the | 1187 | // Resolve a `.btf` address into its rendezvous coordinates by querying the |
| @@ -1107,12 +1202,22 @@ bool BtfResolve(const std::string& btfAddr, std::string& meetingHostPort, | |||
| 1107 | if (!lease.Connected()) | 1202 | if (!lease.Connected()) |
| 1108 | continue; | 1203 | continue; |
| 1109 | btf::Descriptor d; | 1204 | btf::Descriptor d; |
| 1110 | if (!ResolveDescriptor(lease.Ws(), g_nostrKey.ctx, btfAddr, d)) | 1205 | bool fExchangeOk = false; |
| 1206 | if (!ResolveDescriptor(lease.Ws(), g_nostrKey.ctx, btfAddr, d, fExchangeOk)) | ||
| 1111 | { | 1207 | { |
| 1112 | lease.fOk = true; // exchange completed, just no match | 1208 | // Only keep the pooled socket if the relay actually finished |
| 1209 | // the conversation. This used to say "exchange completed, just | ||
| 1210 | // no match" on every failure -- including a dead socket -- so | ||
| 1211 | // ~CRelayLease never closed it and fConnected was never | ||
| 1212 | // cleared. A relay connection that died was then pinned in the | ||
| 1213 | // pool, reported healthy, and reused for every later lookup, | ||
| 1214 | // which is why "no Nostr descriptor found" persisted for a | ||
| 1215 | // descriptor that was demonstrably published, and why only a | ||
| 1216 | // restart cleared it. | ||
| 1217 | lease.fOk = fExchangeOk; | ||
| 1113 | continue; | 1218 | continue; |
| 1114 | } | 1219 | } |
| 1115 | lease.fOk = true; | 1220 | lease.fOk = fExchangeOk; |
| 1116 | if (d.meeting_node.empty() || d.meeting_node == "rendezvous-pending") | 1221 | if (d.meeting_node.empty() || d.meeting_node == "rendezvous-pending") |
| 1117 | { | 1222 | { |
| 1118 | if (!onionHostPort || d.onion.empty()) | 1223 | if (!onionHostPort || d.onion.empty()) |
| @@ -1360,7 +1465,7 @@ static bool SeedFromRelay(CNostrKey& key, const string& relay) | |||
| 1360 | else if (createdAt > it->second) it->second = createdAt; // refresh | 1465 | else if (createdAt > it->second) it->second = createdAt; // refresh |
| 1361 | } | 1466 | } |
| 1362 | if (fNew && fDebug) | 1467 | if (fNew && fDebug) |
| 1363 | LogPrint("nostr", "Nostr: discovered .btf peer %s\n", peerAddr.c_str()); | 1468 | LogPrint("nostrdiscovery", "Nostr: discovered .btf peer %s\n", peerAddr.c_str()); |
| 1364 | if (++nPeers > 500) break; | 1469 | if (++nPeers > 500) break; |
| 1365 | } | 1470 | } |
| 1366 | else if (type == "EOSE") | 1471 | else if (type == "EOSE") |
| @@ -1450,7 +1555,7 @@ void ThreadBtfPoolAnnouncer(void*) | |||
| 1450 | LogPrint("pool", "ThreadBtfPoolAnnouncer started\n"); | 1555 | LogPrint("pool", "ThreadBtfPoolAnnouncer started\n"); |
| 1451 | while (!fShutdown) | 1556 | while (!fShutdown) |
| 1452 | { | 1557 | { |
| 1453 | if (nMineMode == MINE_OPERATOR && gPoolRunning) | 1558 | if (nMineMode == MINE_OPERATOR && gPoolRunning && !fPoolNoAnnounce) |
| 1454 | { | 1559 | { |
| 1455 | BtfPoolAnnouncement ann; | 1560 | BtfPoolAnnouncement ann; |
| 1456 | ann.btfAddress = BtfLocalAddress(); | 1561 | ann.btfAddress = BtfLocalAddress(); |
| @@ -1785,14 +1890,17 @@ void ThreadNostrSeed(void* parg) | |||
| 1785 | PublishDescriptor(ws, key); | 1890 | PublishDescriptor(ws, key); |
| 1786 | Sleep(700); // let the relay store the replaceable event | 1891 | Sleep(700); // let the relay store the replaceable event |
| 1787 | btf::Descriptor d; | 1892 | btf::Descriptor d; |
| 1788 | if (ResolveDescriptor(ws, key.ctx, key.BtfAddress(), d)) | 1893 | bool fExchangeOk = false; |
| 1894 | if (ResolveDescriptor(ws, key.ctx, key.BtfAddress(), d, fExchangeOk)) | ||
| 1789 | { | 1895 | { |
| 1790 | LogPrint("nostr", "Nostr: .btf self-resolve OK via %s (meeting_node=%s, enc=%s...)\n", | 1896 | LogPrint("nostr", "Nostr: .btf self-resolve OK via %s (meeting_node=%s, enc=%s...)\n", |
| 1791 | pszNostrRelays[i], d.meeting_node.c_str(), d.enc.substr(0, 16).c_str()); | 1897 | pszNostrRelays[i], d.meeting_node.c_str(), d.enc.substr(0, 16).c_str()); |
| 1792 | lease.fOk = true; | 1898 | lease.fOk = fExchangeOk; |
| 1793 | break; | 1899 | break; |
| 1794 | } | 1900 | } |
| 1795 | lease.fOk = true; | 1901 | // Same rule as BtfResolve: a socket only goes back in the pool if |
| 1902 | // the relay finished the conversation on it. | ||
| 1903 | lease.fOk = fExchangeOk; | ||
| 1796 | } | 1904 | } |
| 1797 | CATCH_PRINT_EXCEPTION("btf self-test") | 1905 | CATCH_PRINT_EXCEPTION("btf self-test") |
| 1798 | } | 1906 | } |
| @@ -1824,7 +1932,7 @@ void ThreadNostrSeed(void* parg) | |||
| 1824 | } | 1932 | } |
| 1825 | CATCH_PRINT_EXCEPTION("ConnectDiscoveredBtfPeers") | 1933 | CATCH_PRINT_EXCEPTION("ConnectDiscoveredBtfPeers") |
| 1826 | 1934 | ||
| 1827 | if (nMineMode == MINE_OPERATOR && gPoolRunning) | 1935 | if (nMineMode == MINE_OPERATOR && gPoolRunning && !fPoolNoAnnounce) |
| 1828 | { | 1936 | { |
| 1829 | BtfPoolAnnouncement ann; | 1937 | BtfPoolAnnouncement ann; |
| 1830 | ann.btfAddress = BtfLocalAddress(); | 1938 | ann.btfAddress = BtfLocalAddress(); |