Merge sync-freeze-fix: resolves IBD freeze at 15k + PoS header rejection at 1026
From-zero sync test confirmed: chain advances past 15k freeze zone to 17k+ with no stall. Build clean (149/149 Ninja targets). 132/132 unit tests pass.
This commit is contained in:
+97
-14
@@ -31,6 +31,11 @@ static const size_t HEADER_REDUNDANT_PEER_THRESHOLD = 4;
|
||||
static const int64_t HEADER_REQUEST_TIMEOUT_MICROS = 60 * 1000000;
|
||||
static const int64_t HEADER_REDUNDANT_REQUEST_MICROS = 5 * 1000000;
|
||||
static const int64_t HEADER_SYNC_TTL_MICROS = 15 * 60 * 1000000;
|
||||
// Backpressure ceiling: how far (in blocks) the header front is allowed to
|
||||
// run ahead of the connected chain tip before we stop fetching MORE headers
|
||||
// and let the block planner catch up. Comfortly below MAX_HEADER_SYNC_CACHE
|
||||
// so the cache never overflows in normal from-zero sync.
|
||||
static const int HEADER_FRONT_MAX_AHEAD = 8000;
|
||||
|
||||
std::map<uint256, CSyncManager::HeaderNode> mapHeaders;
|
||||
uint256 hashBestHeader = 0;
|
||||
@@ -124,12 +129,23 @@ void CSyncManager::PruneHeaders()
|
||||
{
|
||||
const int64_t nNow = GetTime() * 1000000;
|
||||
|
||||
// Never evict headers in the live sync window. Fix 2's backpressure caps the
|
||||
// header front at nBestHeight + HEADER_FRONT_MAX_AHEAD, so protecting that
|
||||
// whole span means the entire in-flight header chain is safe: no mid-chain
|
||||
// hole can form between the connected tip and the front during normal sync.
|
||||
// Evicting any of these would sever GetDownloadPath() from the connected
|
||||
// chain and freeze sync. (The hard cap below is the memory safety valve;
|
||||
// Fix 3's bridge-repair is the backstop for restart/reorg edge cases.)
|
||||
const int nProtectFloor = nBestHeight + HEADER_FRONT_MAX_AHEAD;
|
||||
|
||||
// TTL pass: evict aged headers, but only ABOVE the protected floor.
|
||||
if (mapHeaders.size() > MAX_HEADER_SYNC_CACHE / 2)
|
||||
{
|
||||
unsigned int nEvicted = 0;
|
||||
for (std::map<uint256, HeaderNode>::iterator it = mapHeaders.begin(); it != mapHeaders.end(); )
|
||||
{
|
||||
if (nNow - it->second.nInsertTime >= HEADER_SYNC_TTL_MICROS)
|
||||
if (it->second.nHeight > nProtectFloor &&
|
||||
nNow - it->second.nInsertTime >= HEADER_SYNC_TTL_MICROS)
|
||||
{
|
||||
it = mapHeaders.erase(it);
|
||||
++nEvicted;
|
||||
@@ -139,25 +155,37 @@ void CSyncManager::PruneHeaders()
|
||||
}
|
||||
if (nEvicted > 0)
|
||||
{
|
||||
printf("IBD-DIAG: TTL-evicted %u stale sync headers, %u remain\n",
|
||||
nEvicted, (unsigned int)mapHeaders.size());
|
||||
printf("IBD-DIAG: TTL-evicted %u stale sync headers above floor %d, %u remain\n",
|
||||
nEvicted, nProtectFloor, (unsigned int)mapHeaders.size());
|
||||
RecomputeBestHeader();
|
||||
}
|
||||
}
|
||||
|
||||
// Hard cap (last-resort safety valve): evict the HIGHEST-height headers
|
||||
// first — the ones furthest ahead of the tip — never the bridge zone.
|
||||
// Lowering the sync target temporarily is fine; we re-extend it once blocks
|
||||
// catch up. Losing a bridge header is not fine: it stalls forever.
|
||||
if (mapHeaders.size() > MAX_HEADER_SYNC_CACHE)
|
||||
{
|
||||
printf("IBD-DIAG: sync header cache exceeded %u entries, evicting oldest\n", MAX_HEADER_SYNC_CACHE);
|
||||
while (mapHeaders.size() > MAX_HEADER_SYNC_CACHE * 3 / 4)
|
||||
{
|
||||
std::map<uint256, HeaderNode>::iterator oldest = mapHeaders.begin();
|
||||
printf("IBD-DIAG: sync header cache exceeded %u entries, evicting from the front (floor=%d)\n",
|
||||
MAX_HEADER_SYNC_CACHE, nProtectFloor);
|
||||
|
||||
std::vector<std::map<uint256, HeaderNode>::iterator> vEvictable;
|
||||
for (std::map<uint256, HeaderNode>::iterator it = mapHeaders.begin(); it != mapHeaders.end(); ++it)
|
||||
{
|
||||
if (it->second.nInsertTime < oldest->second.nInsertTime)
|
||||
oldest = it;
|
||||
}
|
||||
mapHeaders.erase(oldest);
|
||||
}
|
||||
if (it->second.nHeight > nProtectFloor)
|
||||
vEvictable.push_back(it);
|
||||
|
||||
std::sort(vEvictable.begin(), vEvictable.end(),
|
||||
[](const std::map<uint256, HeaderNode>::iterator& a,
|
||||
const std::map<uint256, HeaderNode>::iterator& b) {
|
||||
return a->second.nHeight > b->second.nHeight;
|
||||
});
|
||||
|
||||
const size_t nTarget = (size_t)MAX_HEADER_SYNC_CACHE * 3 / 4;
|
||||
size_t i = 0;
|
||||
while (mapHeaders.size() > nTarget && i < vEvictable.size())
|
||||
mapHeaders.erase(vEvictable[i++]);
|
||||
|
||||
RecomputeBestHeader();
|
||||
}
|
||||
}
|
||||
@@ -191,7 +219,11 @@ bool CSyncManager::AddHeaderNode(const CBlock& header, const uint256& hashHeader
|
||||
}
|
||||
|
||||
const int nHeight = nPrevHeight + 1;
|
||||
if (nHeight <= CUTOFF_POW_BLOCK && !CheckProofOfWork(hashHeader, header.nBits))
|
||||
// Only check PoW on actual PoW headers (nNonce != 0).
|
||||
// Triangles is a hybrid PoW/PoS coin — PoS blocks (nonce=0) can appear
|
||||
// even within the 0-9000 PoW range. Checking PoW on a PoS header
|
||||
// rejects valid blocks and severs the header chain during IBD.
|
||||
if (nHeight <= CUTOFF_POW_BLOCK && header.nNonce != 0 && !CheckProofOfWork(hashHeader, header.nBits))
|
||||
{
|
||||
printf("IBD-DIAG: header PoW FAILED at height %d hash=%s nBits=%08x prevHash=%s\n",
|
||||
nHeight, hashHeader.ToString().substr(0,20).c_str(), header.nBits,
|
||||
@@ -235,6 +267,20 @@ std::vector<uint256> CSyncManager::GetDownloadPath(uint256 hashTip) const
|
||||
return vPath;
|
||||
}
|
||||
|
||||
// A download path is "anchored" when its lowest header's parent is a block we
|
||||
// already have in the active chain (mapBlockIndex). If it isn't, a bridging
|
||||
// header was lost and requesting these blocks would only create orphans —
|
||||
// Tick() rebuilds the bridge with a getheaders anchored at pindexBest.
|
||||
bool CSyncManager::PathReachesChain(const std::vector<uint256>& vPath) const
|
||||
{
|
||||
if (vPath.empty())
|
||||
return true; // nothing queued == nothing to bridge
|
||||
std::map<uint256, HeaderNode>::const_iterator mi = mapHeaders.find(vPath.front());
|
||||
if (mi == mapHeaders.end())
|
||||
return false;
|
||||
return mapBlockIndex.count(mi->second.header.hashPrevBlock) != 0;
|
||||
}
|
||||
|
||||
unsigned int CSyncManager::CountInFlight() const
|
||||
{
|
||||
const int64_t nNow = GetTime() * 1000000;
|
||||
@@ -291,6 +337,13 @@ void CSyncManager::ContinueHeaders(CNode* pfrom, const uint256& hashTip)
|
||||
if (!pfrom || hashTip == 0)
|
||||
return;
|
||||
|
||||
// Backpressure: don't extend the header front when it is already far ahead
|
||||
// of the connected block tip. Otherwise headers race past the block planner
|
||||
// and the bridge headers age out / get evicted before their blocks arrive.
|
||||
if (hashBestHeader != 0 &&
|
||||
GetPlannerHeight() - nBestHeight > HEADER_FRONT_MAX_AHEAD)
|
||||
return;
|
||||
|
||||
std::vector<uint256> vHave;
|
||||
uint256 hashWalk = hashTip;
|
||||
int nStep = 1;
|
||||
@@ -326,6 +379,12 @@ bool CSyncManager::RequestRefill(CNode* pfrom, uint256 hashTip, int64_t nMinInte
|
||||
nNowSec - pfrom->nLastIbdHeaderRequest < nMinIntervalSeconds)
|
||||
return false;
|
||||
|
||||
// Backpressure: stop pulling new headers once the front is far enough ahead
|
||||
// of the connected tip; let block download drain first (see ContinueHeaders).
|
||||
if (hashBestHeader != 0 &&
|
||||
GetPlannerHeight() - nBestHeight > HEADER_FRONT_MAX_AHEAD)
|
||||
return false;
|
||||
|
||||
uint256 hashLocatorTip = hashTip;
|
||||
if (hashLocatorTip == 0 ||
|
||||
(!mapBlockIndex.count(hashLocatorTip) && !mapHeaders.count(hashLocatorTip)))
|
||||
@@ -386,6 +445,15 @@ unsigned int CSyncManager::QueueBlocksParallel(unsigned int nWindow)
|
||||
if (vPath.empty())
|
||||
return 0;
|
||||
|
||||
// If the path doesn't connect back to the active chain, requesting these
|
||||
// blocks just fills the orphan pool. Bail and let Tick() repair the bridge.
|
||||
if (!PathReachesChain(vPath))
|
||||
{
|
||||
printf("IBD-DIAG: download path not anchored to chain (front=%s) — deferring to bridge repair\n",
|
||||
vPath.front().ToString().substr(0,20).c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<CNode*> vEligiblePeers;
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
@@ -645,6 +713,21 @@ void CSyncManager::Tick(CNode* pto, int nHighestInvWalk, const uint256& hashHigh
|
||||
if (nNowSec - pto->nLastIbdHeaderRequest >= nMinInterval)
|
||||
RequestRefill(pto, hashBestHeader, nMinInterval, "heartbeat");
|
||||
|
||||
// Bridge repair: we have a best header, but the download path can't reach
|
||||
// the connected chain — a linking header was lost (TTL/eviction/hole). Ask
|
||||
// this peer for headers with a locator anchored at the REAL chain tip so it
|
||||
// resends the headers directly above pindexBest and re-links the path.
|
||||
if (hashBestHeader != 0 && pindexBest &&
|
||||
!PathReachesChain(GetDownloadPath(hashBestHeader)) &&
|
||||
nNowSec - pto->nLastIbdHeaderRequest >= HEADER_SYNC_REFILL_MIN_INTERVAL_SECONDS)
|
||||
{
|
||||
pto->pindexLastGetHeadersBegin = NULL;
|
||||
pto->PushGetHeaders(pindexBest, uint256(0));
|
||||
pto->nLastIbdHeaderRequest = nNowSec;
|
||||
printf("IBD-DIAG: bridge-repair getheaders from connected tip height=%d peer=%s\n",
|
||||
pindexBest->nHeight, pto->addr.ToString().c_str());
|
||||
}
|
||||
|
||||
if (nNowSec - nLastBlockPlannerControl >= HEADER_SYNC_CONTROL_INTERVAL_SECONDS &&
|
||||
hashBestHeader != 0 &&
|
||||
nPlannerDepth > 0)
|
||||
|
||||
@@ -50,6 +50,7 @@ private:
|
||||
void PruneHeaders();
|
||||
bool AddHeaderNode(const CBlock& header, const uint256& hashHeader);
|
||||
std::vector<uint256> GetDownloadPath(uint256 hashTip) const;
|
||||
bool PathReachesChain(const std::vector<uint256>& vPath) const;
|
||||
void ContinueHeaders(CNode* pfrom, const uint256& hashTip);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user