upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBitflash <btf01@proton.me>2026-07-31 17:40:58 -0300
committerGitHub <noreply@github.com>2026-07-31 17:40:58 -0300
commitd5bcd347cd8d156e3a6d4526fd2470741194a64a (patch)
tree41e0820126fb7a0f1daebc8130054c64a00b9faf
parent795e51612bd5b17dbbf8f0cbea64ee4c874b5a33 (diff)
net: a node could only ever watch 63 peers (#78)
-rw-r--r--bitflash-node-1.2.10-x86_64bin0 -> 2602056 bytes
-rw-r--r--bitflash-node-1.2.9-x86_64bin0 -> 2593864 bytes
-rw-r--r--src/makefile.mingw7
-rw-r--r--src/net.cpp96
-rw-r--r--src/net.h26
5 files changed, 120 insertions, 9 deletions
diff --git a/bitflash-node-1.2.10-x86_64 b/bitflash-node-1.2.10-x86_64
new file mode 100644
index 0000000..a279608
--- /dev/null
+++ b/bitflash-node-1.2.10-x86_64
Binary files differ
diff --git a/bitflash-node-1.2.9-x86_64 b/bitflash-node-1.2.9-x86_64
new file mode 100644
index 0000000..561bc22
--- /dev/null
+++ b/bitflash-node-1.2.9-x86_64
Binary files differ
diff --git a/src/makefile.mingw b/src/makefile.mingw
index 01f7702..ae533ef 100644
--- a/src/makefile.mingw
+++ b/src/makefile.mingw
@@ -14,8 +14,13 @@ WARNFLAGS = -Wformat -Werror=format \
14 -Wno-deprecated-declarations -Wno-write-strings \ 14 -Wno-deprecated-declarations -Wno-write-strings \
15 -Wno-narrowing -Wno-unused-result 15 -Wno-narrowing -Wno-unused-result
16 16
17# FD_SETSIZE has to be defined before winsock2.h and it has to be the same
18# number in every translation unit -- fd_set carries an array of that length,
19# so disagreeing about it means disagreeing about sizeof(fd_set). That is why
20# it lives here as a -D and not as a #define in a header. Windows defaults to
21# 64, which capped this node at 63 watchable peers no matter how many it had.
17CXXFLAGS = -std=gnu++14 -O2 $(WARNFLAGS) -fpermissive -pthread \ 22CXXFLAGS = -std=gnu++14 -O2 $(WARNFLAGS) -fpermissive -pthread \
18 -DWIN32 -DNOPCH \ 23 -DWIN32 -DNOPCH -DFD_SETSIZE=1024 \
19 -DSECP256K1_STATIC \ 24 -DSECP256K1_STATIC \
20 -I$(DEPS)/include -I$(RANDOMX)/src \ 25 -I$(DEPS)/include -I$(RANDOMX)/src \
21 -I$(DEPS)/include/nlohmann \ 26 -I$(DEPS)/include/nlohmann \
diff --git a/src/net.cpp b/src/net.cpp
index 37c081f..5ef8316 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -954,8 +954,27 @@ void ThreadSocketHandler2(void* parg)
954 vector<CNode*> vNodesCopy = vNodes; 954 vector<CNode*> vNodesCopy = vNodes;
955 foreach(CNode* pnode, vNodesCopy) 955 foreach(CNode* pnode, vNodesCopy)
956 { 956 {
957 if (pnode->ReadyToDisconnect() && pnode->vRecv.empty() && pnode->vSend.empty()) 957 // Wait for the buffers to drain before letting a node go, so a
958 // last message still gets out -- but not forever. vSend never
959 // drains through a socket whose far end is gone, and a node
960 // stuck that way stayed in vNodes for the life of the process,
961 // holding its socket and counting as a peer.
962 if (!pnode->ReadyToDisconnect())
963 pnode->nDisconnectSince = 0;
964 else if (pnode->nDisconnectSince == 0)
965 pnode->nDisconnectSince = GetTime();
966
967 bool fDrained = pnode->vRecv.empty() && pnode->vSend.empty();
968 bool fStuck = pnode->nDisconnectSince != 0 &&
969 GetTime() - pnode->nDisconnectSince > DISCONNECT_DRAIN_SECS;
970
971 if (pnode->ReadyToDisconnect() && (fDrained || fStuck))
958 { 972 {
973 if (!fDrained)
974 printf("dropping stuck node %s after %d s with %d bytes unsent\n",
975 pnode->addr.ToString().c_str(),
976 (int)(GetTime() - pnode->nDisconnectSince),
977 (int)pnode->vSend.size());
959 // remove from vNodes 978 // remove from vNodes
960 vNodes.erase(remove(vNodes.begin(), vNodes.end(), pnode), vNodes.end()); 979 vNodes.erase(remove(vNodes.begin(), vNodes.end(), pnode), vNodes.end());
961 pnode->Disconnect(); 980 pnode->Disconnect();
@@ -1010,16 +1029,35 @@ void ThreadSocketHandler2(void* parg)
1010 SOCKET hSocketMax = 0; 1029 SOCKET hSocketMax = 0;
1011 FD_SET(hListenSocket, &fdsetRecv); 1030 FD_SET(hListenSocket, &fdsetRecv);
1012 hSocketMax = max(hSocketMax, hListenSocket); 1031 hSocketMax = max(hSocketMax, hListenSocket);
1032 // FD_SET drops silently once the set is full, so count what goes in
1033 // and say so. A peer that never makes it into the set is never read,
1034 // and that is indistinguishable from a peer with nothing to say --
1035 // the failure this whole path used to hide.
1036 unsigned int nWatched = 1; // the listen socket
1013 CRITICAL_BLOCK(cs_vNodes) 1037 CRITICAL_BLOCK(cs_vNodes)
1014 { 1038 {
1015 foreach(CNode* pnode, vNodes) 1039 foreach(CNode* pnode, vNodes)
1016 { 1040 {
1041 if (nWatched >= FD_SETSIZE)
1042 break;
1017 FD_SET(pnode->hSocket, &fdsetRecv); 1043 FD_SET(pnode->hSocket, &fdsetRecv);
1044 nWatched++;
1018 hSocketMax = max(hSocketMax, pnode->hSocket); 1045 hSocketMax = max(hSocketMax, pnode->hSocket);
1019 TRY_CRITICAL_BLOCK(pnode->cs_vSend) 1046 TRY_CRITICAL_BLOCK(pnode->cs_vSend)
1020 if (!pnode->vSend.empty()) 1047 if (!pnode->vSend.empty())
1021 FD_SET(pnode->hSocket, &fdsetSend); 1048 FD_SET(pnode->hSocket, &fdsetSend);
1022 } 1049 }
1050 if (vNodes.size() + 1 > (size_t)FD_SETSIZE)
1051 {
1052 static int64 nLastWarned = 0;
1053 if (GetTime() - nLastWarned > 60)
1054 {
1055 nLastWarned = GetTime();
1056 printf("WARNING: %d peers but select() can only watch %d -- %d are not being read\n",
1057 (int)vNodes.size(), (int)FD_SETSIZE - 1,
1058 (int)vNodes.size() - (int)FD_SETSIZE + 1);
1059 }
1060 }
1023 } 1061 }
1024 1062
1025 vfThreadRunning[0] = false; 1063 vfThreadRunning[0] = false;
@@ -1028,12 +1066,37 @@ void ThreadSocketHandler2(void* parg)
1028 CheckForShutdown(0); 1066 CheckForShutdown(0);
1029 if (nSelect == SOCKET_ERROR) 1067 if (nSelect == SOCKET_ERROR)
1030 { 1068 {
1069 // This used to answer a failed select() by marking every
1070 // descriptor from 0 to hSocketMax ready, which is code written for
1071 // POSIX, where a descriptor is a small integer. A Windows SOCKET is
1072 // a kernel handle in the thousands, so the loop iterated over
1073 // numbers that are not sockets and told the rest of the function
1074 // that every peer had data. One bad handle in the set makes
1075 // select() fail on every pass, so a single closed socket put the
1076 // whole thread into that state permanently.
1077 //
1078 // Find the bad handle instead and let it be dropped.
1031 int nErr = WSAGetLastError(); 1079 int nErr = WSAGetLastError();
1032 LogPrint("net", "select failed: %d\n", nErr); 1080 LogPrint("net", "select failed: %d\n", nErr);
1033 for (int i = 0; i <= hSocketMax; i++) 1081 FD_ZERO(&fdsetRecv);
1082 FD_ZERO(&fdsetSend);
1083 CRITICAL_BLOCK(cs_vNodes)
1034 { 1084 {
1035 FD_SET(i, &fdsetRecv); 1085 foreach(CNode* pnode, vNodes)
1036 FD_SET(i, &fdsetSend); 1086 {
1087 int nType = 0;
1088#ifdef _WIN32
1089 int nTypeLen = sizeof(nType);
1090#else
1091 socklen_t nTypeLen = sizeof(nType);
1092#endif
1093 if (getsockopt(pnode->hSocket, SOL_SOCKET, SO_TYPE, (char*)&nType, &nTypeLen) != 0)
1094 {
1095 printf("dropping node %s: its socket is no longer valid\n",
1096 pnode->addr.ToString().c_str());
1097 pnode->fDisconnect = true;
1098 }
1099 }
1037 } 1100 }
1038 Sleep(timeout.tv_usec/1000); 1101 Sleep(timeout.tv_usec/1000);
1039 } 1102 }
@@ -1068,11 +1131,28 @@ void ThreadSocketHandler2(void* parg)
1068 } 1131 }
1069 else 1132 else
1070 { 1133 {
1071 LogPrint("net", "accepted connection from %s\n", addr.ToString().c_str()); 1134 // Refuse rather than accept a connection this node cannot
1072 CNode* pnode = new CNode(hSocket, addr, true); 1135 // watch. Without this the listen socket kept taking peers --
1073 pnode->AddRef(); 1136 // it is always in the select set -- while everything past the
1137 // limit sat open and unread, which fed on itself: the deafer
1138 // the node got, the more connections it collected.
1139 unsigned int nNodes = 0;
1074 CRITICAL_BLOCK(cs_vNodes) 1140 CRITICAL_BLOCK(cs_vNodes)
1075 vNodes.push_back(pnode); 1141 nNodes = (unsigned int)vNodes.size();
1142 if (nNodes >= MAX_CONNECTIONS)
1143 {
1144 LogPrint("net", "refusing connection from %s, already at %u\n",
1145 addr.ToString().c_str(), nNodes);
1146 closesocket(hSocket);
1147 }
1148 else
1149 {
1150 LogPrint("net", "accepted connection from %s\n", addr.ToString().c_str());
1151 CNode* pnode = new CNode(hSocket, addr, true);
1152 pnode->AddRef();
1153 CRITICAL_BLOCK(cs_vNodes)
1154 vNodes.push_back(pnode);
1155 }
1076 } 1156 }
1077 } 1157 }
1078 1158
diff --git a/src/net.h b/src/net.h
index 5e828c3..01f314d 100644
--- a/src/net.h
+++ b/src/net.h
@@ -67,6 +67,30 @@ static const int BTF_RECV_TIMEOUT_SECS = 30 * 60;
67static const int BTF_SEND_STALL_SECS = 10 * 60; 67static const int BTF_SEND_STALL_SECS = 10 * 60;
68static const int BTF_HANDSHAKE_GRACE_SECS = 60; 68static const int BTF_HANDSHAKE_GRACE_SECS = 60;
69 69
70// Ceiling on simultaneous connections.
71//
72// ThreadSocketHandler watches every peer through one select(), and select()
73// cannot watch more descriptors than FD_SETSIZE. On Windows FD_SET simply
74// stops adding once the set is full -- no error, no log -- so every socket
75// past the limit stays open and is never read again. Nothing capped vNodes,
76// so a node accepted connections it had no way to service, and because new
77// entries go on the end of vNodes it was always the newest connection that
78// starved, including the ones dialed to fix connectivity.
79//
80// Measured on two nodes before this ceiling existed: 277 and 90 CNode objects
81// against 93 and 90 live sockets, and a status bar reporting hundreds of
82// peers for a node that was really talking to thirty.
83//
84// 125 is Bitcoin's number and sits far below FD_SETSIZE on both platforms.
85static const unsigned int MAX_CONNECTIONS = 125;
86
87// A node marked for disconnect is normally held until its buffers drain, so
88// a last message still goes out. But vSend cannot drain through a socket
89// whose far end is gone, and that made such a node immortal: it stayed in
90// vNodes, kept its socket open, and kept being counted. Force it out after
91// this long regardless of what is still buffered.
92static const int64 DISCONNECT_DRAIN_SECS = 60;
93
70// Send a ping after this long with nothing to say, so a peer running the 94// Send a ping after this long with nothing to say, so a peer running the
71// inactivity check above does not mistake a quiet node for a dead one. Must 95// inactivity check above does not mistake a quiet node for a dead one. Must
72// stay well under BTF_RECV_TIMEOUT_SECS. 96// stay well under BTF_RECV_TIMEOUT_SECS.
@@ -520,6 +544,7 @@ protected:
520 int nRefCount; 544 int nRefCount;
521public: 545public:
522 int64 nReleaseTime; 546 int64 nReleaseTime;
547 int64 nDisconnectSince;
523 map<uint256, CRequestTracker> mapRequests; 548 map<uint256, CRequestTracker> mapRequests;
524 CCriticalSection cs_mapRequests; 549 CCriticalSection cs_mapRequests;
525 550
@@ -572,6 +597,7 @@ public:
572 fDisconnect = false; 597 fDisconnect = false;
573 nRefCount = 0; 598 nRefCount = 0;
574 nReleaseTime = 0; 599 nReleaseTime = 0;
600 nDisconnectSince = 0;
575 nLastPexRecv = 0; 601 nLastPexRecv = 0;
576 pindexLastGetBlocksBegin = NULL; 602 pindexLastGetBlocksBegin = NULL;
577 hashLastGetBlocksEnd = 0; 603 hashLastGetBlocksEnd = 0;