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-09-02 08:29:44 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-09-02 08:29:44 +0000
commit2c0410b20893383ec6f1c4c8cd5134e420513e80 (patch)
treecb0716e5770e67a429de6f9f8bdf7c0009f980c9 /src/main.cpp
parent73e77c3bc259a4c8e0a4cc5c376f1a9545439bdd (diff)
net: fail over a stalled block fetch to another peer
The 15s retry spacing only helps when several peers announce the block: the queued retries are created by inv handling, so a block announced by a single peer that then goes quiet was re-requested from nobody, and the tip stayed frozen until somebody happened to re-announce (#33). Track every block getdata in mapBlockFetch, erase the entry when the body arrives, and let SendMessages re-issue a request that has gone unanswered for BLOCK_FETCH_RETRY_SECS to the next peer that has not had a turn this round, restarting the round once every connected peer has. Client peers are skipped -- they declared they cannot serve bodies. Tracking is capped at 8 in-flight blocks so initial download, where hundreds are legitimately outstanding to one peer, is not duplicated to everybody else, and an entry is dropped after 10 minutes so a hash that was announced and never existed cannot be chased forever. The failover log line is deliberately not behind a debug category: it firing means a peer announces blocks it does not serve, which is the pathology being diagnosed.
Diffstat (limited to 'src/main.cpp')
-rw-r--r--src/main.cpp105
1 files changed, 105 insertions, 0 deletions
diff --git a/src/main.cpp b/src/main.cpp
index 7b3e555..d9fe713 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -64,6 +64,31 @@ CBlockIndex* pindexBest = NULL;
64map<uint256, CBlock*> mapOrphanBlocks; 64map<uint256, CBlock*> mapOrphanBlocks;
65multimap<uint256, CBlock*> mapOrphanBlocksByPrev; 65multimap<uint256, CBlock*> mapOrphanBlocksByPrev;
66 66
67// Block bodies we have sent a getdata for and not yet received, so the
68// failover pass in SendMessages can tell "requested and being served" from
69// "requested and gone quiet" and re-issue the request to a different peer
70// (#33). Keyed by block hash; setAsked is the peers already tried since the
71// round last restarted, so the retry rotates instead of hammering the
72// announcer that is not serving. Guarded by cs_main, which both the message
73// handlers and SendMessages already hold.
74//
75// Capped small on purpose. At the tip there is one block in flight; during
76// initial download there are legitimately hundreds outstanding to one peer,
77// and rotating those would duplicate the whole download to every other peer.
78// Anything past the cap is simply not tracked and behaves as before.
79struct CBlockFetch
80{
81 int64 nTimeFirstAsked; // when the first getdata for this block went out
82 int64 nTimeAsked; // when the latest getdata went out
83 set<CAddress> setAsked; // peers tried since the round last restarted
84};
85map<uint256, CBlockFetch> mapBlockFetch;
86static const int BLOCK_FETCH_TRACK_MAX = 8;
87// A block nobody has served for this long is not a fetch problem: the hash may
88// have been announced and never existed. Drop the entry rather than getdata
89// forever; a re-announcement recreates it.
90static const int BLOCK_FETCH_GIVEUP_SECS = 10 * 60;
91
67map<uint256, CDataStream*> mapOrphanTransactions; 92map<uint256, CDataStream*> mapOrphanTransactions;
68multimap<uint256, CDataStream*> mapOrphanTransactionsByPrev; 93multimap<uint256, CDataStream*> mapOrphanTransactionsByPrev;
69 94
@@ -3268,6 +3293,12 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
3268 CInv inv(MSG_BLOCK, pblock->GetHash()); 3293 CInv inv(MSG_BLOCK, pblock->GetHash());
3269 pfrom->AddInventoryKnown(inv); 3294 pfrom->AddInventoryKnown(inv);
3270 3295
3296 // The body is here, so the fetch is over whatever validation says
3297 // next -- unlike the mapAlreadyAskedFor erase below, which stays
3298 // conditional on acceptance so a peer feeding us garbage does not
3299 // cancel the scheduled retries for the real thing.
3300 mapBlockFetch.erase(inv.hash);
3301
3271 if (ProcessBlock(pfrom, pblock.release())) 3302 if (ProcessBlock(pfrom, pblock.release()))
3272 { 3303 {
3273 mapAlreadyAskedFor.erase(inv); 3304 mapAlreadyAskedFor.erase(inv);
@@ -3396,12 +3427,86 @@ bool SendMessages(CNode* pto)
3396 const CInv& inv = (*pto->mapAskFor.begin()).second; 3427 const CInv& inv = (*pto->mapAskFor.begin()).second;
3397 if (LogAcceptsCategory("net")) printf("sending getdata: %s\n", inv.ToString().c_str()); 3428 if (LogAcceptsCategory("net")) printf("sending getdata: %s\n", inv.ToString().c_str());
3398 if (!AlreadyHave(txdb, inv)) 3429 if (!AlreadyHave(txdb, inv))
3430 {
3399 vAskFor.push_back(inv); 3431 vAskFor.push_back(inv);
3432 // Blocks get their delivery tracked so the failover pass
3433 // below can chase the ones that go quiet. See mapBlockFetch
3434 // for why anything past the cap is left untracked.
3435 if (inv.type == MSG_BLOCK &&
3436 (mapBlockFetch.count(inv.hash) || mapBlockFetch.size() < BLOCK_FETCH_TRACK_MAX))
3437 {
3438 CBlockFetch& fetch = mapBlockFetch[inv.hash];
3439 if (fetch.nTimeFirstAsked == 0)
3440 fetch.nTimeFirstAsked = GetTime();
3441 fetch.nTimeAsked = GetTime();
3442 fetch.setAsked.insert(pto->addr);
3443 }
3444 }
3400 pto->mapAskFor.erase(pto->mapAskFor.begin()); 3445 pto->mapAskFor.erase(pto->mapAskFor.begin());
3401 } 3446 }
3402 if (!vAskFor.empty()) 3447 if (!vAskFor.empty())
3403 pto->PushMessage("getdata", vAskFor); 3448 pto->PushMessage("getdata", vAskFor);
3404 3449
3450
3451 //
3452 // Message: getdata (block fetch failover)
3453 //
3454 // A requested block that has not arrived in BLOCK_FETCH_RETRY_SECS is
3455 // not still in transit -- a body is at most 1 MB and somebody
3456 // demonstrably has it -- so ask somebody else instead of waiting on
3457 // whoever was asked first (#33). Each peer the message handler visits
3458 // takes at most one turn per block per round; once every connected
3459 // peer has had its turn and a further interval passes with nobody
3460 // new to ask, the round restarts. A peer that declared itself a
3461 // client cannot serve block bodies and is no use as a target.
3462 if (!pto->fClient)
3463 {
3464 vector<CInv> vFailover;
3465 int64 nNowSecs = GetTime();
3466 for (map<uint256, CBlockFetch>::iterator mi = mapBlockFetch.begin(); mi != mapBlockFetch.end();)
3467 {
3468 CBlockFetch& fetch = (*mi).second;
3469 CInv inv(MSG_BLOCK, (*mi).first);
3470 if (AlreadyHave(txdb, inv))
3471 {
3472 // Arrived by some other road -- our own miner, an orphan
3473 // parked in mapOrphanBlocks. Nothing left to chase.
3474 mapBlockFetch.erase(mi++);
3475 continue;
3476 }
3477 if (nNowSecs - fetch.nTimeFirstAsked > BLOCK_FETCH_GIVEUP_SECS)
3478 {
3479 printf("block fetch: giving up on %s -- no peer served it in %d s\n",
3480 inv.ToString().c_str(), (int)(nNowSecs - fetch.nTimeFirstAsked));
3481 mapBlockFetch.erase(mi++);
3482 continue;
3483 }
3484 if (nNowSecs - fetch.nTimeAsked >= BLOCK_FETCH_RETRY_SECS)
3485 {
3486 // The handler loop visits every peer within ~100ms, so a
3487 // request stale by two full intervals means nobody outside
3488 // setAsked was there to ask. Start the round over.
3489 if (nNowSecs - fetch.nTimeAsked >= 2 * BLOCK_FETCH_RETRY_SECS)
3490 fetch.setAsked.clear();
3491 if (!fetch.setAsked.count(pto->addr))
3492 {
3493 // Deliberately not behind a debug category: this line
3494 // firing is a peer announcing blocks it does not
3495 // serve, which is the pathology of #33.
3496 printf("block fetch failover: %s unanswered for %d s, re-requesting from %s\n",
3497 inv.ToString().c_str(), (int)(nNowSecs - fetch.nTimeAsked),
3498 pto->addr.ToString().c_str());
3499 vFailover.push_back(inv);
3500 fetch.nTimeAsked = nNowSecs;
3501 fetch.setAsked.insert(pto->addr);
3502 }
3503 }
3504 ++mi;
3505 }
3506 if (!vFailover.empty())
3507 pto->PushMessage("getdata", vFailover);
3508 }
3509
3405 } 3510 }
3406 return true; 3511 return true;
3407} 3512}