What changed, and why it matters
This update fixes a rule in btcd (a Bitcoin implementation) that was skipping an expensive duplicate-check too early. The change re-enables that duplicate check once the blockchain reaches height 1,983,702, matching a safeguard already added to Bitcoin Core. Without the fix, a carefully crafted block could potentially reuse an old coinbase transaction ID after BIP34 is active, which could cause nodes to disagree on the state of coins. The patch includes a regression test.
Reviewers should confirm the chosen re-enable height matches Bitcoin Core exactly and that the condition params.BIP0034Height < h && h < bip34ReenableBIP30Height correctly re-enables at height 1,983,702 (not 1,983,701). Operators should upgrade btcd nodes to avoid consensus divergence at the re-enable height.
Security signals we found
Consensus-rule change ported from upstream Bitcoin Core PR 12204
Re-enables BIP30 duplicate coinbase transaction ID check at height 1,983,702
Prevents potential duplicate coinbase txid after BIP34 activation
Adds regression test for the re-enable behavior
Evidence from the diff
The commit modifies blockchain/validate.go so that bip0030CheckNeeded returns true when the block height is below BIP34 activation OR at/above the new bip34ReenableBIP30Height (1,983,702). Previously it returned false for any height above BIP34Height, bypassing BIP30’s duplicate-txid check entirely. The re-enable closes a window where a pre-BIP34 coinbase that serializes a future height could collide with a later coinbase, because BIP34 only prevents duplicates by requiring the height in the coinbase scriptSig. A new test, TestBip0030CheckNeededReenabled, verifies the check is re-enabled at the threshold.
Changed components
blockchain/validate.goblockchain/bip30_test.goInspect captured patch +38 / −5
diff --git a/blockchain/bip30_test.go b/blockchain/bip30_test.go
index c3a21ab..09e84ab 100644
--- a/blockchain/bip30_test.go
+++ b/blockchain/bip30_test.go
@@ -123,3 +123,27 @@ func TestBip0030CheckNeededMismatchedActivation(t *testing.T) {
require.True(t, bip0030CheckNeeded(node, ¶ms))
}
+
+// TestBip0030CheckNeededReenabled ensures that once the chain reaches the
+// re-enable height, the duplicate check is performed again even when BIP34 is
+// active on the current branch.
+func TestBip0030CheckNeededReenabled(t *testing.T) {
+ params := chaincfg.MainNetParams
+
+ ancestor := &blockNode{
+ height: params.BIP0034Height,
+ hash: mustHashFromStr(t, "000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8"),
+ }
+ parent := &blockNode{
+ height: ancestor.height + 1,
+ hash: mustHashFromStr(t, "0000000000000000000000000000000000000000000000000000000000000100"),
+ parent: ancestor,
+ }
+ node := &blockNode{
+ height: bip34ReenableBIP30Height,
+ hash: mustHashFromStr(t, "0000000000000000000000000000000000000000000000000000000000000200"),
+ parent: parent,
+ }
+
+ require.True(t, bip0030CheckNeeded(node, ¶ms))
+}
diff --git a/blockchain/validate.go b/blockchain/validate.go
index 818fee2..9fb8f66 100644
--- a/blockchain/validate.go
+++ b/blockchain/validate.go
@@ -51,6 +51,11 @@ const (
// block of a difficulty adjustment period is allowed to
// be earlier than the last block of the previous period (BIP94).
maxTimeWarp = 600 * time.Second
+
+ // bip34ReenableBIP30Height is the height where BIP0030 is re-enabled even
+ // though BIP34 is active. This mirrors Bitcoin Core's safeguard against
+ // coinbases that serialized future heights prior to BIP34 activation.
+ bip34ReenableBIP30Height int32 = 1983702
)
var (
@@ -202,9 +207,10 @@ func isBIP0030Node(node *blockNode) bool {
// In addition, as of BIP0034, duplicate coinbases are no longer possible due to
// its requirement for including the block height in the coinbase and thus it is
// no longer possible to create transactions that 'overwrite' older ones.
-// Therefore, only enforce the rule if BIP0034 is not yet active. This is a
-// useful optimization because the BIP0030 check is expensive since it involves
-// a ton of cache misses in the utxoset.
+// Therefore, only enforce the rule if BIP0034 is not yet active, or the chain
+// has reached the height bip34ReenableBIP30Height where the optimization must
+// no longer apply. This is a useful optimization because the BIP0030 check is
+// expensive since it involves a ton of cache misses in the utxoset.
func bip0030CheckNeeded(node *blockNode, params *chaincfg.Params) bool {
// Sanity checks for the inputs not to dereference a nil pointer.
if node == nil || params == nil {
@@ -218,8 +224,11 @@ func bip0030CheckNeeded(node *blockNode, params *chaincfg.Params) bool {
}
// Once BIP0034 is known to be active on this chain, duplicate coinbases
- // can no longer occur, so the check can be omitted.
- if node.height > params.BIP0034Height {
+ // can no longer occur, so the check can be omitted until the re-enable
+ // height. See the following comment for the details about re-enabling:
+ // https://github.com/bitcoin/bitcoin/pull/12204#issuecomment-359106628
+ h := node.height
+ if params.BIP0034Height < h && h < bip34ReenableBIP30Height {
// Make sure that BIP0034 was activated. We need to make sure
// that there is a block with the hash we expect at the height
// BIP0034Height. If this is not the case, we might be on an
Why this scored 59/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.