Draft: seeking design input: This PR changes when a peer gives up the sole initial headers-sync slot. Before merging, I’d like to confirm whether retaining that slot after a valid empty headers response is intentional, or whether another eligible peer should be tried. The existing timeout does not hand off the slot when no other preferred download peer is available; an additional sync attempt also depends on receiving a qualifying new-block announcement.
Problem: While the best header is at least a day old, a node normally selects one peer for initial headers sync. An inbound or manual peer can answer with valid empty headers and remain selected, so another eligible peer is not asked. The peer may simply be caught up with the node or stuck at the same height. Bitcoin Core itself can also return empty headers while its active chain is below minimumchainwork, so an empty response is not misbehavior. This can prevent IBD from progressing when no other preferred download peer is available.
Fix: After such a response from an inbound or manual peer, release its initial-sync slot and cancel its download deadline. Start a new response window so another eligible peer can take the slot before the same peer is selected again. Automatic full-relay and block-relay peers retain their existing timeout and old-chain eviction behavior.
Remaining cases: This does not prevent deliberate stalling. A block announcement can bypass the retry delay, and an already-known connecting header can clear it. Short non-empty responses can also end headers download without releasing the slot. Those cases, timeout handoff without a preferred peer, and early release for automatic outbound peers need separate design discussion.
<details><summary>Manual reproducer</summary>
Run these commands before and after the fix. The Python script connects two inbound P2P peers.
{ cmake -B build && cmake --build build -j10 --target bitcoind; } >/dev/null 2>&1
build/bin/bitcoind -regtest -daemonwait -datadir="$(mktemp -d)" -connect=0 -listen=1 -port=18455
time python3 <(curl -fsSL https://gist.github.com/l0rinc/39e471dd6835fe332205d3812ebb9e92/raw)
killall bitcoind
The fixed node returns as soon as the replacement peer receives getheaders.
On the old node, that wait times out after 10 seconds because the initial peer is still treated as active.
</details>
Fixes #34096