upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/main.cpp
diff options
context:
space:
mode:
authorbitflash-maintainer <noreply@bitflash.invalid>2026-08-13 14:54:17 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-08-19 22:48:27 +0000
commit5490d6682be52a3de5388086b01fc84bea195d61 (patch)
treebb4bd4ff6ee2e18fc12f7c0ac77e966578c574df /src/main.cpp
parentb126b2a678109621d6a8038f5a63bf1e8fb98813 (diff)
net: do not consume a header until the body is there, and cap what one can claim
Upstream #109's message-stall rework, hand-merged so it keeps the progress timing this tree already had. The old path consumed the header, found the payload short, inserted the header back at the front of vRecv, then Sleep(100) inside the shared message thread. A peer that announces a large payload and drips bytes made every pass pay an O(n) insert and a 100 ms stall -- one peer slowing the thread that serves all of them. Now the header is read from a copy and only skipped once the whole body has arrived, so there is nothing to put back and nothing to sleep for. Kept OURS, not upstream's, on the timeout: upstream restarts its clock only when the (size, command) pair changes, which drops a peer sending one big message slowly but honestly. This times the STALL -- the clock restarts on any progress -- which is the distinction that matters on a slow link. Also from #109: a header could announce 256 MB while serialize.h refuses anything over MAX_SIZE (32 MB), so the check admitted an allocation the parser would reject anyway and burned a peer slot for two minutes doing it. MAX_PROTOCOL_MESSAGE_SIZE ties the two together. 8x less a peer can make us hold, and this node takes public Stratum. And #103's other half: ~CNode closed its socket with a raw closesocket(), invisible to the accounting, and left hSocket set afterwards -- which is how a descriptor number that now belongs to somebody else gets closed a second time. (cherry picked from commit 2f25e2e07985e9bc732cadfbb3c9ac233895a814)
Diffstat (limited to 'src/main.cpp')
-rw-r--r--src/main.cpp82
1 files changed, 60 insertions, 22 deletions
diff --git a/src/main.cpp b/src/main.cpp
index 37a15c6..11eb11d 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -2827,11 +2827,13 @@ bool ProcessMessages(CNode* pfrom)
2827 if (LogAcceptsCategory("net")) printf("\n\nPROCESSMESSAGE SKIPPED %d BYTES\n\n", (int)(pstart - vRecv.begin())); 2827 if (LogAcceptsCategory("net")) printf("\n\nPROCESSMESSAGE SKIPPED %d BYTES\n\n", (int)(pstart - vRecv.begin()));
2828 vRecv.erase(vRecv.begin(), pstart); 2828 vRecv.erase(vRecv.begin(), pstart);
2829 2829
2830 // Read the header from a copy first. The old path consumed the header, 2830 // Read the header from a copy first, and do not consume it until the
2831 // noticed the payload was incomplete, inserted the header back at the 2831 // whole body is here. The old path consumed the header, noticed the
2832 // front of the receive buffer, then slept inside the shared message 2832 // payload was incomplete, inserted the header back at the front of the
2833 // thread. A peer could announce a large payload and drip bytes forever, 2833 // receive buffer, then slept inside the shared message thread. A peer
2834 // making every pass pay for an O(n) insert and a 100 ms stall. 2834 // could announce a large payload and drip bytes forever, making every
2835 // pass pay for an O(n) insert and a 100 ms stall -- a cheap way for one
2836 // peer to slow the thread that serves all of them. (upstream #109)
2835 CMessageHeader hdr; 2837 CMessageHeader hdr;
2836 CDataStream vHeader(vRecv.begin(), vRecv.begin() + sizeof(CMessageHeader), 2838 CDataStream vHeader(vRecv.begin(), vRecv.begin() + sizeof(CMessageHeader),
2837 vRecv.nType, vRecv.nVersion); 2839 vRecv.nType, vRecv.nVersion);
@@ -2841,7 +2843,10 @@ bool ProcessMessages(CNode* pfrom)
2841 if (LogAcceptsCategory("net")) printf("\n\nPROCESSMESSAGE: ERRORS IN HEADER %s\n\n\n", hdr.GetCommand().c_str()); 2843 if (LogAcceptsCategory("net")) printf("\n\nPROCESSMESSAGE: ERRORS IN HEADER %s\n\n\n", hdr.GetCommand().c_str());
2842 if (hdr.nMessageSize > MAX_PROTOCOL_MESSAGE_SIZE) 2844 if (hdr.nMessageSize > MAX_PROTOCOL_MESSAGE_SIZE)
2843 pfrom->fDisconnect = true; 2845 pfrom->fDisconnect = true;
2844 pfrom->nIncompleteMessageStart = 0; 2846 pfrom->nMsgWaitSince = 0;
2847 pfrom->nMsgWaitSize = 0;
2848 // The header was read from a copy, so it is still in vRecv. Skip it
2849 // explicitly or this loop never advances.
2845 vRecv.ignore(sizeof(CMessageHeader)); 2850 vRecv.ignore(sizeof(CMessageHeader));
2846 continue; 2851 continue;
2847 } 2852 }
@@ -2852,30 +2857,63 @@ bool ProcessMessages(CNode* pfrom)
2852 unsigned int nPayloadAvailable = vRecv.size() - sizeof(CMessageHeader); 2857 unsigned int nPayloadAvailable = vRecv.size() - sizeof(CMessageHeader);
2853 if (nMessageSize > nPayloadAvailable) 2858 if (nMessageSize > nPayloadAvailable)
2854 { 2859 {
2855 if (pfrom->nIncompleteMessageStart == 0 || 2860 // Wait for the rest of the message. The header stays in vRecv.
2856 pfrom->nIncompleteMessageSize != nMessageSize || 2861 //
2857 pfrom->strIncompleteMessageCommand != strCommand) 2862 // This is the mechanism the original note asked for: "need a
2863 // mechanism to give up waiting for overlong message size error".
2864 // Without one the wait is unbounded, and unbounded here means
2865 // permanent. The header stays at the front of vRecv and the loop
2866 // breaks; if the body never completes, every later pass re-reads
2867 // that same header and breaks in the same place. The peer is never
2868 // parsed again -- no blocks, no inv, not even its version message,
2869 // so it never reports a height either, which is why a wedged node
2870 // has peers and GetPeerMedianHeight() still returns -1.
2871 //
2872 // Nothing rescues it. A header could claim up to 256 MB and still
2873 // pass CMessageHeader::IsValid() -- now capped at
2874 // MAX_PROTOCOL_MESSAGE_SIZE -- and any byte that arrives
2875 // refreshes nLastRecv, so the inactivity timeout that drops a
2876 // silent peer never fires on one that is merely unreadable. The
2877 // socket stays ESTABLISHED and select() keeps offering it.
2878 // Measured on a wedged pool: this branch ran 2014 times in the
2879 // deaf window, against 607 in the healthy window before it.
2880 if (LogAcceptsCategory("net")) printf("MESSAGE-BREAK 2\n");
2881 //
2882 // Time the STALL, not the wait. A big message on a slow link is
2883 // still arriving, and dropping that peer would be a bug of our own
2884 // making; what must be caught is a buffer that has stopped growing
2885 // while we go on expecting more. So the clock restarts whenever
2886 // even one more byte has landed since the last look.
2887 //
2888 // Upstream #109 times this differently: it restarts the clock only
2889 // when the (size, command) pair changes, which drops a peer sending
2890 // one big message slowly but honestly. Progress is the better test,
2891 // so that half is kept as it was.
2892 int64 nNow = GetTime();
2893 if (pfrom->nMsgWaitSince == 0 || nPayloadAvailable > pfrom->nMsgWaitSize)
2858 { 2894 {
2859 pfrom->nIncompleteMessageStart = GetTime(); 2895 pfrom->nMsgWaitSince = nNow;
2860 pfrom->nIncompleteMessageSize = nMessageSize; 2896 pfrom->nMsgWaitSize = nPayloadAvailable;
2861 pfrom->strIncompleteMessageCommand = strCommand;
2862 } 2897 }
2863 else if (GetTime() - pfrom->nIncompleteMessageStart > 2898 else if (nNow - pfrom->nMsgWaitSince > BTF_MSG_WAIT_MAX_SECS)
2864 BTF_INCOMPLETE_MESSAGE_TIMEOUT_SECS)
2865 { 2899 {
2866 LogPrint("net", 2900 // Deliberately not behind a debug category. This is the node
2867 "disconnecting %s: incomplete %s message, %u of %u bytes after %d seconds\n", 2901 // going deaf to a peer, which is the thing nobody could see.
2868 pfrom->addr.ToString().c_str(), strCommand.c_str(), 2902 printf("peer %s announced a %u byte '%s' and has sent nothing more "
2869 nPayloadAvailable, nMessageSize, 2903 "for %d s (%d of %u payload bytes) -- dropping, this peer is unreadable\n",
2870 BTF_INCOMPLETE_MESSAGE_TIMEOUT_SECS); 2904 pfrom->addr.ToString().c_str(), nMessageSize,
2905 strCommand.c_str(), (int)(nNow - pfrom->nMsgWaitSince),
2906 (int)nPayloadAvailable, nMessageSize);
2871 pfrom->fDisconnect = true; 2907 pfrom->fDisconnect = true;
2872 } 2908 }
2909 // No re-insert, no Sleep(): the header was never consumed, so there
2910 // is nothing to put back, and sleeping here stalls every peer.
2873 break; 2911 break;
2874 } 2912 }
2875 pfrom->nIncompleteMessageStart = 0;
2876 pfrom->nIncompleteMessageSize = 0;
2877 pfrom->strIncompleteMessageCommand.clear();
2878 vRecv.ignore(sizeof(CMessageHeader)); 2913 vRecv.ignore(sizeof(CMessageHeader));
2914 // The body arrived, so this peer is readable again.
2915 pfrom->nMsgWaitSince = 0;
2916 pfrom->nMsgWaitSize = 0;
2879 2917
2880 // Copy message to its own buffer 2918 // Copy message to its own buffer
2881 CDataStream vMsg(vRecv.begin(), vRecv.begin() + nMessageSize, vRecv.nType, vRecv.nVersion); 2919 CDataStream vMsg(vRecv.begin(), vRecv.begin() + nMessageSize, vRecv.nType, vRecv.nVersion);