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:
+98
-15
@@ -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_REQUEST_TIMEOUT_MICROS = 60 * 1000000;
|
||||||
static const int64_t HEADER_REDUNDANT_REQUEST_MICROS = 5 * 1000000;
|
static const int64_t HEADER_REDUNDANT_REQUEST_MICROS = 5 * 1000000;
|
||||||
static const int64_t HEADER_SYNC_TTL_MICROS = 15 * 60 * 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;
|
std::map<uint256, CSyncManager::HeaderNode> mapHeaders;
|
||||||
uint256 hashBestHeader = 0;
|
uint256 hashBestHeader = 0;
|
||||||
@@ -124,12 +129,23 @@ void CSyncManager::PruneHeaders()
|
|||||||
{
|
{
|
||||||
const int64_t nNow = GetTime() * 1000000;
|
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)
|
if (mapHeaders.size() > MAX_HEADER_SYNC_CACHE / 2)
|
||||||
{
|
{
|
||||||
unsigned int nEvicted = 0;
|
unsigned int nEvicted = 0;
|
||||||
for (std::map<uint256, HeaderNode>::iterator it = mapHeaders.begin(); it != mapHeaders.end(); )
|
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);
|
it = mapHeaders.erase(it);
|
||||||
++nEvicted;
|
++nEvicted;
|
||||||
@@ -139,25 +155,37 @@ void CSyncManager::PruneHeaders()
|
|||||||
}
|
}
|
||||||
if (nEvicted > 0)
|
if (nEvicted > 0)
|
||||||
{
|
{
|
||||||
printf("IBD-DIAG: TTL-evicted %u stale sync headers, %u remain\n",
|
printf("IBD-DIAG: TTL-evicted %u stale sync headers above floor %d, %u remain\n",
|
||||||
nEvicted, (unsigned int)mapHeaders.size());
|
nEvicted, nProtectFloor, (unsigned int)mapHeaders.size());
|
||||||
RecomputeBestHeader();
|
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)
|
if (mapHeaders.size() > MAX_HEADER_SYNC_CACHE)
|
||||||
{
|
{
|
||||||
printf("IBD-DIAG: sync header cache exceeded %u entries, evicting oldest\n", MAX_HEADER_SYNC_CACHE);
|
printf("IBD-DIAG: sync header cache exceeded %u entries, evicting from the front (floor=%d)\n",
|
||||||
while (mapHeaders.size() > MAX_HEADER_SYNC_CACHE * 3 / 4)
|
MAX_HEADER_SYNC_CACHE, nProtectFloor);
|
||||||
{
|
|
||||||
std::map<uint256, HeaderNode>::iterator oldest = mapHeaders.begin();
|
std::vector<std::map<uint256, HeaderNode>::iterator> vEvictable;
|
||||||
for (std::map<uint256, HeaderNode>::iterator it = mapHeaders.begin(); it != mapHeaders.end(); ++it)
|
for (std::map<uint256, HeaderNode>::iterator it = mapHeaders.begin(); it != mapHeaders.end(); ++it)
|
||||||
{
|
if (it->second.nHeight > nProtectFloor)
|
||||||
if (it->second.nInsertTime < oldest->second.nInsertTime)
|
vEvictable.push_back(it);
|
||||||
oldest = it;
|
|
||||||
}
|
std::sort(vEvictable.begin(), vEvictable.end(),
|
||||||
mapHeaders.erase(oldest);
|
[](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();
|
RecomputeBestHeader();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -191,7 +219,11 @@ bool CSyncManager::AddHeaderNode(const CBlock& header, const uint256& hashHeader
|
|||||||
}
|
}
|
||||||
|
|
||||||
const int nHeight = nPrevHeight + 1;
|
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",
|
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,
|
nHeight, hashHeader.ToString().substr(0,20).c_str(), header.nBits,
|
||||||
@@ -235,6 +267,20 @@ std::vector<uint256> CSyncManager::GetDownloadPath(uint256 hashTip) const
|
|||||||
return vPath;
|
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
|
unsigned int CSyncManager::CountInFlight() const
|
||||||
{
|
{
|
||||||
const int64_t nNow = GetTime() * 1000000;
|
const int64_t nNow = GetTime() * 1000000;
|
||||||
@@ -291,6 +337,13 @@ void CSyncManager::ContinueHeaders(CNode* pfrom, const uint256& hashTip)
|
|||||||
if (!pfrom || hashTip == 0)
|
if (!pfrom || hashTip == 0)
|
||||||
return;
|
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;
|
std::vector<uint256> vHave;
|
||||||
uint256 hashWalk = hashTip;
|
uint256 hashWalk = hashTip;
|
||||||
int nStep = 1;
|
int nStep = 1;
|
||||||
@@ -326,6 +379,12 @@ bool CSyncManager::RequestRefill(CNode* pfrom, uint256 hashTip, int64_t nMinInte
|
|||||||
nNowSec - pfrom->nLastIbdHeaderRequest < nMinIntervalSeconds)
|
nNowSec - pfrom->nLastIbdHeaderRequest < nMinIntervalSeconds)
|
||||||
return false;
|
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;
|
uint256 hashLocatorTip = hashTip;
|
||||||
if (hashLocatorTip == 0 ||
|
if (hashLocatorTip == 0 ||
|
||||||
(!mapBlockIndex.count(hashLocatorTip) && !mapHeaders.count(hashLocatorTip)))
|
(!mapBlockIndex.count(hashLocatorTip) && !mapHeaders.count(hashLocatorTip)))
|
||||||
@@ -386,6 +445,15 @@ unsigned int CSyncManager::QueueBlocksParallel(unsigned int nWindow)
|
|||||||
if (vPath.empty())
|
if (vPath.empty())
|
||||||
return 0;
|
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;
|
std::vector<CNode*> vEligiblePeers;
|
||||||
{
|
{
|
||||||
LOCK(cs_vNodes);
|
LOCK(cs_vNodes);
|
||||||
@@ -645,6 +713,21 @@ void CSyncManager::Tick(CNode* pto, int nHighestInvWalk, const uint256& hashHigh
|
|||||||
if (nNowSec - pto->nLastIbdHeaderRequest >= nMinInterval)
|
if (nNowSec - pto->nLastIbdHeaderRequest >= nMinInterval)
|
||||||
RequestRefill(pto, hashBestHeader, nMinInterval, "heartbeat");
|
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 &&
|
if (nNowSec - nLastBlockPlannerControl >= HEADER_SYNC_CONTROL_INTERVAL_SECONDS &&
|
||||||
hashBestHeader != 0 &&
|
hashBestHeader != 0 &&
|
||||||
nPlannerDepth > 0)
|
nPlannerDepth > 0)
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ private:
|
|||||||
void PruneHeaders();
|
void PruneHeaders();
|
||||||
bool AddHeaderNode(const CBlock& header, const uint256& hashHeader);
|
bool AddHeaderNode(const CBlock& header, const uint256& hashHeader);
|
||||||
std::vector<uint256> GetDownloadPath(uint256 hashTip) const;
|
std::vector<uint256> GetDownloadPath(uint256 hashTip) const;
|
||||||
|
bool PathReachesChain(const std::vector<uint256>& vPath) const;
|
||||||
void ContinueHeaders(CNode* pfrom, const uint256& hashTip);
|
void ContinueHeaders(CNode* pfrom, const uint256& hashTip);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user