upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/net.cpp
diff options
context:
space:
mode:
authorbitflash-maintainer <noreply@bitflash.invalid>2026-08-23 05:21:31 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-08-23 05:21:31 +0000
commita9b4f8940907c2bf51e431172b628de89734a908 (patch)
treeff0a0b19d7d7754b809e35c6eae43dae7a32e1e4 /src/net.cpp
parent6d4c3f153cf44cefead39b21191dfb33491bfa5a (diff)
parentbc3de301d9ed0e5fb5e2034be2e066d77cf23804 (diff)
Merge upstream v1.2.18 into the fork
Merges upstream tag v1.2.18 (bc3de30) into our 24 commits. The merge base is a259215, upstream's own commit, whose src/ tree is byte-identical to 68de575 -- our commits were replayed onto a259215 first so that git had a real base to work from. That produced 28 conflict hunks in 9 files instead of the 21 conflict events / 51 hunks a direct cherry-pick onto the tag would have forced, most of which were artifacts of lifting our commits away from their own predecessors. No consensus change. main.cpp's GetBlockValue, GetNextWorkRequired, ConnectInputs, ConnectBlock, CheckBlock, AcceptBlock and AcceptTransaction are untouched by this merge; script.cpp, serialize.h and randomx_pow.h are identical on both sides. The frozen-chain oracle reproduces the pinned UTXO root 116a2b6b... byte for byte, and the merged binary loads the same 12,586-block chain to the same tip aac622f0a3b2f8... with all blocks re-verified and none rejected. Which side won, per file and per hunk. src/rpc.cpp -- 15 hunks. 1. RebuildJob transaction selection. THEIRS for the multi-pass loop and the nFees fix, OURS for the cap. Their vfAlreadyAdded/fFoundSomething loop lands dependent transactions that our single pass dropped deterministically, and their nFees is accumulated across the scan instead of being redeclared inside it -- ours passed a hardcoded 0 to GetBlockValue, so every pool block built here discarded its fees. But their bound is MAX_SIZE/2 = 16 MB, and MAX_BLOCK_SIZE is 1 MB, so their template can exceed what their own CheckBlock accepts. Cap kept at MAX_BLOCK_SIZE/2, with our inner break so a single pass cannot overshoot by more than one transaction. Our RebuildJob sync gating and deaf-node detection are kept; upstream has none. 2. struct PendingPayout. OURS for the key, THEIRS for paidTxids. Their id is the string "block-<height>", which is height keying -- the exact bug 44849e6 fixed after a 280-block reorg left ~14.4k BTF owed against blocks that no longer existed. Height keying also lets the crash-recovery probe match the previous round's transaction after a reorg and silently skip a miner, so their id is dropped entirely and blockHash stays the key. paidTxids is taken because it is what makes an entry survivable. Their nextAttempt and our retryAfter are the same concept under two names; ours is kept and theirs dropped. matureAtHeight gained an explicit = 0, which neither side had in our style of initialiser. 3./4. Ledger writer and loader. Mechanical follow-ons of 2. The loader keeps ours and takes their tolerance for an entry with no usable height, guarded so it cannot fire on a well-formed entry. One redundancy was removed rather than merged: their unguarded `if (obj.contains("blockHeight")) get<int>()` sat two lines above our is_number-guarded version, and an unguarded get<int>() on a malformed value throws out of the whole loop, losing every entry in the file. Verified against the live production pending_payouts.json (2 entries, keys amounts/blockHash/blockHeight/matureAtHeight): loads both. A file in the new shape, with retryAfter and paidTxids, also loads. 5. Insertion point after the loader. BOTH. Our PayingRecord/gPaying in-flight ledger and their PoolRoundProof/PoolRoundToJson are disjoint features that landed at the same offset. Both kept, in full. 6. Writer tail. THEIRS, copied into our writer. Checking fclose() matters here because fclose is where the buffered write actually reaches the OS: renaming a short .tmp over a good file is precisely the truncation the atomic write exists to prevent. The Windows remove()-before-rename() is required because rename() there will not replace an existing file. 7. Loader bodies. BOTH -- LoadPaying and LoadPoolRounds are different functions that happened to share a common tail. 8. Function block, 111 vs 36 lines. BOTH, with one deliberate deletion. PayoutBlockStillOnChain, PayoutComment, RecordPoolRound, MarkPoolRoundPaid all kept. WalletAlreadyPaid is DELETED and ReconcilePayouts rewritten, because our reconciliation and their FindRecordedPoolPayout are two answers to the same crash-recovery question and running both would give the same debt two authorities. Their wallet probe wins: it matches structured metadata rather than a comment string, so it can tell two rounds paying the same address the same amount apart. ReconcilePayouts keeps only the job that is still real -- dropping in-flight records the ledger already shows paid, and arming each restored record for its one wallet probe. It no longer touches gPendingPayouts at all; under the new model dropping ledger entries there would reintroduce the loss this merge adopts their model to prevent. Their gPayoutExecMutex/gPayoutThreadRunning re-declarations were dropped as duplicates of ours, and gPayoutExecQueue was not taken: gPaying is the exec queue in this tree, at finer granularity. 9. QueuePayouts signature. THEIRS for the round-proof arguments, OURS for the type of blockHash. It stays a uint256 and is converted to hex only where the round proof needs text -- a ledger key that has been through ToString() is a key that can be compared wrong. 10. Maturity policy. OURS. matureAtHeight = blockHeight + COINBASE_SPENDABLE_DEPTH (120), not + COINBASE_MATURITY (100). At depth 100 the block's own coinbase is still 20 blocks short of spendable, so the payment must come from unrelated mature coin and the pool runs a permanent float. Our own history contradicts itself here -- d6972a0 argued for COINBASE_MATURITY citing "96% of 934 payments", 44849e6 reversed it on the grounds that the measurement was taken while the node was alone on a private fork paying itself. HEAD's position is kept. This is a considered disagreement with upstream, not an oversight. The live production ledger confirms it: both entries have matureAtHeight - blockHeight = 120. 11. Comment above FlushMaturePayouts. Rewritten to describe the merged design. 12. FlushMaturePayouts, the central difference. THEIRS for the model. The ledger entry now survives on disk until every recipient has a txid, instead of being erased as soon as its payments were handed off -- the old ordering left a window where the debt was described by nothing at all, and a container restart on 2026-07-30 silently dropped ~1,156 BTF through it. Our gPaying is kept on top as the in-flight working set, because it carries the per-recipient retry and deferral state a per-round nextAttempt cannot express. It is explicitly a cache derived from the ledger, not a second record of debt: if it were lost the next flush would rebuild it. 13. PayoutThreadFn, 79 vs 70 lines. OURS for the loop and the reorg re-check, THEIRS for the per-recipient dedupe. The re-check on every attempt is load-bearing and upstream has no equivalent -- it decides once, at maturity, and a payment can sit through hours of retries during which the reorg that kills its funding block can land. Their paidTxids check is taken, against the ledger rather than against the batch. Our deferral policy is kept: waiting for coin is not a failure and must not consume the retry budget. Two fixes upstream does not have are folded in here rather than deferred to a follow-up commit: - FindRecordedPoolPayout runs once per record, on its first attempt, not on every attempt. It is a linear scan of mapWallet under cs_mapWallet, per recipient; upstream repeats it for every retry, which with 2000 rotating payout addresses and a budget of 1440 retries is a full wallet scan per address per minute for a day. The question it answers can only change across a restart, and every restart re-arms the flag. - The ledger file write no longer happens under gPayoutLedgerMutex in the per-recipient loop. SavePendingPayoutsLocked was split into a serialise step that needs the lock and a write step that does not, so the mutex is held only long enough to read the structure. The orphan scan in FlushMaturePayouts was likewise restructured so the ledger mutex is not held across PayoutBlockStillOnChain's cs_main acquisition. One case the merged model created and neither side handled: under survives-until-fully-paid, our GIVING UP path dropped the in-flight record but left the recipient owed in the ledger forever, so every later flush would explode it again -- a forfeit that silently becomes an infinite retry. ForfeitPayoutRecipient writes the amount off so the entry can retire. 14. QueuePayouts call site. Follow-on of 9. 15. ThreadRPCServer startup. BOTH. LoadPoolRounds added; LoadPaying and ReconcilePayouts stay, and stay before the payout thread starts. Crash safety, stated as the property that must hold: after kill -9 at any point, restart pays each recipient exactly once. Before SendMoney, the ledger has no txid and the payment is retried. Between SendMoney and the ledger write, the wallet holds the transaction and the first-attempt probe finds it. After the ledger write, paidTxids suppresses it. If the in-flight file is lost entirely, the ledger rebuilds it and the probe still covers the middle window. src/nostr.cpp -- 3 hunks. THEIRS as the base, with two things restored from ours. Theirs loads the Windows ROOT certificate store, which ours lacks and genuinely needs, since OpenSSL's default verify paths are usually empty there and our version would fail closed. But the plan's claim that theirs is a superset of our 06bceb4 is FALSE: upstream's nostr.cpp contains no min_proto_version, no TLS1_2, no SSL_OP_NO_*, and no set_options anywhere, so taking it wholesale would have removed the TLS-version floor outright. Restored. Second, their SSL_get_verify_result check sits after a successful handshake, where with SSL_VERIFY_PEER set it is nearly dead code -- a bad certificate aborts inside SSL_connect. The reporting belongs in the failure branch, which is where the merge put it; their post-handshake duplicate was dropped. Our relay-list and socket-pooling hunks auto-merged and were verified intact. src/net.cpp -- 1 hunk. THEIRS. Their guard, hSocket == INVALID_SOCKET || pnode->hSocket != hSocket, subsumes ours and additionally catches descriptor recycling, which ours did not; our version also referenced a pnode that only existed in the select() loop. Our other db34011 hunk, nulling pnode->hSocket in the poll-failure branch, auto-merged and was NOT re-applied -- plan section 7 Batch D says to re-apply it, and that instruction is stale. Two pieces of residue the merge left behind were cleaned up: a comment still describing select() semantics, and a pnode->hSocket != INVALID_SOCKET guard made redundant by a continue two lines above it. src/main.cpp -- 2 hunks. h1 BOTH, pure add/add adjacency between our ParticipantHashThreadCount and their SecureClear forward declarations. h2 OURS, and their handler discarded: 44004e3 no-ops checkorder/submitorder but keeps market.cpp and market.h, whereas 11766be deletes the marketplace outright including CRequestTracker, subscribe/publish and mapProducts. Ours is strictly more. src/main_gui.cpp -- 2 hunks. BOTH, additive on both sides. Our /payoutaddr, /payoutfile and /hashthreads alongside their /stratumbridge and /stratumbridgeport. src/btfrv.cpp -- 1 hunk, not predicted by the plan. False adjacency: our INVALID_SOCKET check and their #endif landed at the same offset. Ordering is load-bearing -- our check goes inside the #else arm, before the #endif, or the non-proxy build silently loses it. The BITFLASH_NODE_BUILD arm already returns early, so both paths now have the check. Makefile -- 2 hunks plus a hazard with no conflict marker. THEIRS for both dependency-fetch recipes: checkout --detach FETCH_HEAD followed by test "$(git rev-parse HEAD)" = "$(..._COMMIT)" asserts the pin actually landed, where ours only checked it out. Everything of ours that they lack is kept: the appimagetool 1.9.1 release pin with a sha256 check, where upstream fetches the mutable "continuous" tag and executes it undigested, and the move of both the tool and the AppDir out of /tmp, where any local user could pre-place an executable and have it run as whoever runs make. The hazard: both sides added a pin block in a different place, so git merged both, outside any conflict region. SECP256K1_COMMIT and RANDOMX_COMMIT ended up assigned twice, near L16 and near L44, and GNU Make takes the last := -- a naive resolution would have silently built against our old secp256k1 pin while appearing to adopt upstream's. The duplicate block was deleted by hand. Each of the four variables is now assigned exactly once, and make -n resolves SECP256K1_COMMIT to 7fecac74 with 6e2c8bc4 appearing nowhere. Also applied here, from plan phase 4: SECP256K1_COMMIT moved to upstream's 7fecac74aed8e1fd9078380d67dd04663705c989 so signature validation matches the binaries the rest of the network runs. secp256k1 was rebuilt from scratch against the new pin on the build host and the checked-out dependency commit verified. RANDOMX_COMMIT already matched at 1e9d4b2d and is unchanged. src/Makefile, src/makefile.mingw -- 1 hunk each, both the HEADERS list. Ours drops market.h, theirs adds proxy.h; the resolution does both. Taking theirs wholesale would have left the Makefiles referencing a header git had already deleted in our favour, silently, without a conflict. Files the plan predicted would conflict and did not: net.h, main.h and db.cpp auto-merged. Verified rather than assumed. net.h carries both our BTF_MSG_WAIT_MAX_SECS work, including the constructor fix from 2d84982 that starts nMsgWaitSince and nMsgWaitSize at 0, and their comment rewrite with extern int nPeersWatched. The dead members that fix removed are gone from every file. Deviations from MERGE-1.2.18-PLAN.md. - Section 6's mechanism was not used. Replayed onto the real merge base and merged, rather than cherry-picking onto the tag. Same result, 28 hunks instead of 51. - Section 2 calls 09ff575 a superset of our 06bceb4. It is not; upstream has no TLS-version floor at all. Ours restored on top of theirs. - Section 7 Batch D says to re-apply our db34011 line into their poll-failure branch. It auto-merged; re-applying would have duplicated it. - Section 2 says do not take the stratum bridge. It is taken, and left off. fStratumBridge defaults false in main.cpp, the thread is only spawned when /stratumbridge is passed, and the listener binds 127.0.0.1 only. Excising it would cost real divergence and future conflict for no gain while it is disabled. Its defects -- select()/FD_SET on a node that can hold more than 1024 descriptors, and an uncapped thread per connection -- are real only when it is enabled and are being reported upstream separately. - Section 7's staged batches were not run as separate gated commits. The conflict surface is interleaved within single functions, chiefly the payout ledger, and splitting it would have meant committing states that do not build. The merge is one commit, as briefed. - Batch F's two fixes are folded into hunk 13 rather than deferred, because they change the same lines the resolution rewrites. - The plan did not anticipate the btfrv.cpp conflict, the duplicate pin block, the market.h trap in the two Makefiles, or the give-up/infinite-retry interaction created by adopting upstream's ledger model.
Diffstat (limited to 'src/net.cpp')
-rw-r--r--src/net.cpp181
1 files changed, 101 insertions, 80 deletions
diff --git a/src/net.cpp b/src/net.cpp
index 34c608c..bb39122 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -13,15 +13,32 @@
13#include <thread> // hardware_concurrency, for the miner thread count 13#include <thread> // hardware_concurrency, for the miner thread count
14#ifdef _WIN32 14#ifdef _WIN32
15#include <winsock2.h> 15#include <winsock2.h>
16#else
17#include <poll.h>
16#endif 18#endif
17#include <openssl/rand.h> 19#include <openssl/rand.h>
18#include "btfaddr.h" 20#include "btfaddr.h"
19#include "btftunnel.h" 21#include "btftunnel.h"
22#include "proxy.h"
20 23
21void ThreadReconnectCachedBtfPeers(void* parg); 24void ThreadReconnectCachedBtfPeers(void* parg);
22void ThreadMessageHandler2(void* parg); 25void ThreadMessageHandler2(void* parg);
23void ThreadSocketHandler2(void* parg); 26void ThreadSocketHandler2(void* parg);
24 27
28#ifdef _WIN32
29typedef WSAPOLLFD BtfPollFd;
30static int BtfPoll(BtfPollFd* pfd, unsigned int nfd, int nTimeoutMs)
31{
32 return WSAPoll(pfd, nfd, nTimeoutMs);
33}
34#else
35typedef struct pollfd BtfPollFd;
36static int BtfPoll(BtfPollFd* pfd, unsigned int nfd, int nTimeoutMs)
37{
38 return poll(pfd, (nfds_t)nfd, nTimeoutMs);
39}
40#endif
41
25 42
26 43
27 44
@@ -153,7 +170,7 @@ static const char* pszSockSite[SOCK_SITES] = {
153 "external-ip probe", "listen socket", "inbound accept", 170 "external-ip probe", "listen socket", "inbound accept",
154 "rendezvous dial", "rendezvous listen", "rendezvous accept", 171 "rendezvous dial", "rendezvous listen", "rendezvous accept",
155 "loopback pair listener", "loopback pair app end", "loopback pair pump end", 172 "loopback pair listener", "loopback pair app end", "loopback pair pump end",
156 "nostr relay" 173 "nostr relay", "stratum bridge"
157}; 174};
158 175
159static string SockAccountingText() 176static string SockAccountingText()
@@ -274,8 +291,8 @@ string GetDiagnosticsText()
274 291
275 // The number that would have made the deafness obvious. Anything held but 292 // The number that would have made the deafness obvious. Anything held but
276 // not watched is a socket this node will never read again. 293 // not watched is a socket this node will never read again.
277 str += strprintf(" peers watched %d of %d that select() can hold%s\n", 294 str += strprintf(" peers watched %d of %d held by poll%s\n",
278 nPeersWatched, (int)FD_SETSIZE - 1, 295 nPeersWatched, nHeld,
279 nHeld > nPeersWatched ? " <-- NOT ALL PEERS ARE BEING READ" : ""); 296 nHeld > nPeersWatched ? " <-- NOT ALL PEERS ARE BEING READ" : "");
280 297
281 if (nBlocksReceived > 0) 298 if (nBlocksReceived > 0)
@@ -361,6 +378,9 @@ string GetDiagnosticsText()
361 378
362bool GetMyExternalIP(unsigned int& ipRet) 379bool GetMyExternalIP(unsigned int& ipRet)
363{ 380{
381 if (BtfSocks5ProxyEnabled())
382 return error("GetMyExternalIP() skipped while SOCKS5 proxy is enabled\n");
383
364 // Try several plain-text IP echo services in order. 384 // Try several plain-text IP echo services in order.
365 // Each returns just the IPv4 address as the first line of the HTTP body. 385 // Each returns just the IPv4 address as the first line of the HTTP body.
366 struct { const char* host; const char* path; } services[] = { 386 struct { const char* host; const char* path; } services[] = {
@@ -1127,7 +1147,7 @@ void CNode::Disconnect()
1127 1147
1128 // Invalidate before anything else can look at it. This used to close the 1148 // Invalidate before anything else can look at it. This used to close the
1129 // socket and leave the handle number sitting in the object, so ~CNode 1149 // socket and leave the handle number sitting in the object, so ~CNode
1130 // closed it a second time and the select loop kept arming it in the 1150 // closed it a second time and the socket loop kept arming it in the
1131 // meantime. See the note on ~CNode in net.h. 1151 // meantime. See the note on ~CNode in net.h.
1132 BtfCloseSocket(hSocket); 1152 BtfCloseSocket(hSocket);
1133 hSocket = INVALID_SOCKET; 1153 hSocket = INVALID_SOCKET;
@@ -1276,52 +1296,49 @@ void ThreadSocketHandler2(void* parg)
1276 // 1296 //
1277 // Find which sockets have data to receive 1297 // Find which sockets have data to receive
1278 // 1298 //
1279 struct timeval timeout; 1299 static const int nPollTimeoutMs = 50; // frequency to poll pnode->vSend
1280 timeout.tv_sec = 0; 1300
1281 timeout.tv_usec = 50000; // frequency to poll pnode->vSend 1301 struct NodePollResult
1282 1302 {
1283 fd_set fdsetRecv; 1303 CNode* pnode;
1284 fd_set fdsetSend; 1304 SOCKET hSocket;
1285 FD_ZERO(&fdsetRecv); 1305 bool fRecv;
1286 FD_ZERO(&fdsetSend); 1306 bool fSend;
1287 SOCKET hSocketMax = 0; 1307 bool fInvalid;
1288 FD_SET(hListenSocket, &fdsetRecv); 1308 NodePollResult(CNode* pnodeIn, SOCKET hSocketIn)
1289 hSocketMax = max(hSocketMax, hListenSocket); 1309 : pnode(pnodeIn), hSocket(hSocketIn), fRecv(false), fSend(false), fInvalid(false) {}
1290 // FD_SET drops silently once the set is full, so count what goes in 1310 };
1291 // and say so. A peer that never makes it into the set is never read, 1311
1292 // and that is indistinguishable from a peer with nothing to say -- 1312 vector<BtfPollFd> vPoll;
1293 // the failure this whole path used to hide. 1313 vector<NodePollResult> vPollNodes;
1294 unsigned int nWatched = 1; // the listen socket 1314
1315 BtfPollFd listenPoll;
1316 memset(&listenPoll, 0, sizeof(listenPoll));
1317 listenPoll.fd = hListenSocket;
1318 listenPoll.events = POLLIN;
1319 vPoll.push_back(listenPoll);
1320
1295 CRITICAL_BLOCK(cs_vNodes) 1321 CRITICAL_BLOCK(cs_vNodes)
1296 { 1322 {
1297 foreach(CNode* pnode, vNodes) 1323 foreach(CNode* pnode, vNodes)
1298 { 1324 {
1299 if (nWatched >= FD_SETSIZE)
1300 break;
1301 // A disconnected node stays in vNodes until its references go. 1325 // A disconnected node stays in vNodes until its references go.
1302 // Arming its closed handle asks select() to watch a number the 1326 // Do not watch a closed handle number: the OS may already have
1303 // operating system may already have given to somebody else. 1327 // handed it to a different connection.
1304 if (pnode->hSocket == INVALID_SOCKET) 1328 if (pnode->hSocket == INVALID_SOCKET)
1305 continue; 1329 continue;
1306 FD_SET(pnode->hSocket, &fdsetRecv); 1330
1307 nWatched++; 1331 BtfPollFd pfd;
1308 hSocketMax = max(hSocketMax, pnode->hSocket); 1332 memset(&pfd, 0, sizeof(pfd));
1333 pfd.fd = pnode->hSocket;
1334 pfd.events = POLLIN;
1309 TRY_CRITICAL_BLOCK(pnode->cs_vSend) 1335 TRY_CRITICAL_BLOCK(pnode->cs_vSend)
1310 if (!pnode->vSend.empty()) 1336 if (!pnode->vSend.empty())
1311 FD_SET(pnode->hSocket, &fdsetSend); 1337 pfd.events |= POLLOUT;
1312 } 1338 vPoll.push_back(pfd);
1313 if (vNodes.size() + 1 > (size_t)FD_SETSIZE) 1339 vPollNodes.push_back(NodePollResult(pnode, pnode->hSocket));
1314 {
1315 static int64 nLastWarned = 0;
1316 if (GetTime() - nLastWarned > 60)
1317 {
1318 nLastWarned = GetTime();
1319 printf("WARNING: %d peers but select() can only watch %d -- %d are not being read\n",
1320 (int)vNodes.size(), (int)FD_SETSIZE - 1,
1321 (int)vNodes.size() - (int)FD_SETSIZE + 1);
1322 }
1323 } 1340 }
1324 nPeersWatched = (int)nWatched - 1; // less the listen socket 1341 nPeersWatched = (int)vPollNodes.size();
1325 } 1342 }
1326 1343
1327 // Say the state of the node out loud now and then, so a log pulled off 1344 // Say the state of the node out loud now and then, so a log pulled off
@@ -1342,32 +1359,17 @@ void ThreadSocketHandler2(void* parg)
1342 } 1359 }
1343 1360
1344 vfThreadRunning[0] = false; 1361 vfThreadRunning[0] = false;
1345 int nSelect = select(hSocketMax + 1, &fdsetRecv, &fdsetSend, NULL, &timeout); 1362 int nPoll = BtfPoll(&vPoll[0], (unsigned int)vPoll.size(), nPollTimeoutMs);
1346 vfThreadRunning[0] = true; 1363 vfThreadRunning[0] = true;
1347 CheckForShutdown(0); 1364 CheckForShutdown(0);
1348 if (nSelect == SOCKET_ERROR) 1365 if (nPoll == SOCKET_ERROR)
1349 { 1366 {
1350 // This used to answer a failed select() by marking every
1351 // descriptor from 0 to hSocketMax ready, which is code written for
1352 // POSIX, where a descriptor is a small integer. A Windows SOCKET is
1353 // a kernel handle in the thousands, so the loop iterated over
1354 // numbers that are not sockets and told the rest of the function
1355 // that every peer had data. One bad handle in the set makes
1356 // select() fail on every pass, so a single closed socket put the
1357 // whole thread into that state permanently.
1358 //
1359 // Find the bad handle instead and let it be dropped.
1360 int nErr = WSAGetLastError(); 1367 int nErr = WSAGetLastError();
1361 LogPrint("net", "select failed: %d\n", nErr); 1368 LogPrint("net", "poll failed: %d\n", nErr);
1362 FD_ZERO(&fdsetRecv);
1363 FD_ZERO(&fdsetSend);
1364 CRITICAL_BLOCK(cs_vNodes) 1369 CRITICAL_BLOCK(cs_vNodes)
1365 { 1370 {
1366 foreach(CNode* pnode, vNodes) 1371 foreach(CNode* pnode, vNodes)
1367 { 1372 {
1368 // Already disconnected: its handle is gone on purpose, and
1369 // reporting it here would name the wrong node for a select()
1370 // failure it did not cause.
1371 if (pnode->hSocket == INVALID_SOCKET) 1373 if (pnode->hSocket == INVALID_SOCKET)
1372 continue; 1374 continue;
1373 int nType = 0; 1375 int nType = 0;
@@ -1376,24 +1378,27 @@ void ThreadSocketHandler2(void* parg)
1376#else 1378#else
1377 socklen_t nTypeLen = sizeof(nType); 1379 socklen_t nTypeLen = sizeof(nType);
1378#endif 1380#endif
1379 if (pnode->hSocket != INVALID_SOCKET && 1381 if (getsockopt(pnode->hSocket, SOL_SOCKET, SO_TYPE, (char*)&nType, &nTypeLen) != 0)
1380 getsockopt(pnode->hSocket, SOL_SOCKET, SO_TYPE, (char*)&nType, &nTypeLen) != 0)
1381 { 1382 {
1382 printf("dropping node %s: its socket is no longer valid\n", 1383 printf("dropping node %s: its socket is no longer valid\n",
1383 pnode->addr.ToString().c_str()); 1384 pnode->addr.ToString().c_str());
1384 pnode->fDisconnect = true; 1385 pnode->fDisconnect = true;
1385 1386
1386 // Stop referring to it, or it goes back into the next 1387 // Stop referring to it, or it goes into the next poll
1387 // select() set and fails it again. 1388 // set and fails it again.
1388 // 1389 //
1389 // Marking fDisconnect is not enough: a node is only 1390 // Marking fDisconnect is not enough: a node is only
1390 // removed from vNodes once it has drained or once 1391 // removed from vNodes once it has drained or once
1391 // DISCONNECT_DRAIN_SECS has passed, so one dead 1392 // DISCONNECT_DRAIN_SECS has passed, so one dead
1392 // descriptor kept failing select() for a full minute -- 1393 // descriptor kept failing the wait for a full minute --
1393 // and a failed select() zeroes both sets above, which 1394 // and a failed wait skips the service loop below
1394 // means NO peer is read on that pass. Measured on a 1395 // entirely, which means NO peer is read on that pass.
1395 // wedged pool: 1,027 consecutive passes over 61 1396 // Measured on a wedged pool under the old select()
1396 // seconds, one bad socket, the entire node deaf. 1397 // implementation: 1,027 consecutive passes over 61
1398 // seconds, one bad socket, the entire node deaf. poll()
1399 // reports a bad descriptor as POLLNVAL on that entry
1400 // rather than failing the whole call, so this path is
1401 // now the backstop rather than the common case.
1397 // 1402 //
1398 // Not closed, only forgotten. getsockopt just told us 1403 // Not closed, only forgotten. getsockopt just told us
1399 // this descriptor is not ours any more; closing it now 1404 // this descriptor is not ours any more; closing it now
@@ -1402,7 +1407,17 @@ void ThreadSocketHandler2(void* parg)
1402 } 1407 }
1403 } 1408 }
1404 } 1409 }
1405 Sleep(timeout.tv_usec/1000); 1410 Sleep(nPollTimeoutMs);
1411 }
1412 else
1413 {
1414 for (size_t i = 0; i < vPollNodes.size(); i++)
1415 {
1416 short revents = vPoll[i + 1].revents; // poll[0] is the listener
1417 vPollNodes[i].fRecv = (revents & (POLLIN | POLLERR | POLLHUP)) != 0;
1418 vPollNodes[i].fSend = (revents & POLLOUT) != 0;
1419 vPollNodes[i].fInvalid = (revents & POLLNVAL) != 0;
1420 }
1406 } 1421 }
1407 RandAddSeed(); 1422 RandAddSeed();
1408 1423
@@ -1418,7 +1433,7 @@ void ThreadSocketHandler2(void* parg)
1418 // 1433 //
1419 // Accept new connections 1434 // Accept new connections
1420 // 1435 //
1421 if (FD_ISSET(hListenSocket, &fdsetRecv)) 1436 if (nPoll != SOCKET_ERROR && (vPoll[0].revents & POLLIN))
1422 { 1437 {
1423 struct sockaddr_in sockaddr; 1438 struct sockaddr_in sockaddr;
1424#ifdef _WIN32 1439#ifdef _WIN32
@@ -1437,7 +1452,7 @@ void ThreadSocketHandler2(void* parg)
1437 { 1452 {
1438 // Refuse rather than accept a connection this node cannot 1453 // Refuse rather than accept a connection this node cannot
1439 // watch. Without this the listen socket kept taking peers -- 1454 // watch. Without this the listen socket kept taking peers --
1440 // it is always in the select set -- while everything past the 1455 // it is always watched -- while everything past the
1441 // limit sat open and unread, which fed on itself: the deafer 1456 // limit sat open and unread, which fed on itself: the deafer
1442 // the node got, the more connections it collected. 1457 // the node got, the more connections it collected.
1443 unsigned int nNodes = 0; 1458 unsigned int nNodes = 0;
@@ -1464,22 +1479,28 @@ void ThreadSocketHandler2(void* parg)
1464 // 1479 //
1465 // Service each socket 1480 // Service each socket
1466 // 1481 //
1467 vector<CNode*> vNodesCopy; 1482 for (size_t i = 0; i < vPollNodes.size(); i++)
1468 CRITICAL_BLOCK(cs_vNodes)
1469 vNodesCopy = vNodes;
1470 foreach(CNode* pnode, vNodesCopy)
1471 { 1483 {
1472 CheckForShutdown(0); 1484 CheckForShutdown(0);
1473 SOCKET hSocket = pnode->hSocket; 1485 NodePollResult& poll = vPollNodes[i];
1474 // Forgotten above because it was already invalid. FD_ISSET on it 1486 CNode* pnode = poll.pnode;
1475 // is meaningless and recv()/send() would be operating on -1. 1487 SOCKET hSocket = poll.hSocket;
1476 if (hSocket == INVALID_SOCKET) 1488
1489 if (hSocket == INVALID_SOCKET || pnode->hSocket != hSocket)
1477 continue; 1490 continue;
1478 1491
1492 if (poll.fInvalid)
1493 {
1494 LogPrint("net", "poll reported invalid socket for %s\n",
1495 pnode->addr.ToString().c_str());
1496 pnode->fDisconnect = true;
1497 continue;
1498 }
1499
1479 // 1500 //
1480 // Receive 1501 // Receive
1481 // 1502 //
1482 if (FD_ISSET(hSocket, &fdsetRecv)) 1503 if (poll.fRecv)
1483 { 1504 {
1484 TRY_CRITICAL_BLOCK(pnode->cs_vRecv) 1505 TRY_CRITICAL_BLOCK(pnode->cs_vRecv)
1485 { 1506 {
@@ -1519,7 +1540,7 @@ void ThreadSocketHandler2(void* parg)
1519 // 1540 //
1520 // Send 1541 // Send
1521 // 1542 //
1522 if (FD_ISSET(hSocket, &fdsetSend)) 1543 if (poll.fSend)
1523 { 1544 {
1524 TRY_CRITICAL_BLOCK(pnode->cs_vSend) 1545 TRY_CRITICAL_BLOCK(pnode->cs_vSend)
1525 { 1546 {