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 block processing and block downloading, HaveBlock is used to check if
the block data already exists. It was ok to just check for the
existence of the blockNode but we now we also need to check if the data
exists as the blockNode may be present for just the block header.
On flushes to the database, we check that the blockNodes we have for
the downloaded block headers are not flushed to the disk unless the
block data is stored as well for backwards compatibility.
With older btcd clients, they rely on the fact that the blockNode is
present to check if the block data is also present. Since we now
store blockNodes for just the block headers, this is no longer true.
Because of this, we don't flush the blockNodes if there's no
accompanying block data for it. This results in downloading and
verifying the headers again if the node were to restart but since the
header data is small and the verification is quick, it's not a big
downside.
As an optimization, flushToDB now skips opening a write transaction
entirely when every dirty node is header-only. This avoids a no-op
write transaction on every ProcessBlockHeader call during header sync.
maybeAcceptBlock unconditionally created a new blockNode, overwriting the
index entry. If maybeAcceptBlockHeader had already processed the header,
the pointer held by bestHeader's chainView became orphaned, breaking
bestHeader.Contains and downstream checks like IsValidHeader.
Check for an existing node first and upgrade its status to
statusDataStored rather than replacing it.
ProcessBlockHeader performs chain selection and context-free &
contextual validation for the given block header. The function allows
a header-first downloading of blocks even without checkpoints.
maybeAcceptHeader performs checks to accept block headers into the
header chain. This function allows for a true headers-first download
where we only accept block headers for downloading new blocks.
We add a chainview of bestHeaders so that we'll be able to keep track of
headers separately from the bestChain. This is needed as we're getting
headers for new block annoucements instead of invs.
Set regtest buried heights to match Core: BIP34/65/66 at height 1 and make
CSV/SegWit/Taproot always active. Added regtest coverage for header version
floors, coinbase height enforcement, and deployment state to ensure we now
reject the blocks Core rejects and accept the ones Core accepts.
Updated package fullblocktests to generate BIP34-compliant blocks.
Test helpers now set prev block height to 0 (not the default -1) so generated
blocks start at height 1 and satisfy coinbase height rules.
Bitcoin Core PR https://github.com/bitcoin/bitcoin/pull/12204 tightened BIP30
handling: re-enable the check once height 1,983,702 is reached.
This change ports that logic into btcd and adds a regression test.
Mirror BIP34-aware BIP30 skip logic from Bitcoin Core.
PR https://github.com/bitcoin/bitcoin/pull/6931 in Bitcoin Core introduced
an optimization that skips the expensive BIP30 duplicate coinbase check once
BIP34 is active *and* the chain actually contains the recorded activation block.
See the comment in that PR "//Only continue to enforce if we're below BIP34
activation height or the block hash at that height doesn't correspond."
btcd used to drop the check purely based on height. On a fork that lacks the
BIP34 activation block, that difference lets a duplicate coinbase slip through
consensus. This patch ports the missing pieces: we store the activation hash in
chain parameters and only disable BIP30 after we see that block on-chain.
Sync the behaviour with Bitcoin Core.
See https://github.com/bitcoin/bitcoin/pull/6931
In Core, the BIP30 flag is cleared only if the previous block's ancestor chain
already contains the BIP34 activation block. For the activation block itself
(height = BIP0034Height), pindex->pprev->GetAncestor(BIP34Height) returns
nullptr, so the flag stays on and BIP30 is still enforced. In other words, Core
enforces BIP30 for the activation block and skips it starting with the following
block.
Upcoming commits tweak the conditions under which the expensive BIP30
duplicate-coinbase check should run. To unit-test those changes, this
commit moves the decision logic out of BlockChain.checkConnectBlock and
into a dedicated helper, bip0030CheckNeeded. The behaviour is unchanged;
the new helper is covered by tests in bip30_test.go.
This commit introduces the concept of `AlwaysActiveHeight` to the
deployment mechanism, allowing a deployment to be forced into the active
state if the next block's height meets or exceeds this threshold.
This is intended primarily to be used alongside the new Testnet4
deployment, as the past major soft forks are meant to be active from the
very first block height.
Add robust property-based tests for the assertNoTimeWarp function using
the rapid testing library. The tests verify the following scenarios:
- Basic property tests:
- Only retarget blocks (block height divisible by blocksPerRetarget) are checked
- Valid timestamps (within maxTimeWarp of previous block) pass validation
- Invalid timestamps (too early) fail with appropriate ErrTimewarpAttack
- Correct boundary behavior (exactly at maxTimeWarp limit)
- Invariant tests:
- Function never panics with valid inputs
- Non-retarget blocks always return nil regardless of timestamps
- Security tests:
- All retarget blocks are protected from timewarp attacks
- Non-retarget blocks are not affected by the timewarp check
ReconsiderBlock reconsiders the validity of the block for the passed
in blockhash. The behavior of the function mimics that of Bitcoin Core.
The invalid status of the block nodes are reset and if the chaintip that
is being reconsidered has more cumulative work, then we'll validate the
blocks and reorganize to it. If the cumulative work is lesser than the
current active chain tip, then nothing else will be done.
reorganizeChain() used to handle the following:
1: That the blocknodes being disconnected/connected indeed to connect
properly without errors.
2: Perform the actual disconnect/connect of the blocknodes.
The functionality of 1, the validation that the disconnects/connects can
happen without errors are now refactored out into
verifyReorganizationValidity.
This is an effort made so that ReconsiderBlock() can call
verifyReorganizationValidity and set the block status of the
reconsidered chain and return nil even when an error returns as it's ok
to get an error when reconsidering an invalid branch.
InvalidateBlock() invalidates a given block and marks all its
descendents as invalid as well. The active chain tip changes if the
invalidated block is part of the best chain.
The block generating functions here allow for a test to create mock
blocks. This is useful for testing invalidateblock and reconsiderblock
methods on blockchain that will be added in later commits.
createSpendTx is moved to testhelper so that the function can be used
for callers in package blockchain without introducing import cycles.
The test code for invalidateblock and reconsiderblock that are going to
be added in later commits make use of this code.
createCoinbaseTx's code is refactored out and placed in testhelper
package and is exported so that callers in package blockchain can reuse
the code without introducing import cycles. The test code for
invalidateblock and reconsiderblock that'll be added in later commits
make use of this code.
standardCoinbaseScript is moved to testhelper and is exported. This
allows test code in package blockchain to reuse the code without
introducing an import cycle. This code is used in the testing code
for invalidateblock and reconsiderblock that's added in the later
commits.
uniqueOpReturnScript is moved to testhelper and is exported so that the
code and be reused in package blockchain without introducing import
cycles. The test code for invalidateblock and reconsiderblock that are
gonna be added in later commits uses the functions.
The variables are moved to testhelper so that they can be reused in the
blockchain package without introducing an import cycle. The testing
code for invalidateblock and reconsiderblock that will be added in later
commits will be using these variables.
solveBlock is moved to testhelper and is exported. This is done so that
the code can be reused without introducing import cycles. The testing
code to be added in alter commits for invalidateblock and reconsider
block will use SolveBlock.
spendableOut and the functions related to it are is moved to package
testhelper and are exported. This is done to make the code reusable
without introducing an import cycle when the testing code for
invalidateblock and reconsiderblock are added in follow up commits.
Some of the functions in difficulty.go are not dependent on any external
functions and they are needed to introduce testing code for the
invalidateblock and reconsiderblock methods that are to be added on in
later commits. Having the workmath package let's us reuse the code and
avoid dependency cycles.
The existing functions that were exported already (HashToBig,
CompactToBig, BigToCompact, CalcWork) are still kept in difficulty.go
to avoid breaking external code that depends on those exported
functions.