upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBitflash <btf01@proton.me>2026-08-02 11:59:12 -0300
committerGitHub <noreply@github.com>2026-08-02 11:59:12 -0300
commit758ff4bc49af78b06e37fc1e99741cb5fa8e43d6 (patch)
tree4e9d4ed63430d7a442260d1be78d63878a2fc455 /src
parentfe04a2045c3080797c6f93bf7cd2e12192b8f68f (diff)
rendezvous: let a node park more than one registration (#113)
A registration was consumed by the first caller and registering again replaced the previous one, so a node was reachable to one peer at a time and unreachable for the gap between being paired and registering again. Measured on a live node: descriptors resolved 53/54, our own registrations paired 11/12, nothing paired and went silent -- and half of every outbound dial failed, 15 of 30 in eleven minutes. The relay keeps a queue per pubkey capped at four, pairs with the oldest still-alive registration and discards the dead; the node runs three accept threads, each holding its own registration. The wire protocol does not change, so either side can be deployed first. Bench with both relays built from source and waves of concurrent dialers: old+1 65%, new+1 58%, old+3 68%, new+3 98%. Each half alone lands within noise of doing nothing.
Diffstat (limited to 'src')
-rw-r--r--src/net.cpp194
-rw-r--r--src/net.h22
-rw-r--r--src/nostr.cpp4
3 files changed, 214 insertions, 6 deletions
diff --git a/src/net.cpp b/src/net.cpp
index 7091981..449d91c 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -51,6 +51,103 @@ int64 nBlocksReceived = 0;
51int64 nBlocksWithoutParent = 0; 51int64 nBlocksWithoutParent = 0;
52static int64 nNodeStartTime = 0; 52static int64 nNodeStartTime = 0;
53 53
54static CCriticalSection cs_btfChurn;
55static int64 nBtfResolveAttempts = 0;
56static int64 nBtfResolveOk = 0;
57static int64 nBtfResolveMiss = 0;
58static int64 nBtfDialAttempts = 0;
59static int64 nBtfDialOk = 0;
60static int64 nBtfDialFailed = 0;
61static int64 nBtfRegisterOk = 0;
62static int64 nBtfRegisterFailed = 0;
63static int64 nBtfPairOk = 0;
64static int64 nBtfPairFailed = 0;
65static int64 nBtfHandshakeNoRecv = 0;
66static int64 nBtfHandshakeNoSend = 0;
67static int64 nBtfHandshakeSilent = 0;
68static string strBtfLastDialFail;
69static string strBtfLastHandshakeTimeout;
70
71void BtfChurnNoteResolveAttempt()
72{
73 CRITICAL_BLOCK(cs_btfChurn)
74 nBtfResolveAttempts++;
75}
76
77void BtfChurnNoteResolveResult(bool fOk)
78{
79 CRITICAL_BLOCK(cs_btfChurn)
80 {
81 if (fOk) nBtfResolveOk++;
82 else nBtfResolveMiss++;
83 }
84}
85
86void BtfChurnNoteDialAttempt(const string& strBtfAddr, const string& strMeeting)
87{
88 CRITICAL_BLOCK(cs_btfChurn)
89 nBtfDialAttempts++;
90 LogPrint("net", "btfchurn: dial attempt addr=%s meeting=%s\n",
91 strBtfAddr.c_str(), strMeeting.c_str());
92}
93
94void BtfChurnNoteDialResult(const string& strBtfAddr, const string& strMeeting, bool fOk)
95{
96 CRITICAL_BLOCK(cs_btfChurn)
97 {
98 if (fOk)
99 nBtfDialOk++;
100 else
101 {
102 nBtfDialFailed++;
103 strBtfLastDialFail = strBtfAddr + " via " + strMeeting;
104 }
105 }
106 LogPrint("net", "btfchurn: dial %s addr=%s meeting=%s\n",
107 fOk ? "ok" : "failed", strBtfAddr.c_str(), strMeeting.c_str());
108}
109
110void BtfChurnNoteRegisterResult(const string& strMeeting, bool fOk)
111{
112 CRITICAL_BLOCK(cs_btfChurn)
113 {
114 if (fOk) nBtfRegisterOk++;
115 else nBtfRegisterFailed++;
116 }
117 LogPrint("net", "btfchurn: register %s meeting=%s\n",
118 fOk ? "ok" : "failed", strMeeting.c_str());
119}
120
121void BtfChurnNotePairResult(const string& strMeeting, bool fOk)
122{
123 CRITICAL_BLOCK(cs_btfChurn)
124 {
125 if (fOk) nBtfPairOk++;
126 else nBtfPairFailed++;
127 }
128 LogPrint("net", "btfchurn: pair %s meeting=%s\n",
129 fOk ? "ok" : "timeout-or-drop", strMeeting.c_str());
130}
131
132void BtfChurnNoteHandshakeTimeout(const string& strBtfAddr, const string& strMeeting,
133 bool fRecv, bool fSend)
134{
135 CRITICAL_BLOCK(cs_btfChurn)
136 {
137 if (!fRecv && !fSend)
138 nBtfHandshakeSilent++;
139 else
140 {
141 if (!fRecv) nBtfHandshakeNoRecv++;
142 if (!fSend) nBtfHandshakeNoSend++;
143 }
144 strBtfLastHandshakeTimeout = strprintf("%s via %s recv=%d send=%d",
145 strBtfAddr.empty() ? "unknown" : strBtfAddr.c_str(),
146 strMeeting.empty() ? "unknown" : strMeeting.c_str(),
147 fRecv, fSend);
148 }
149}
150
54// --- Socket accounting (implementation lives in sockcount.h) -------------- 151// --- Socket accounting (implementation lives in sockcount.h) --------------
55static const char* pszSockSite[SOCK_SITES] = { 152static const char* pszSockSite[SOCK_SITES] = {
56 "external-ip probe", "listen socket", "inbound accept", 153 "external-ip probe", "listen socket", "inbound accept",
@@ -199,19 +296,64 @@ string GetDiagnosticsText()
199 296
200 str += SockAccountingText(); 297 str += SockAccountingText();
201 298
202 str += "\n peer dir height last recv last send unsent\n"; 299 int64 nResolveAttempts, nResolveOk, nResolveMiss;
300 int64 nDialAttempts, nDialOk, nDialFailed;
301 int64 nRegisterOk, nRegisterFailed, nPairOk, nPairFailed;
302 int64 nHandshakeNoRecv, nHandshakeNoSend, nHandshakeSilent;
303 string strLastDialFail, strLastHandshakeTimeout;
304 CRITICAL_BLOCK(cs_btfChurn)
305 {
306 nResolveAttempts = nBtfResolveAttempts;
307 nResolveOk = nBtfResolveOk;
308 nResolveMiss = nBtfResolveMiss;
309 nDialAttempts = nBtfDialAttempts;
310 nDialOk = nBtfDialOk;
311 nDialFailed = nBtfDialFailed;
312 nRegisterOk = nBtfRegisterOk;
313 nRegisterFailed = nBtfRegisterFailed;
314 nPairOk = nBtfPairOk;
315 nPairFailed = nBtfPairFailed;
316 nHandshakeNoRecv = nBtfHandshakeNoRecv;
317 nHandshakeNoSend = nBtfHandshakeNoSend;
318 nHandshakeSilent = nBtfHandshakeSilent;
319 strLastDialFail = strBtfLastDialFail;
320 strLastHandshakeTimeout = strBtfLastHandshakeTimeout;
321 }
322 str += "\n .btf churn\n";
323 str += strprintf(" resolves %lld attempts, %lld ok, %lld no descriptor\n",
324 (long long)nResolveAttempts, (long long)nResolveOk,
325 (long long)nResolveMiss);
326 str += strprintf(" outbound dials %lld attempts, %lld ok, %lld failed\n",
327 (long long)nDialAttempts, (long long)nDialOk,
328 (long long)nDialFailed);
329 str += strprintf(" rendezvous local register ok/fail %lld/%lld, pair ok/drop %lld/%lld\n",
330 (long long)nRegisterOk, (long long)nRegisterFailed,
331 (long long)nPairOk, (long long)nPairFailed);
332 str += strprintf(" handshake grace silent %lld, no recv %lld, no send %lld\n",
333 (long long)nHandshakeSilent, (long long)nHandshakeNoRecv,
334 (long long)nHandshakeNoSend);
335 if (!strLastDialFail.empty())
336 str += strprintf(" last dial failure %s\n", strLastDialFail.c_str());
337 if (!strLastHandshakeTimeout.empty())
338 str += strprintf(" last handshake %s\n", strLastHandshakeTimeout.c_str());
339
340 str += "\n peer dir height last recv last send unsent via\n";
203 foreach(CNode* pnode, vCopy) 341 foreach(CNode* pnode, vCopy)
204 { 342 {
205 int nSendSize = 0; 343 int nSendSize = 0;
206 TRY_CRITICAL_BLOCK(pnode->cs_vSend) 344 TRY_CRITICAL_BLOCK(pnode->cs_vSend)
207 nSendSize = (int)pnode->vSend.size(); 345 nSendSize = (int)pnode->vSend.size();
208 str += strprintf(" %-28s %-4s %6d %10s %10s %7d\n", 346 string strVia;
347 if (!pnode->strBtfMeeting.empty())
348 strVia = pnode->strBtfMeeting;
349 str += strprintf(" %-28s %-4s %6d %10s %10s %7d %s\n",
209 pnode->addr.ToString().substr(0, 28).c_str(), 350 pnode->addr.ToString().substr(0, 28).c_str(),
210 pnode->fInbound ? "in" : "out", 351 pnode->fInbound ? "in" : "out",
211 pnode->nStartingHeight, 352 pnode->nStartingHeight,
212 FormatAge(pnode->nLastRecv ? nNow - pnode->nLastRecv : -1).c_str(), 353 FormatAge(pnode->nLastRecv ? nNow - pnode->nLastRecv : -1).c_str(),
213 FormatAge(pnode->nLastSend ? nNow - pnode->nLastSend : -1).c_str(), 354 FormatAge(pnode->nLastSend ? nNow - pnode->nLastSend : -1).c_str(),
214 nSendSize); 355 nSendSize,
356 strVia.substr(0, 28).c_str());
215 } 357 }
216 return str; 358 return str;
217} 359}
@@ -814,30 +956,41 @@ static CNode* ConnectNodeBtfTail(const string& strBtfAddr, const unsigned char p
814 return pnode; 956 return pnode;
815 } 957 }
816 958
959 BtfChurnNoteDialAttempt(strBtfAddr, strMeeting);
817 size_t colon = strMeeting.rfind(':'); 960 size_t colon = strMeeting.rfind(':');
818 if (colon == string::npos) 961 if (colon == string::npos)
962 {
963 BtfChurnNoteDialResult(strBtfAddr, strMeeting, false);
819 return NULL; 964 return NULL;
965 }
820 string strHost = strMeeting.substr(0, colon); 966 string strHost = strMeeting.substr(0, colon);
821 int nPort = atoi(strMeeting.substr(colon + 1).c_str()); 967 int nPort = atoi(strMeeting.substr(colon + 1).c_str());
822 if (nPort <= 0 || nPort > 65535) 968 if (nPort <= 0 || nPort > 65535)
969 {
970 BtfChurnNoteDialResult(strBtfAddr, strMeeting, false);
823 return NULL; 971 return NULL;
972 }
824 973
825 btf_socket_t hSocket = btf::BtfClientTunnel(strHost.c_str(), (unsigned short)nPort, pk, enc_pub); 974 btf_socket_t hSocket = btf::BtfClientTunnel(strHost.c_str(), (unsigned short)nPort, pk, enc_pub);
826 if (hSocket == INVALID_SOCKET) 975 if (hSocket == INVALID_SOCKET)
827 { 976 {
828 if (fDebug) 977 if (fDebug)
829 LogPrint("net", "ConnectNodeBtf: tunnel to %s via %s failed\n", strBtfAddr.c_str(), strMeeting.c_str()); 978 LogPrint("net", "ConnectNodeBtf: tunnel to %s via %s failed\n", strBtfAddr.c_str(), strMeeting.c_str());
979 BtfChurnNoteDialResult(strBtfAddr, strMeeting, false);
830 return NULL; 980 return NULL;
831 } 981 }
832 982
833 if (fDebug) 983 if (fDebug)
834 LogPrint("net", "connected %s via rendezvous %s\n", strBtfAddr.c_str(), strMeeting.c_str()); 984 LogPrint("net", "connected %s via rendezvous %s\n", strBtfAddr.c_str(), strMeeting.c_str());
985 BtfChurnNoteDialResult(strBtfAddr, strMeeting, true);
835 986
836 // This one answered -- worth trying first next time we start. 987 // This one answered -- worth trying first next time we start.
837 RememberBtfPeer(strBtfAddr, strMeeting, enc_pub); 988 RememberBtfPeer(strBtfAddr, strMeeting, enc_pub);
838 989
839 // Add node 990 // Add node
840 pnode = new CNode(hSocket, addr, false); 991 pnode = new CNode(hSocket, addr, false);
992 pnode->strBtfAddr = strBtfAddr;
993 pnode->strBtfMeeting = strMeeting;
841 pnode->AddRef(); 994 pnode->AddRef();
842 CRITICAL_BLOCK(cs_vNodes) 995 CRITICAL_BLOCK(cs_vNodes)
843 vNodes.push_back(pnode); 996 vNodes.push_back(pnode);
@@ -863,12 +1016,15 @@ CNode* ConnectNodeBtf(const string& strBtfAddr)
863 1016
864 string strMeeting; 1017 string strMeeting;
865 unsigned char enc_pub[32]; 1018 unsigned char enc_pub[32];
1019 BtfChurnNoteResolveAttempt();
866 if (!BtfResolve(strBtfAddr, strMeeting, enc_pub)) 1020 if (!BtfResolve(strBtfAddr, strMeeting, enc_pub))
867 { 1021 {
1022 BtfChurnNoteResolveResult(false);
868 if (fDebug) 1023 if (fDebug)
869 LogPrint("net", "ConnectNodeBtf: could not resolve a descriptor for %s\n", strBtfAddr.c_str()); 1024 LogPrint("net", "ConnectNodeBtf: could not resolve a descriptor for %s\n", strBtfAddr.c_str());
870 return NULL; 1025 return NULL;
871 } 1026 }
1027 BtfChurnNoteResolveResult(true);
872 return ConnectNodeBtfTail(strBtfAddr, pk, strMeeting, enc_pub); 1028 return ConnectNodeBtfTail(strBtfAddr, pk, strMeeting, enc_pub);
873} 1029}
874 1030
@@ -951,12 +1107,14 @@ void ThreadBtfAccept(void* parg)
951 } 1107 }
952 if (rv == btf::RV_INVALID) 1108 if (rv == btf::RV_INVALID)
953 { 1109 {
1110 BtfChurnNoteRegisterResult(strMeeting, false);
954 LogPrint("net", "rendezvous: could not register at %s, trying another\n", 1111 LogPrint("net", "rendezvous: could not register at %s, trying another\n",
955 strMeeting.c_str()); 1112 strMeeting.c_str());
956 iRelay++; // this relay is down/attacked -> try the next one 1113 iRelay++; // this relay is down/attacked -> try the next one
957 Sleep(3000); 1114 Sleep(3000);
958 continue; 1115 continue;
959 } 1116 }
1117 BtfChurnNoteRegisterResult(strMeeting, true);
960 1118
961 // Registered. Advertise THIS relay now, while we are listed and before 1119 // Registered. Advertise THIS relay now, while we are listed and before
962 // anybody dials -- a descriptor naming it is what makes a dial possible 1120 // anybody dials -- a descriptor naming it is what makes a dial possible
@@ -971,7 +1129,9 @@ void ThreadBtfAccept(void* parg)
971 // here, the node stopped being reachable, and nothing in the log said 1129 // here, the node stopped being reachable, and nothing in the log said
972 // so. Re-registering every few minutes when nobody has dialled costs 1130 // so. Re-registering every few minutes when nobody has dialled costs
973 // one reconnect and removes the whole failure mode. 1131 // one reconnect and removes the whole failure mode.
974 if (!btf::RvServiceWaitPaired(rv, BTF_RENDEZVOUS_WAIT_SECS)) 1132 bool fPaired = btf::RvServiceWaitPaired(rv, BTF_RENDEZVOUS_WAIT_SECS);
1133 BtfChurnNotePairResult(strMeeting, fPaired);
1134 if (!fPaired)
975 { 1135 {
976 btf::RvClose(rv); 1136 btf::RvClose(rv);
977 LogPrint("net", "rendezvous: no dial at %s within %ds (or it dropped us), " 1137 LogPrint("net", "rendezvous: no dial at %s within %ds (or it dropped us), "
@@ -981,7 +1141,11 @@ void ThreadBtfAccept(void* parg)
981 1141
982 btf_socket_t hSocket = btf::BtfServiceWrap(rv, sk); 1142 btf_socket_t hSocket = btf::BtfServiceWrap(rv, sk);
983 if (hSocket == INVALID_SOCKET) 1143 if (hSocket == INVALID_SOCKET)
1144 {
1145 LogPrint("net", "rendezvous: paired at %s but channel setup failed\n",
1146 strMeeting.c_str());
984 continue; 1147 continue;
1148 }
985 1149
986 // The dialer is anonymous (its pubkey never reaches us), so tag the 1150 // The dialer is anonymous (its pubkey never reaches us), so tag the
987 // connection with a random marker address. 1151 // connection with a random marker address.
@@ -991,6 +1155,8 @@ void ThreadBtfAccept(void* parg)
991 1155
992 LogPrint("net", "accepted .btf connection via rendezvous %s\n", strMeeting.c_str()); 1156 LogPrint("net", "accepted .btf connection via rendezvous %s\n", strMeeting.c_str());
993 CNode* pnode = new CNode(hSocket, addr, true); 1157 CNode* pnode = new CNode(hSocket, addr, true);
1158 pnode->strBtfAddr = "inbound";
1159 pnode->strBtfMeeting = strMeeting;
994 pnode->AddRef(); 1160 pnode->AddRef();
995 CRITICAL_BLOCK(cs_vNodes) 1161 CRITICAL_BLOCK(cs_vNodes)
996 vNodes.push_back(pnode); 1162 vNodes.push_back(pnode);
@@ -1457,6 +1623,9 @@ void ThreadSocketHandler2(void* parg)
1457 { 1623 {
1458 if (pnode->nLastRecv == 0 || pnode->nLastSend == 0) 1624 if (pnode->nLastRecv == 0 || pnode->nLastSend == 0)
1459 { 1625 {
1626 if (!pnode->strBtfMeeting.empty())
1627 BtfChurnNoteHandshakeTimeout(pnode->strBtfAddr, pnode->strBtfMeeting,
1628 pnode->nLastRecv != 0, pnode->nLastSend != 0);
1460 LogPrint("net", "socket no message in first %d seconds, recv=%d send=%d\n", 1629 LogPrint("net", "socket no message in first %d seconds, recv=%d send=%d\n",
1461 BTF_HANDSHAKE_GRACE_SECS, pnode->nLastRecv != 0, pnode->nLastSend != 0); 1630 BTF_HANDSHAKE_GRACE_SECS, pnode->nLastRecv != 0, pnode->nLastSend != 0);
1462 pnode->fDisconnect = true; 1631 pnode->fDisconnect = true;
@@ -1803,9 +1972,22 @@ bool StartNode(string& strError)
1803 1972
1804 // Anonymous inbound: this node's .btf hidden service, reachable through the 1973 // Anonymous inbound: this node's .btf hidden service, reachable through the
1805 // meeting relay without exposing our IP or needing a public port. 1974 // meeting relay without exposing our IP or needing a public port.
1975 //
1976 // Several of these, not one. Each holds its own registration at a
1977 // rendezvous, and a registration is consumed the moment a caller is paired
1978 // with it -- so with a single thread the node is unreachable for the whole
1979 // gap between being paired and registering again. Measured from a node's
1980 // own counters: half of all outbound dials failed for exactly that reason,
1981 // 15 of 30 in eleven minutes, while descriptors resolved 53 times out of
1982 // 54. Spares parked at the relay close that gap.
1983 //
1984 // Needs a relay that keeps more than one registration per node; against an
1985 // older relay the extra threads are harmless, because it drops the previous
1986 // registration on each new one and the node ends up where it started.
1806 if (!vBtfMeetingRelays.empty()) 1987 if (!vBtfMeetingRelays.empty())
1807 if (_beginthread(ThreadBtfAccept, 0, NULL) == -1) 1988 for (int i = 0; i < BTF_ACCEPT_THREADS; i++)
1808 printf("Error: _beginthread(ThreadBtfAccept) failed\n"); 1989 if (_beginthread(ThreadBtfAccept, 0, NULL) == -1)
1990 printf("Error: _beginthread(ThreadBtfAccept) failed\n");
1809 1991
1810 // Anonymous outbound: keep a connection to a specific .btf peer, if asked. 1992 // Anonymous outbound: keep a connection to a specific .btf peer, if asked.
1811 if (!strBtfConnect.empty()) 1993 if (!strBtfConnect.empty())
diff --git a/src/net.h b/src/net.h
index 37f335e..59a8028 100644
--- a/src/net.h
+++ b/src/net.h
@@ -54,6 +54,12 @@ static const int64 PEX_MIN_INTERVAL = 60;
54// traffic is nothing: one per relay per five minutes. 54// traffic is nothing: one per relay per five minutes.
55static const int BTF_RENDEZVOUS_WAIT_SECS = 300; 55static const int BTF_RENDEZVOUS_WAIT_SECS = 300;
56 56
57// Registrations this node parks at a rendezvous at once. One is what the
58// protocol needs; more is what keeps the node reachable while one of them is
59// being paired, since pairing consumes the registration it used. Four matches
60// the relay's ceiling per node.
61static const int BTF_ACCEPT_THREADS = 3;
62
57// Liveness of an established peer connection. BTF_RENDEZVOUS_WAIT_SECS bounds 63// Liveness of an established peer connection. BTF_RENDEZVOUS_WAIT_SECS bounds
58// the wait for a dial to arrive; these bound the connection that comes out of 64// the wait for a dial to arrive; these bound the connection that comes out of
59// it, which has the same failure mode once it goes quiet. 65// it, which has the same failure mode once it goes quiet.
@@ -139,6 +145,18 @@ extern int nBestHeight;
139// absurd height cannot move it. 145// absurd height cannot move it.
140int GetPeerMedianHeight(); 146int GetPeerMedianHeight();
141 147
148// Lightweight .btf churn accounting. These counters are diagnostics only: they
149// let an operator tell stale descriptors, relay pairing failures, and paired
150// tunnels that never speak apart before changing behaviour.
151void BtfChurnNoteResolveAttempt();
152void BtfChurnNoteResolveResult(bool fOk);
153void BtfChurnNoteDialAttempt(const string& strBtfAddr, const string& strMeeting);
154void BtfChurnNoteDialResult(const string& strBtfAddr, const string& strMeeting, bool fOk);
155void BtfChurnNoteRegisterResult(const string& strMeeting, bool fOk);
156void BtfChurnNotePairResult(const string& strMeeting, bool fOk);
157void BtfChurnNoteHandshakeTimeout(const string& strBtfAddr, const string& strMeeting,
158 bool fRecv, bool fSend);
159
142// Descriptors to hand a peer: ours first, then peers that answered us. 160// Descriptors to hand a peer: ours first, then peers that answered us.
143void BtfPexCollect(std::vector<std::string>& vDescOut); 161void BtfPexCollect(std::vector<std::string>& vDescOut);
144// Verify descriptors a peer sent and remember the good ones. Returns how many. 162// Verify descriptors a peer sent and remember the good ones. Returns how many.
@@ -606,6 +624,8 @@ public:
606 int64 nIncompleteMessageStart; 624 int64 nIncompleteMessageStart;
607 unsigned int nIncompleteMessageSize; 625 unsigned int nIncompleteMessageSize;
608 string strIncompleteMessageCommand; 626 string strIncompleteMessageCommand;
627 string strBtfAddr;
628 string strBtfMeeting;
609 629
610 // Height this peer announced in its version message, or -1 if it sent a 630 // Height this peer announced in its version message, or -1 if it sent a
611 // version message that predates the field. Knowing how far along everyone 631 // version message that predates the field. Knowing how far along everyone
@@ -639,6 +659,8 @@ public:
639 nIncompleteMessageStart = 0; 659 nIncompleteMessageStart = 0;
640 nIncompleteMessageSize = 0; 660 nIncompleteMessageSize = 0;
641 strIncompleteMessageCommand.clear(); 661 strIncompleteMessageCommand.clear();
662 strBtfAddr.clear();
663 strBtfMeeting.clear();
642 nStartingHeight = -1; 664 nStartingHeight = -1;
643 vfSubscribe.assign(256, false); 665 vfSubscribe.assign(256, false);
644 666
diff --git a/src/nostr.cpp b/src/nostr.cpp
index dc96be2..1fd60ad 100644
--- a/src/nostr.cpp
+++ b/src/nostr.cpp
@@ -1596,6 +1596,8 @@ static void ConnectDiscoveredBtfPeers()
1596 // handshakes refused, which then looks exactly like "dead peers" even 1596 // handshakes refused, which then looks exactly like "dead peers" even
1597 // though the descriptors were perfectly resolvable. 1597 // though the descriptors were perfectly resolvable.
1598 map<string, BtfResolvedPeer> resolved; 1598 map<string, BtfResolvedPeer> resolved;
1599 for (const string& addr : candidates)
1600 BtfChurnNoteResolveAttempt();
1599 BtfResolveMany(candidates, resolved); 1601 BtfResolveMany(candidates, resolved);
1600 1602
1601 // Candidates whose descriptor didn't resolve this pass are recorded as a 1603 // Candidates whose descriptor didn't resolve this pass are recorded as a
@@ -1608,9 +1610,11 @@ static void ConnectDiscoveredBtfPeers()
1608 { 1610 {
1609 if (resolved.count(addr)) 1611 if (resolved.count(addr))
1610 { 1612 {
1613 BtfChurnNoteResolveResult(true);
1611 toDial.push_back(addr); 1614 toDial.push_back(addr);
1612 continue; 1615 continue;
1613 } 1616 }
1617 BtfChurnNoteResolveResult(false);
1614 int n = ++g_btfPeerFails[addr]; 1618 int n = ++g_btfPeerFails[addr];
1615 int64 cap = (n >= BTF_CHRONIC_FAIL_THRESHOLD) ? BTF_BACKOFF_CAP_SECS_DEAD 1619 int64 cap = (n >= BTF_CHRONIC_FAIL_THRESHOLD) ? BTF_BACKOFF_CAP_SECS_DEAD
1616 : BTF_BACKOFF_CAP_SECS; 1620 : BTF_BACKOFF_CAP_SECS;