diff options
| author | Bitflash <btf01@proton.me> | 2026-07-30 15:41:26 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-07-30 15:41:26 -0300 |
| commit | 10f248876aaa77e840dcb0d9585aba2fde712130 (patch) | |
| tree | 0591b8a78a47345e97720090c231b2a31f7bce49 /src/net.cpp | |
| parent | 88a1e9973e34c972e965fe5dd16cd1f891885bba (diff) | |
net: notice a peer that has gone quiet, and stay audible to peers that check (#55)
A connection whose path dies silently -- NAT drops an idle mapping, a relay
restarts, a route changes -- never delivers FIN. Nothing becomes readable,
no error is raised, and the node simply stops hearing from that peer while
the socket looks perfectly healthy. #51 bounded this for the rendezvous
registration; the connection that comes out of the rendezvous had the same
hole, and so did every ordinary peer.
Stamp nLastRecv and nLastSend on the socket path and act on them:
- nothing at all within 60s of connecting, disconnect. In testing this
fired twice per run against dead .btf descriptors handed out by the
relay, which is where the ghosts have been coming from.
- queued to send for 10 minutes with nothing going out, disconnect.
- nothing received for 30 minutes, disconnect. A healthy peer relays a
block inventory about every two minutes, so this is fifteen intervals
of silence. Bitcoin used ninety minutes against ten-minute blocks.
Disconnecting is the whole cure: reconnection already works, and a peer we
cannot hear is worth what no peer is worth.
A node with nothing to relay would now look dead to a peer applying the
same rule, so send "ping" after ten minutes of having said nothing. It
carries no payload and expects no answer -- arriving is the entire content,
because the receive path has already stamped nLastRecv by the time it is
parsed. Requiring an answer would have read every node predating this
change as dead, since those ignore unknown commands rather than replying.
Verified on the live network: synced 0 to 3790 with five peers and no
spurious disconnects, then again with the interval cut to 20s, which put 54
pings on the wire with no peer dropping us and no exceptions.
Diffstat (limited to 'src/net.cpp')
| -rw-r--r-- | src/net.cpp | 43 |
1 files changed, 42 insertions, 1 deletions
diff --git a/src/net.cpp b/src/net.cpp index 9ee056f..81932d7 100644 --- a/src/net.cpp +++ b/src/net.cpp | |||
| @@ -1092,7 +1092,11 @@ void ThreadSocketHandler2(void* parg) | |||
| 1092 | vRecv.resize(nPos + nBufSize); | 1092 | vRecv.resize(nPos + nBufSize); |
| 1093 | int nBytes = recv(hSocket, &vRecv[nPos], nBufSize, 0); | 1093 | int nBytes = recv(hSocket, &vRecv[nPos], nBufSize, 0); |
| 1094 | vRecv.resize(nPos + max(nBytes, 0)); | 1094 | vRecv.resize(nPos + max(nBytes, 0)); |
| 1095 | if (nBytes == 0) | 1095 | if (nBytes > 0) |
| 1096 | { | ||
| 1097 | pnode->nLastRecv = GetTime(); | ||
| 1098 | } | ||
| 1099 | else if (nBytes == 0) | ||
| 1096 | { | 1100 | { |
| 1097 | // socket closed gracefully | 1101 | // socket closed gracefully |
| 1098 | if (!pnode->fDisconnect) | 1102 | if (!pnode->fDisconnect) |
| @@ -1127,6 +1131,7 @@ void ThreadSocketHandler2(void* parg) | |||
| 1127 | if (nBytes > 0) | 1131 | if (nBytes > 0) |
| 1128 | { | 1132 | { |
| 1129 | vSend.erase(vSend.begin(), vSend.begin() + nBytes); | 1133 | vSend.erase(vSend.begin(), vSend.begin() + nBytes); |
| 1134 | pnode->nLastSend = GetTime(); | ||
| 1130 | } | 1135 | } |
| 1131 | else if (nBytes == 0) | 1136 | else if (nBytes == 0) |
| 1132 | { | 1137 | { |
| @@ -1140,6 +1145,42 @@ void ThreadSocketHandler2(void* parg) | |||
| 1140 | pnode->vSend.clear(); | 1145 | pnode->vSend.clear(); |
| 1141 | } | 1146 | } |
| 1142 | } | 1147 | } |
| 1148 | if (vSend.empty()) | ||
| 1149 | pnode->nLastSendEmpty = GetTime(); | ||
| 1150 | } | ||
| 1151 | } | ||
| 1152 | |||
| 1153 | // | ||
| 1154 | // Inactivity | ||
| 1155 | // | ||
| 1156 | // Dropping the connection is the whole cure: everything above this | ||
| 1157 | // layer already knows how to reconnect, and a peer we cannot hear | ||
| 1158 | // is worth exactly as much as no peer at all. Give a new | ||
| 1159 | // connection a grace period first, or we would cut off peers that | ||
| 1160 | // are still completing the rendezvous handshake. | ||
| 1161 | // | ||
| 1162 | if (GetTime() - pnode->nTimeConnected > BTF_HANDSHAKE_GRACE_SECS) | ||
| 1163 | { | ||
| 1164 | if (pnode->nLastRecv == 0 || pnode->nLastSend == 0) | ||
| 1165 | { | ||
| 1166 | LogPrint("net", "socket no message in first %d seconds, recv=%d send=%d\n", | ||
| 1167 | BTF_HANDSHAKE_GRACE_SECS, pnode->nLastRecv != 0, pnode->nLastSend != 0); | ||
| 1168 | pnode->fDisconnect = true; | ||
| 1169 | } | ||
| 1170 | else if (GetTime() - pnode->nLastSend > BTF_SEND_STALL_SECS && | ||
| 1171 | GetTime() - pnode->nLastSendEmpty > BTF_SEND_STALL_SECS) | ||
| 1172 | { | ||
| 1173 | // We have had something queued to send for this long and | ||
| 1174 | // none of it has gone out: the socket accepts no more. | ||
| 1175 | LogPrint("net", "socket not sending\n"); | ||
| 1176 | pnode->fDisconnect = true; | ||
| 1177 | } | ||
| 1178 | else if (GetTime() - pnode->nLastRecv > BTF_RECV_TIMEOUT_SECS) | ||
| 1179 | { | ||
| 1180 | // The deaf case. Nothing has arrived for many block | ||
| 1181 | // intervals while the socket still looks perfectly fine. | ||
| 1182 | LogPrint("net", "socket inactivity timeout\n"); | ||
| 1183 | pnode->fDisconnect = true; | ||
| 1143 | } | 1184 | } |
| 1144 | } | 1185 | } |
| 1145 | } | 1186 | } |