upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbitflash-maintainer <noreply@bitflash.invalid>2026-09-02 16:10:24 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-09-02 16:10:24 +0000
commitcd6edfc2c234db71d9b68f96702ad08a56cc3127 (patch)
tree99e816a74560d9d59b13c7c1ff3ac86dd24e0b43
parenta215505c9e6b63883fa6058a29cc5dc7b22acd94 (diff)
net: only use the 15s block retry spacing at the tip
nRequestTime lives in mapAlreadyAskedFor, which is global to the inv, so the spacing AskFor applies is not per-peer pacing: it is what strings the getdatas for one block across its announcers. Shortening it to 15s was right at the tip and wrong everywhere else, and the eight-entry cap that made the failover table safe for catch-up does not cover this at all -- it is a different mechanism. At two minutes a second announcer's queued request almost always fired after the block had arrived, so AlreadyHave dropped it. At fifteen seconds, with a peer working through a 500-deep queue and block N legitimately minutes away, the second announcer is asked at +15s, the third at +30s, and the bulk download arrives once per announcer -- up to eight times over, with nothing bounding it. So the fast spacing is now conditional on tracking the tip, measured off the age of our own best block. Not off a peer-reported height: nStartingHeight is optional in the version message and every node released so far sends the short form, so it is -1 for essentially every peer, and where it does get filled in later it comes from the height of a block that peer served us, which during catch-up tracks our own height and would answer "at the tip" for the entire download. Block timestamps are the one clock a peer cannot move, being bounded above by CheckBlock and below by AcceptBlock's median-time rule. Thirty block intervals is the threshold: far longer than any stall worth failing over from, far shorter than catching up. Failover at the tip does not rest on this in any case -- mapBlockFetch re-issues a stalled getdata to the next peer on its own 15s timer, and that is what actually answers #33.
-rw-r--r--src/main.cpp23
-rw-r--r--src/net.h30
2 files changed, 50 insertions, 3 deletions
diff --git a/src/main.cpp b/src/main.cpp
index 9ab1f44..be89416 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -101,6 +101,29 @@ static const int BLOCK_FETCH_TRACK_MAX = 8;
101// forever; a re-announcement recreates it. 101// forever; a re-announcement recreates it.
102static const int BLOCK_FETCH_GIVEUP_SECS = 10 * 60; 102static const int BLOCK_FETCH_GIVEUP_SECS = 10 * 60;
103 103
104// How stale our own best block may be before we stop calling this the tip.
105// Blocks target two minutes, so this is thirty intervals: far longer than any
106// stall worth failing over from -- the one in #33 ran sixteen minutes -- and
107// far shorter than catching up, which starts hours or days back.
108static const int BLOCK_FETCH_TIP_AGE_SECS = 60 * 60;
109
110// Whether this node is tracking the tip rather than still catching up.
111//
112// Measured off the age of our own best block, not off any height a peer told
113// us. nStartingHeight is optional in the version message and every node
114// released so far sends the short form, so it is -1 for essentially every
115// peer; and where it is filled in later it is filled in from the height of a
116// block that peer served us, which during catch-up tracks our own height and
117// would report "at the tip" for the whole download. Block timestamps are the
118// one clock here that peers cannot move: CheckBlock refuses anything more than
119// two hours ahead and AcceptBlock refuses anything at or before the previous
120// median, so the only way to make this read "at the tip" falsely is to mine,
121// and all that buys is the 15s spacing this code had unconditionally anyway.
122bool IsBlockFetchNearTip()
123{
124 return pindexBest && (int64)pindexBest->nTime > GetTime() - BLOCK_FETCH_TIP_AGE_SECS;
125}
126
104// Drop one entry so a hash announced by addrNew can be tracked. 127// Drop one entry so a hash announced by addrNew can be tracked.
105// 128//
106// Refusing the insertion instead was a remote off-switch for the whole 129// Refusing the insertion instead was a remote off-switch for the whole
diff --git a/src/net.h b/src/net.h
index 2e4d8cb..64f061f 100644
--- a/src/net.h
+++ b/src/net.h
@@ -23,6 +23,12 @@ enum
23 23
24 24
25 25
26// Defined in main.cpp. True while this node is tracking the tip rather than
27// still catching up, measured off the age of our own best block -- see there
28// for why that and not a peer-reported height. Declared here because net.h is
29// parsed before main.h and CNode::AskFor needs it.
30bool IsBlockFetchNearTip();
31
26bool GetMyExternalIP(unsigned int& ipRet); 32bool GetMyExternalIP(unsigned int& ipRet);
27CNode* ConnectNodeBtf(const string& strBtfAddr); 33CNode* ConnectNodeBtf(const string& strBtfAddr);
28CNode* ConnectNodeBtfResolved(const string& strBtfAddr, const string& strMeeting, 34CNode* ConnectNodeBtfResolved(const string& strBtfAddr, const string& strMeeting,
@@ -810,9 +816,27 @@ public:
810 // for a new block it was the stall of issue #33: nine peers announce 816 // for a new block it was the stall of issue #33: nine peers announce
811 // the block, the first one asked does not serve it, and the ninth 817 // the block, the first one asked does not serve it, and the ninth
812 // candidate is not even asked until sixteen minutes in, with the pool 818 // candidate is not even asked until sixteen minutes in, with the pool
813 // mining on the stale tip the whole time. Blocks retry on the fetch 819 // mining on the stale tip the whole time.
814 // timescale instead. 820 //
815 int64 nRetryUsecs = (inv.type == MSG_BLOCK ? BLOCK_FETCH_RETRY_SECS : 2 * 60) * 1000000LL; 821 // Blocks retry on the fetch timescale, but only while we are at the
822 // tip. That is where the stall costs something -- the pool keeps
823 // handing out jobs against a dead tip -- and it is also where the
824 // spacing is nearly free: one block is in flight, and its body has
825 // almost always arrived before the second announcer's turn comes
826 // round, so AlreadyHave drops the duplicate before a getdata is sent.
827 //
828 // While catching up neither half holds. A peer working through a
829 // 500-deep queue can be minutes away from block N without anything
830 // being wrong, so at 15s the second announcer is asked at +15s, the
831 // third at +30s, and the bulk download arrives once per announcer --
832 // up to eight times over. Nothing caps that: the eight-entry cap is on
833 // mapBlockFetch, which is a different mechanism from this spacing.
834 // Two minutes is the right number there, and it is what this line did
835 // before. Failover at the tip does not depend on this being 15s in any
836 // case: mapBlockFetch re-issues a stalled getdata to the next peer on
837 // its own timer, which is what actually fixes #33.
838 bool fFast = (inv.type == MSG_BLOCK && IsBlockFetchNearTip());
839 int64 nRetryUsecs = (fFast ? BLOCK_FETCH_RETRY_SECS : 2 * 60) * 1000000LL;
816 nRequestTime = max(nRequestTime + nRetryUsecs, nNow); 840 nRequestTime = max(nRequestTime + nRetryUsecs, nNow);
817 mapAskFor.insert(make_pair(nRequestTime, inv)); 841 mapAskFor.insert(make_pair(nRequestTime, inv));
818 } 842 }