From de756d8bd967477568763ec093fc7399257ab626 Mon Sep 17 00:00:00 2001 From: Eric Chennells Date: Sun, 20 Sep 2026 01:44:06 +0000 Subject: [PATCH] Rewind the download window and bump validation on regression. After a reorganization the node can stop making progress while headers continue to arrive; a restart clears it. Two chasers are left waiting on events that never come. `chaser_check::do_regressed` rewinds the position and purges outstanding work, but leaves `requested_` and `advanced_` where they were, and ignores a regression that falls above the position but inside the requested window. `set_unassociated` then defers new requests while `position() < requested_`, waiting on work that was just purged, so the heights between the position and the old request are never requested. The guard now considers the window as well as the position, and both are rewound with it, as at start. `chaser_validate` advances on the checked event for the block above its position, or on a bump. When a reorganization returns to a branch whose next block is already downloaded, neither arrives. The organizer now bumps validation on regression as it does once at start. Reproduced on regtest against bitcoind and on replays of recorded testnet3 reorganizations, and observed handled on live testnet3. A regression organized while not current does not stall, so the bump stays within the currency gate. --- include/bitcoin/node/impl/chasers/chaser_organize.ipp | 6 ++++-- src/chasers/chaser_check.cpp | 11 +++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/include/bitcoin/node/impl/chasers/chaser_organize.ipp b/include/bitcoin/node/impl/chasers/chaser_organize.ipp index c45b9c6f..86593a1b 100644 --- a/include/bitcoin/node/impl/chasers/chaser_organize.ipp +++ b/include/bitcoin/node/impl/chasers/chaser_organize.ipp @@ -332,13 +332,15 @@ void CLASS::do_organize(typename Block::cptr block, bool prioritized, // Checking currency before notify also avoids excessive work backlog. if (is_block() || current) { - if (!bumped_) + if (!bumped_ || regress) { // If at start the fork point is top of both chains, and next candidate // is already downloaded, then new header will arrive and download will // be skipped, resulting in stall until restart at which time the start // event will advance through all downloaded candidates and progress on - // arrivals. This bumps validation once for current strong headers. + // arrivals. This bumps validation once for current strong headers, and + // again on regression, as the candidate above the branch point may + // already be downloaded when reorganizing back to a stored branch. notify(error::success, chases::bump{ add1(branch_point) }); bumped_ = true; } diff --git a/src/chasers/chaser_check.cpp b/src/chasers/chaser_check.cpp index 5ca4c019..dc93be89 100644 --- a/src/chasers/chaser_check.cpp +++ b/src/chasers/chaser_check.cpp @@ -344,12 +344,15 @@ void chaser_check::do_regressed(height_t branch_point) NOEXCEPT { BC_ASSERT(stranded()); - // Inconsequential regression, work isn't there yet. - if (branch_point >= position()) + // Inconsequential regression, neither position nor window is there yet. + if (branch_point >= std::max(position(), requested_)) return; - // Update position, purge outstanding work, and wait on track completion. - set_position(branch_point); + // Update position and window, purge outstanding work, and wait on track + // completion. The window must follow the position or the gap it awaits + // is never requested. + set_position(std::min(branch_point, position())); + requested_ = advanced_ = position(); stop_tracking(); maps_.clear(); notify(error::success, chases::purge{ branch_point });