mirror of
https://github.com/btcsuite/btcd.git
synced 2026-08-13 12:32:51 +02:00
218 lines
5.8 KiB
Go
218 lines
5.8 KiB
Go
package blockchain
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/btcutil/v2"
|
|
"github.com/btcsuite/btcd/chaincfg/v2"
|
|
"github.com/btcsuite/btcd/chainhash/v2"
|
|
"github.com/btcsuite/btcd/txscript/v2"
|
|
"github.com/btcsuite/btcd/wire/v2"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// stubChainCtx provides the minimal ChainCtx implementation needed for header
|
|
// context checks in tests.
|
|
type stubChainCtx struct {
|
|
params *chaincfg.Params
|
|
}
|
|
|
|
// ChainParams returns the active chain parameters.
|
|
func (s stubChainCtx) ChainParams() *chaincfg.Params {
|
|
return s.params
|
|
}
|
|
|
|
// BlocksPerRetarget returns the blocks per difficulty retarget.
|
|
func (s stubChainCtx) BlocksPerRetarget() int32 {
|
|
return int32(s.params.TargetTimespan / s.params.TargetTimePerBlock)
|
|
}
|
|
|
|
// MinRetargetTimespan returns the lower bound of the retarget timespan.
|
|
func (s stubChainCtx) MinRetargetTimespan() int64 {
|
|
return int64(
|
|
s.params.TargetTimespan /
|
|
time.Duration(s.params.RetargetAdjustmentFactor),
|
|
)
|
|
}
|
|
|
|
// MaxRetargetTimespan returns the upper bound of the retarget timespan.
|
|
func (s stubChainCtx) MaxRetargetTimespan() int64 {
|
|
return int64(
|
|
s.params.TargetTimespan *
|
|
time.Duration(s.params.RetargetAdjustmentFactor),
|
|
)
|
|
}
|
|
|
|
// VerifyCheckpoint reports whether a checkpoint matches.
|
|
func (s stubChainCtx) VerifyCheckpoint(_ int32, _ *chainhash.Hash) bool {
|
|
return true
|
|
}
|
|
|
|
// FindPreviousCheckpoint returns the last known checkpoint.
|
|
func (s stubChainCtx) FindPreviousCheckpoint() (HeaderCtx, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
// regtestPrevNode returns a blockNode for the regtest genesis block to serve
|
|
// as the parent of height-1 test headers.
|
|
func regtestPrevNode(t *testing.T) *blockNode {
|
|
t.Helper()
|
|
|
|
params := chaincfg.RegressionNetParams
|
|
|
|
return newBlockNode(¶ms.GenesisBlock.Header, nil)
|
|
}
|
|
|
|
// TestRegtestBlockVersions ensures regtest enforces BIP34/66/65 version floors
|
|
// from height 1.
|
|
func TestRegtestBlockVersions(t *testing.T) {
|
|
params := chaincfg.RegressionNetParams
|
|
prevNode := regtestPrevNode(t)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
version int32
|
|
wantErr ErrorCode
|
|
}{
|
|
{
|
|
name: "v1_rejected",
|
|
version: 1,
|
|
wantErr: ErrBlockVersionTooOld,
|
|
},
|
|
{
|
|
name: "v2_rejected",
|
|
version: 2,
|
|
wantErr: ErrBlockVersionTooOld,
|
|
},
|
|
{
|
|
name: "v3_rejected",
|
|
version: 3,
|
|
wantErr: ErrBlockVersionTooOld,
|
|
},
|
|
{
|
|
name: "v4_allowed",
|
|
version: 4,
|
|
wantErr: ErrorCode(0),
|
|
},
|
|
{
|
|
name: "vb_signal_allowed",
|
|
version: 0x20000000,
|
|
wantErr: ErrorCode(0),
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
block := &wire.BlockHeader{
|
|
Version: tc.version,
|
|
PrevBlock: *params.GenesisHash,
|
|
Bits: params.PowLimitBits,
|
|
Timestamp: time.Unix(prevNode.Timestamp()+1, 0),
|
|
}
|
|
|
|
err := CheckBlockHeaderContext(
|
|
block, prevNode, BFFastAdd,
|
|
stubChainCtx{params: ¶ms}, true,
|
|
)
|
|
|
|
if tc.wantErr == 0 {
|
|
require.NoError(t, err)
|
|
|
|
return
|
|
}
|
|
|
|
require.Error(t, err)
|
|
|
|
var rErr RuleError
|
|
require.ErrorAs(t, err, &rErr)
|
|
require.Equal(t, tc.wantErr, rErr.ErrorCode)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestRegtestBuriedDeploymentsAlwaysActive asserts CSV, SegWit, and Taproot
|
|
// are always active on regtest, mirroring Bitcoin Core.
|
|
func TestRegtestBuriedDeploymentsAlwaysActive(t *testing.T) {
|
|
chain := newFakeChain(&chaincfg.RegressionNetParams)
|
|
prevNode := chain.bestChain.Tip()
|
|
|
|
stateCSV, err := chain.deploymentState(prevNode, chaincfg.DeploymentCSV)
|
|
require.NoError(t, err)
|
|
require.Equal(t, ThresholdActive, stateCSV)
|
|
|
|
stateSegwit, err := chain.deploymentState(
|
|
prevNode, chaincfg.DeploymentSegwit,
|
|
)
|
|
require.NoError(t, err)
|
|
require.Equal(t, ThresholdActive, stateSegwit)
|
|
|
|
stateTaproot, err := chain.deploymentState(
|
|
prevNode, chaincfg.DeploymentTaproot,
|
|
)
|
|
require.NoError(t, err)
|
|
require.Equal(t, ThresholdActive, stateTaproot)
|
|
}
|
|
|
|
// TestRegtestRejectsCoinbaseMissingHeight ensures contextual validation
|
|
// enforces coinbase height serialization on regtest.
|
|
func TestRegtestRejectsCoinbaseMissingHeight(t *testing.T) {
|
|
chain := newFakeChain(&chaincfg.RegressionNetParams)
|
|
prevNode := chain.bestChain.Tip()
|
|
|
|
block := wire.MsgBlock{
|
|
Header: wire.BlockHeader{
|
|
Version: 4,
|
|
PrevBlock: prevNode.hash,
|
|
Bits: chain.chainParams.PowLimitBits,
|
|
Timestamp: time.Unix(prevNode.timestamp+1, 0),
|
|
},
|
|
}
|
|
|
|
coinbase := wire.NewMsgTx(wire.TxVersion)
|
|
coinbase.AddTxIn(&wire.TxIn{SignatureScript: []byte{}})
|
|
coinbase.AddTxOut(&wire.TxOut{Value: 0})
|
|
block.AddTransaction(coinbase)
|
|
block.Header.MerkleRoot = block.Transactions[0].TxHash()
|
|
|
|
btcBlock := btcutil.NewBlock(&block)
|
|
|
|
// The block without a height in coinbase is not considered valid.
|
|
err := chain.checkBlockContext(btcBlock, prevNode, BFNone)
|
|
require.Error(t, err)
|
|
|
|
// Make sure the error is in coinbase height record.
|
|
var rErr RuleError
|
|
require.ErrorAs(t, err, &rErr)
|
|
require.Equal(t, ErrMissingCoinbaseHeight, rErr.ErrorCode)
|
|
}
|
|
|
|
// TestRegtestAcceptsCoinbaseHeight ensures a properly encoded coinbase height
|
|
// passes contextual checks on regtest.
|
|
func TestRegtestAcceptsCoinbaseHeight(t *testing.T) {
|
|
chain := newFakeChain(&chaincfg.RegressionNetParams)
|
|
prevNode := chain.bestChain.Tip()
|
|
|
|
coinbaseScript, err := txscript.NewScriptBuilder().AddInt64(1).Script()
|
|
require.NoError(t, err)
|
|
|
|
block := wire.MsgBlock{
|
|
Header: wire.BlockHeader{
|
|
Version: 4,
|
|
PrevBlock: prevNode.hash,
|
|
Bits: chain.chainParams.PowLimitBits,
|
|
Timestamp: time.Unix(prevNode.timestamp+1, 0),
|
|
},
|
|
}
|
|
|
|
coinbase := wire.NewMsgTx(wire.TxVersion)
|
|
coinbase.AddTxIn(&wire.TxIn{SignatureScript: coinbaseScript})
|
|
coinbase.AddTxOut(&wire.TxOut{Value: 0})
|
|
block.AddTransaction(coinbase)
|
|
block.Header.MerkleRoot = block.Transactions[0].TxHash()
|
|
|
|
btcBlock := btcutil.NewBlock(&block)
|
|
|
|
// Make sure the block with the height in coinbase is considered valid.
|
|
require.NoError(t, chain.checkBlockContext(btcBlock, prevNode, BFNone))
|
|
}
|