TestSyncStateMachine checked that handleHeadersMsg advanced
lastProgressTime by comparing it against the timestamp written by
startSync. Those two writes can happen within the same clock tick, so
the handler can update the field while time.After still reports false.
Reset lastProgressTime to the zero value before delivering headers and
assert that the handler writes a non-zero value. This keeps the test
focused on the behavior under test without depending on adjacent
time.Now calls producing distinct timestamps.
Commit 26124d275 made every peer a sync candidate on regtest and
simnet so that nodes on non-localhost networks (e.g. Docker bridge
networks) can be synced from. Dropping the address requirement was
the intent, but the change also dropped the service-flag requirement,
so light clients became eligible sync peers.
A light client (e.g. neutrino) advertises a recent best height but
can serve neither headers nor blocks. Electing one as the sync peer
stalls the sync until the stall handler disconnects it, and with
other light client connections present the next one is elected and
stalls again, livelocking the sync indefinitely. This surfaced in
neutrino's sync tests, where a btcd simnet node connected to both a
neutrino instance and other btcd nodes never synced.
Keep accepting any peer address on regtest/simnet, but require the
peer to signal SFNodeNetwork or SFNodeNetworkLimited like on any
other network.
handleInvMsg early-returned for any inv from a non-syncPeer whenever
sm.current() was false, with the comment that it prevents fetching a
mass of orphans. That guard assumes a syncPeer is already fetching
blocks; when syncPeer is nil, the assumption breaks down and the
early return becomes a deadlock.
The deadlock is reachable whenever two nodes connect at equal heights:
startSync exits without picking a syncPeer (no peer is "higher"), and
nothing later promotes the freshly-mined blocks the peer announces via
inv. The pre-verack disconnect and sync-race regression tests in
integration/sync_race_test.go fail consistently because of this.
Only skip the inv when we actually have a syncPeer. When syncPeer is
nil, fall through and let the normal request path queue the block --
the inv is the only signal that there are blocks to fetch.
PR #2467 changed regtest to match Core's BIP34/65/66 activation rules, and
the merged stack carries that (commit cd4e5426 "regtest: align activations
with Bitcoin Core"). Height-1 regtest blocks now need a BIP34-compliant
coinbase height and a post-BIP66 block version.
The failing tests came from commits added after PR #2467 was opened on
December 25, 2025 but before it merged on April 30, 2026:
- c1a46122 ("blockchain: add ProcessBlockHeader")
- f9645f07 ("blockchain: reuse existing header node in maybeAcceptBlock")
- dc6e096c ("netsync: add TestSyncStateMachine for end-to-end IBD sync flow")
- ce094262 ("netsync: add TestStartSyncBlockFallback for block-only sync path")
- 2aae8a6d ("netsync: add TestStartSyncChainCurrent for chain-current noop path")
Because those tests landed later, they kept the old regtest assumptions even
though #2467 had already been authored and tested against the older tree.
Once #2467 finally merged, these newer tests started building invalid regtest
blocks and headers.
Fix them by setting the genesis tip height to 0 before generating descendants,
using Version 4 in the regtest block/header helpers, and encoding the test
coinbase height with a minimal BIP34 push plus padding for the generic coinbase
script-length rule.
In this commit, we remove the localhost-only restriction for sync peer
candidates on regtest and simnet. Previously, isSyncCandidate rejected
any regtest peer not on 127.0.0.1 or localhost, which prevented IBD
when nodes run in Docker containers on bridge networks (e.g. 172.18.0.x).
We also replace fragile pointer-equality checks against global chaincfg
params with value-based Name field comparisons, and extend the exception
to simnet since it has the same local-only usage pattern.
Verify that startSync skips header download and directly requests
blocks when the header chain is already caught up to the peer's
height but the block chain lags behind.
ince we now utilize ProcessBlockHeaders, we change the startSync
function to utilize the block index for downloading blocks/headers
instead of using the headerList in SyncManager.
Refactor fetchHeaderBlocks and buildBlockRequest to take an explicit
peer parameter instead of implicitly using sm.syncPeer. This makes the
caller responsible for choosing which peer to fetch from and adds a nil
guard to prevent a panic if the sync peer has been cleared.
headers
We introduce buildBlockRequest that'll create a getdata message based
off of the block index instead of the headerList in SyncManager.
The new fetchHeaderBlocks utilizes buildBlockRequest and now will create
fetch requests based on the processed block headers.
isInIBDMode returns if the SyncManager needs to download blocks and sync
to the latest chain tip.
It determines if it's in ibd mode by checking if the blockchain thinks
we're current and if we don't have peers that are at higher advertised
blocks.
When we're all caught up and we have a new peer, we'll ask for blocks
from peers that we're on the same height. Since these peers don't have
any blocks to send us, they don't reply and we disconnect from them as
they timeout.
To prevent this, we don't ask for blocks if the chain thinks we're
current and do not have a peer that reports having a higher block.
Noticed during ibd that there's a slight overhead for
handleBlockchainNotification on mempool/fee estimator updates. Since
there's no reason to be looking at this while we're not caught up,
return early and avoid the calls.
This change is part of the effort to add utxocache support to btcd.
utxo cache is now used by the BlockChain struct. By default it's used
and the minimum cache is set to 250MiB. The change made helps speed up
block/tx validation as the cache allows for much faster lookup of utxos.
The initial block download in particular is improved as the db i/o
bottleneck is remedied by the cache.
isSyncCandidate is now changed to return true even if the peer is a
pruned node if and only if our chaintip is within 288 blocks of the
peer.
Rationale:
Pruned nodes that signal NODE_NETWORK_LIMITED MUST serve 288 blocks from
their chaintip. If our chaintip is within that range, this peer can be
a sync candidate even if they aren't an archival node.
The doc formatting changes introduced in the recent go version is
increasing the diff for all of the new commits. Formatting it all in
this commit will help the readability of future PRs by reducing the
diff.
In this commit, we update all the btcutil imports to point to the new
sub-module.
In the same commit, we also modify the recently added `btcutil/go.mod`
file as we need to continue pointing to the _old_ version of btcd, until
we merge this PR and push a new tag.
backport from https://github.com/decred/dcrd/pull/2253
When a peer sends a notfound message, remove the hash from requested
map. Also increase notfound ban score and return early if it
disconnects the peer.
Adds stall detection when no blocks have been received from the sync
peer for at least 3 minutes.
The change backports parts of https://github.com/gcash/bchd/pull/105,
though has different behavior, such as:
- Rotating the sync peer at tip.
- Only disconnecting the sync peer if they're height exceeds our own.
Since we will instead rotate the sync peer at tip, this prevents us
from disconnecting good peers while waiting for new blocks.
- Not resetting the progress time when blocks are submitted via rpc.
This commit modifies the sync peer selection to prefer peers with a
higher advertised height than our currently known best height. If no
peers are known with a higher height, we will fall back to selecting a
random peer with the same best height as our own.
The current algorithm currently selects a random peer from the union of
these two sets, while this approach will favor trying to make progress.
This will likely help in selecting a good peer once already at tip, such
as after a restart.
Backport of https://github.com/gcash/bchd/pull/96
This modifies the utxoset in the database and related UtxoViewpoint to
store and work with unspent transaction outputs on a per-output basis
instead of at a transaction level. This was inspired by similar recent
changes in Bitcoin Core.
The primary motivation is to simplify the code, pave the way for a
utxo cache, and generally focus on optimizing runtime performance.
The tradeoff is that this approach does somewhat increase the size of
the serialized utxoset since it means that the transaction hash is
duplicated for each output as a part of the key and some additional
details such as whether the containing transaction is a coinbase and the
block height it was a part of are duplicated in each output.
However, in practice, the size difference isn't all that large, disk
space is relatively cheap, certainly cheaper than memory, and it is much
more important to provide more efficient runtime operation since that is
the ultimate purpose of the daemon.
While performing this conversion, it also simplifies the code to remove
the transaction version information from the utxoset as well as the
spend journal. The logic for only serializing it under certain
circumstances is complicated and it isn't actually used anywhere aside
from the gettxout RPC where it also isn't used by anything important
either. Consequently, this also removes the version field of the
gettxout RPC result.
The utxos in the database are automatically migrated to the new format
with this commit and it is possible to interrupt and resume the
migration process.
Finally, it also updates the tests for the new format and adds a new
function to the tests to convert the old test data to the new format for
convenience. The data has already been converted and updated in the
commit.
An overview of the changes are as follows:
- Remove transaction version from both spent and unspent output entries
- Update utxo serialization format to exclude the version
- Modify the spend journal serialization format
- The old version field is now reserved and always stores zero and
ignores it when reading
- This allows old entries to be used by new code without having to
migrate the entire spend journal
- Remove version field from gettxout RPC result
- Convert UtxoEntry to represent a specific utxo instead of a
transaction with all remaining utxos
- Optimize for memory usage with an eye towards a utxo cache
- Combine details such as whether the txout was contained in a
coinbase, is spent, and is modified into a single packed field of
bit flags
- Align entry fields to eliminate extra padding since ultimately
there will be a lot of these in memory
- Introduce a free list for serializing an outpoint to the database
key format to significantly reduce pressure on the GC
- Update all related functions that previously dealt with transaction
hashes to accept outpoints instead
- Update all callers accordingly
- Only add individually requested outputs from the mempool when
constructing a mempool view
- Modify the spend journal to always store the block height and coinbase
information with every spent txout
- Introduce code to handle fetching the missing information from
another utxo from the same transaction in the event an old style
entry is encountered
- Make use of a database cursor with seek to do this much more
efficiently than testing every possible output
- Always decompress data loaded from the database now that a utxo entry
only consists of a specific output
- Introduce upgrade code to migrate the utxo set to the new format
- Store versions of the utxoset and spend journal buckets
- Allow migration process to be interrupted and resumed
- Update all tests to expect the correct encodings, remove tests that no
longer apply, and add new ones for the new expected behavior
- Convert old tests for the legacy utxo format deserialization code to
test the new function that is used during upgrade
- Update the utxostore test data and add function that was used to
convert it
- Introduce a few new functions on UtxoViewpoint
- AddTxOut for adding an individual txout versus all of them
- addTxOut to handle the common code between the new AddTxOut and
existing AddTxOuts
- RemoveEntry for removing an individual txout
- fetchEntryByHash for fetching any remaining utxo for a given
transaction hash
new txs that it observes. The block manager alerts the fee estimator
of new and orphaned blocks.
Check for invalid state and recreate FeeEstimator if necessary.