diff options
| author | Bitflash <btf01@proton.me> | 2026-08-02 11:59:12 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-02 11:59:12 -0300 |
| commit | 758ff4bc49af78b06e37fc1e99741cb5fa8e43d6 (patch) | |
| tree | 4e9d4ed63430d7a442260d1be78d63878a2fc455 | |
| parent | fe04a2045c3080797c6f93bf7cd2e12192b8f68f (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.
| -rw-r--r-- | relay/btfrv.cpp | 97 | ||||
| -rw-r--r-- | src/net.cpp | 194 | ||||
| -rw-r--r-- | src/net.h | 22 | ||||
| -rw-r--r-- | src/nostr.cpp | 4 |
4 files changed, 287 insertions, 30 deletions
diff --git a/relay/btfrv.cpp b/relay/btfrv.cpp index b1eee55..833c47c 100644 --- a/relay/btfrv.cpp +++ b/relay/btfrv.cpp | |||
| @@ -31,6 +31,7 @@ typedef int SOCKET; | |||
| 31 | #include <cstring> | 31 | #include <cstring> |
| 32 | #include <cstdio> | 32 | #include <cstdio> |
| 33 | #include <string> | 33 | #include <string> |
| 34 | #include <deque> | ||
| 34 | #include <map> | 35 | #include <map> |
| 35 | #include <thread> | 36 | #include <thread> |
| 36 | #include <mutex> | 37 | #include <mutex> |
| @@ -44,7 +45,24 @@ static std::atomic<bool> g_stop(false); | |||
| 44 | static SOCKET g_listen = INVALID_SOCKET; | 45 | static SOCKET g_listen = INVALID_SOCKET; |
| 45 | 46 | ||
| 46 | // Services that have registered and are waiting for a client, keyed by pubkey. | 47 | // Services that have registered and are waiting for a client, keyed by pubkey. |
| 47 | static std::map<std::string, SOCKET> g_waiting; | 48 | // More than one registration per node, on purpose. |
| 49 | // | ||
| 50 | // This used to be one socket per pubkey, and a dial *consumed* it: the first | ||
| 51 | // caller took the registration and everyone else was told "not found" until the | ||
| 52 | // node noticed and registered again. A node re-registers only after its pairing | ||
| 53 | // has been handed off, so between the two there is a window where it is | ||
| 54 | // unreachable to the whole network. Measured from a node's own counters: half | ||
| 55 | // of all outbound dials failed that way -- 15 of 30 in eleven minutes -- while | ||
| 56 | // descriptors resolved 53 times out of 54 and the node's own registrations | ||
| 57 | // paired 11 times out of 12. The rendezvous was not losing anybody; it was | ||
| 58 | // letting one caller in at a time. | ||
| 59 | // | ||
| 60 | // A queue per key lets a node keep spare registrations parked, so a dial that | ||
| 61 | // arrives while another is being paired finds the next one instead of a closed | ||
| 62 | // door. Old nodes register exactly as before and simply keep one entry: the | ||
| 63 | // wire protocol does not change, so a relay running this serves both. | ||
| 64 | static const size_t MAX_WAITING_PER_KEY = 4; | ||
| 65 | static std::map<std::string, std::deque<SOCKET> > g_waiting; | ||
| 48 | static std::mutex g_mtx; | 66 | static std::mutex g_mtx; |
| 49 | 67 | ||
| 50 | // How often the reaper walks the waiting map. | 68 | // How often the reaper walks the waiting map. |
| @@ -150,16 +168,24 @@ static void ReapDeadRegistrations() | |||
| 150 | int nBefore = 0, nReaped = 0; | 168 | int nBefore = 0, nReaped = 0; |
| 151 | { | 169 | { |
| 152 | std::lock_guard<std::mutex> lk(g_mtx); | 170 | std::lock_guard<std::mutex> lk(g_mtx); |
| 153 | nBefore = (int)g_waiting.size(); | 171 | std::map<std::string, std::deque<SOCKET> >::iterator it = g_waiting.begin(); |
| 154 | std::map<std::string, SOCKET>::iterator it = g_waiting.begin(); | ||
| 155 | while (it != g_waiting.end()) | 172 | while (it != g_waiting.end()) |
| 156 | { | 173 | { |
| 157 | if (RegistrationDead(it->second)) | 174 | std::deque<SOCKET>& q = it->second; |
| 175 | nBefore += (int)q.size(); | ||
| 176 | for (std::deque<SOCKET>::iterator jt = q.begin(); jt != q.end();) | ||
| 158 | { | 177 | { |
| 159 | CLOSESOCK(it->second); | 178 | if (RegistrationDead(*jt)) |
| 160 | g_waiting.erase(it++); | 179 | { |
| 161 | nReaped++; | 180 | CLOSESOCK(*jt); |
| 181 | jt = q.erase(jt); | ||
| 182 | nReaped++; | ||
| 183 | } | ||
| 184 | else | ||
| 185 | ++jt; | ||
| 162 | } | 186 | } |
| 187 | if (q.empty()) | ||
| 188 | g_waiting.erase(it++); | ||
| 163 | else | 189 | else |
| 164 | ++it; | 190 | ++it; |
| 165 | } | 191 | } |
| @@ -209,34 +235,57 @@ static void HandleIncoming(SOCKET s) | |||
| 209 | { | 235 | { |
| 210 | // Register and wait; a future client will pair us. | 236 | // Register and wait; a future client will pair us. |
| 211 | std::lock_guard<std::mutex> lk(g_mtx); | 237 | std::lock_guard<std::mutex> lk(g_mtx); |
| 212 | // If a stale service for this key exists, drop it. | 238 | std::deque<SOCKET>& q = g_waiting[pubkey]; |
| 213 | std::map<std::string, SOCKET>::iterator it = g_waiting.find(pubkey); | 239 | |
| 214 | if (it != g_waiting.end()) CLOSESOCK(it->second); | 240 | // Drop registrations this node has already lost the far end of, rather |
| 215 | g_waiting[pubkey] = s; | 241 | // than dropping the previous one on principle. The old code closed the |
| 216 | // leave the socket open in the map; do not close here. | 242 | // prior registration on every new one, which is why a node could never |
| 243 | // hold a spare. | ||
| 244 | for (std::deque<SOCKET>::iterator it = q.begin(); it != q.end();) | ||
| 245 | { | ||
| 246 | if (RegistrationDead(*it)) { CLOSESOCK(*it); it = q.erase(it); } | ||
| 247 | else ++it; | ||
| 248 | } | ||
| 249 | |||
| 250 | // A ceiling, because a stranger can open these. Oldest goes first: it | ||
| 251 | // is the one most likely to be stale. | ||
| 252 | while (q.size() >= MAX_WAITING_PER_KEY) | ||
| 253 | { | ||
| 254 | CLOSESOCK(q.front()); | ||
| 255 | q.pop_front(); | ||
| 256 | } | ||
| 257 | q.push_back(s); | ||
| 258 | // leave the socket open in the queue; do not close here. | ||
| 217 | } | 259 | } |
| 218 | else if (role == 'C') | 260 | else if (role == 'C') |
| 219 | { | 261 | { |
| 220 | SOCKET svc = INVALID_SOCKET; | 262 | SOCKET svc = INVALID_SOCKET; |
| 221 | { | 263 | { |
| 222 | std::lock_guard<std::mutex> lk(g_mtx); | 264 | std::lock_guard<std::mutex> lk(g_mtx); |
| 223 | std::map<std::string, SOCKET>::iterator it = g_waiting.find(pubkey); | 265 | std::map<std::string, std::deque<SOCKET> >::iterator it = g_waiting.find(pubkey); |
| 224 | if (it != g_waiting.end()) | 266 | if (it != g_waiting.end()) |
| 225 | { | 267 | { |
| 226 | // Check before handing it over. Pairing a client with a dead | 268 | std::deque<SOCKET>& q = it->second; |
| 227 | // registration produces a tunnel that fails after both sides | 269 | // Take the oldest one that is still alive, discarding the dead |
| 228 | // think they succeeded, which is the failure the dialling node | 270 | // as we go. Checking before handing it over matters: pairing a |
| 229 | // reports as "tunnel to X failed" with nothing to explain it. | 271 | // client with a dead registration produces a tunnel that fails |
| 230 | if (RegistrationDead(it->second)) | 272 | // after both sides think they succeeded, which is the failure |
| 273 | // the dialling node reports as "tunnel to X failed" with | ||
| 274 | // nothing to explain it. | ||
| 275 | while (!q.empty()) | ||
| 231 | { | 276 | { |
| 232 | CLOSESOCK(it->second); | 277 | SOCKET candidate = q.front(); |
| 233 | g_waiting.erase(it); | 278 | q.pop_front(); |
| 279 | if (RegistrationDead(candidate)) | ||
| 280 | { | ||
| 281 | CLOSESOCK(candidate); | ||
| 282 | continue; | ||
| 283 | } | ||
| 284 | svc = candidate; | ||
| 285 | break; | ||
| 234 | } | 286 | } |
| 235 | else | 287 | if (q.empty()) |
| 236 | { | ||
| 237 | svc = it->second; | ||
| 238 | g_waiting.erase(it); | 288 | g_waiting.erase(it); |
| 239 | } | ||
| 240 | } | 289 | } |
| 241 | } | 290 | } |
| 242 | if (svc == INVALID_SOCKET) | 291 | if (svc == INVALID_SOCKET) |
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; | |||
| 51 | int64 nBlocksWithoutParent = 0; | 51 | int64 nBlocksWithoutParent = 0; |
| 52 | static int64 nNodeStartTime = 0; | 52 | static int64 nNodeStartTime = 0; |
| 53 | 53 | ||
| 54 | static CCriticalSection cs_btfChurn; | ||
| 55 | static int64 nBtfResolveAttempts = 0; | ||
| 56 | static int64 nBtfResolveOk = 0; | ||
| 57 | static int64 nBtfResolveMiss = 0; | ||
| 58 | static int64 nBtfDialAttempts = 0; | ||
| 59 | static int64 nBtfDialOk = 0; | ||
| 60 | static int64 nBtfDialFailed = 0; | ||
| 61 | static int64 nBtfRegisterOk = 0; | ||
| 62 | static int64 nBtfRegisterFailed = 0; | ||
| 63 | static int64 nBtfPairOk = 0; | ||
| 64 | static int64 nBtfPairFailed = 0; | ||
| 65 | static int64 nBtfHandshakeNoRecv = 0; | ||
| 66 | static int64 nBtfHandshakeNoSend = 0; | ||
| 67 | static int64 nBtfHandshakeSilent = 0; | ||
| 68 | static string strBtfLastDialFail; | ||
| 69 | static string strBtfLastHandshakeTimeout; | ||
| 70 | |||
| 71 | void BtfChurnNoteResolveAttempt() | ||
| 72 | { | ||
| 73 | CRITICAL_BLOCK(cs_btfChurn) | ||
| 74 | nBtfResolveAttempts++; | ||
| 75 | } | ||
| 76 | |||
| 77 | void BtfChurnNoteResolveResult(bool fOk) | ||
| 78 | { | ||
| 79 | CRITICAL_BLOCK(cs_btfChurn) | ||
| 80 | { | ||
| 81 | if (fOk) nBtfResolveOk++; | ||
| 82 | else nBtfResolveMiss++; | ||
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 86 | void 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 | |||
| 94 | void 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 | |||
| 110 | void 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 | |||
| 121 | void 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 | |||
| 132 | void 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) -------------- |
| 55 | static const char* pszSockSite[SOCK_SITES] = { | 152 | static 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()) |
| @@ -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. |
| 55 | static const int BTF_RENDEZVOUS_WAIT_SECS = 300; | 55 | static 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. | ||
| 61 | static 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. |
| 140 | int GetPeerMedianHeight(); | 146 | int 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. | ||
| 151 | void BtfChurnNoteResolveAttempt(); | ||
| 152 | void BtfChurnNoteResolveResult(bool fOk); | ||
| 153 | void BtfChurnNoteDialAttempt(const string& strBtfAddr, const string& strMeeting); | ||
| 154 | void BtfChurnNoteDialResult(const string& strBtfAddr, const string& strMeeting, bool fOk); | ||
| 155 | void BtfChurnNoteRegisterResult(const string& strMeeting, bool fOk); | ||
| 156 | void BtfChurnNotePairResult(const string& strMeeting, bool fOk); | ||
| 157 | void 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. |
| 143 | void BtfPexCollect(std::vector<std::string>& vDescOut); | 161 | void 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; |