Skip to content

Commit

Permalink
multi: add testnet4 support
Browse files Browse the repository at this point in the history
Adds support of the Bitcoin testnet version 4 chain.
Reference: https://bips.dev/94/
  • Loading branch information
bullet-tooth committed Dec 13, 2024
1 parent 684d64a commit 730a170
Show file tree
Hide file tree
Showing 20 changed files with 390 additions and 20 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ btcutil/psbt/coverage.txt
*.swo
/.vim

.idea

# Binaries produced by "make build"
/addblock
/btcctl
Expand Down
12 changes: 11 additions & 1 deletion blockchain/difficulty.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,12 +191,22 @@ func calcNextRequiredDifficulty(lastNode HeaderCtx, newBlockTime time.Time,
adjustedTimespan = c.MaxRetargetTimespan()
}

var oldTarget *big.Int
// Special difficulty rule for Testnet4
if c.ChainParams().EnforceBIP94 {
// Here we use the first block of the difficulty period. This way
// the real difficulty is always preserved in the first block as
// it is not allowed to use the min-difficulty exception.
oldTarget = CompactToBig(firstNode.Bits())
} else {
oldTarget = CompactToBig(lastNode.Bits())
}

// Calculate new target difficulty as:
// currentDifficulty * (adjustedTimespan / targetTimespan)
// The result uses integer division which means it will be slightly
// rounded down. Bitcoind also uses integer division to calculate this
// result.
oldTarget := CompactToBig(lastNode.Bits())
newTarget := new(big.Int).Mul(oldTarget, big.NewInt(adjustedTimespan))
targetTimeSpan := int64(c.ChainParams().TargetTimespan / time.Second)
newTarget.Div(newTarget, big.NewInt(targetTimeSpan))
Expand Down
2 changes: 2 additions & 0 deletions blockchain/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,8 @@ const (
// current chain tip. This is not a block validation rule, but is required
// for block proposals submitted via getblocktemplate RPC.
ErrPrevBlockNotBest

ErrTimewarpAttack
)

// Map of ErrorCode values back to their constant names for pretty printing.
Expand Down
42 changes: 31 additions & 11 deletions blockchain/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ const (
// coinbaseHeightAllocSize is the amount of bytes that the
// ScriptBuilder will allocate when validating the coinbase height.
coinbaseHeightAllocSize = 5

// maxTimeWarp is a maximum number of seconds that the timestamp of the first
// block of a difficulty adjustment period is allowed to
// be earlier than the last block of the previous period (BIP94).
maxTimeWarp = 600 * time.Second
)

var (
Expand Down Expand Up @@ -85,7 +90,7 @@ func ShouldHaveSerializedBlockHeight(header *wire.BlockHeader) bool {

// IsCoinBaseTx determines whether or not a transaction is a coinbase. A coinbase
// is a special transaction created by miners that has no inputs. This is
// represented in the block chain by a transaction with a single input that has
// represented in the blockchain by a transaction with a single input that has
// a previous output transaction index set to the maximum value along with a
// zero hash.
//
Expand All @@ -109,7 +114,7 @@ func IsCoinBaseTx(msgTx *wire.MsgTx) bool {

// IsCoinBase determines whether or not a transaction is a coinbase. A coinbase
// is a special transaction created by miners that has no inputs. This is
// represented in the block chain by a transaction with a single input that has
// represented in the blockchain by a transaction with a single input that has
// a previous output transaction index set to the maximum value along with a
// zero hash.
//
Expand Down Expand Up @@ -446,7 +451,7 @@ func CheckBlockHeaderSanity(header *wire.BlockHeader, powLimit *big.Int,
// A block timestamp must not have a greater precision than one second.
// This check is necessary because Go time.Time values support
// nanosecond precision whereas the consensus rules only apply to
// seconds and it's much nicer to deal with standard Go time values
// seconds, and it's much nicer to deal with standard Go time values
// instead of converting to seconds everywhere.
if !header.Timestamp.Equal(time.Unix(header.Timestamp.Unix(), 0)) {
str := fmt.Sprintf("block timestamp of %v has a higher "+
Expand Down Expand Up @@ -669,7 +674,7 @@ func compareScript(height int32, script []byte) error {
}

// CheckBlockHeaderContext performs several validation checks on the block header
// which depend on its position within the block chain.
// which depend on its position within the blockchain.
//
// The flags modify the behavior of this function as follows:
// - BFFastAdd: All checks except those involving comparing the header against
Expand All @@ -684,6 +689,10 @@ func compareScript(height int32, script []byte) error {
func CheckBlockHeaderContext(header *wire.BlockHeader, prevNode HeaderCtx,
flags BehaviorFlags, c ChainCtx, skipCheckpoint bool) error {

// The height of this block is one more than the referenced previous
// block.
blockHeight := prevNode.Height() + 1

fastAdd := flags&BFFastAdd == BFFastAdd
if !fastAdd {
// Ensure the difficulty specified in the block header matches
Expand All @@ -710,11 +719,22 @@ func CheckBlockHeaderContext(header *wire.BlockHeader, prevNode HeaderCtx,
str = fmt.Sprintf(str, header.Timestamp, medianTime)
return ruleError(ErrTimeTooOld, str)
}
}

// The height of this block is one more than the referenced previous
// block.
blockHeight := prevNode.Height() + 1
// Testnet4 only: Check timestamp against prev for difficulty-adjustment
// blocks to prevent timewarp attacks.
if c.ChainParams().EnforceBIP94 {
// Check timestamp for the first block of each difficulty adjustment
// interval, except the genesis block.
if blockHeight%c.BlocksPerRetarget() == 0 {
prevBlockTimestamp := time.Unix(prevNode.Timestamp(), 0)
if header.Timestamp.Before(prevBlockTimestamp.Add(-maxTimeWarp)) {
str := "block's timestamp %v is too early on diff adjustment block %v"
str = fmt.Sprintf(str, header.Timestamp, prevBlockTimestamp)
return ruleError(ErrTimewarpAttack, str)
}
}
}
}

// Reject outdated block versions once a majority of the network
// has upgraded. These were originally voted on by BIP0034,
Expand Down Expand Up @@ -762,7 +782,7 @@ func CheckBlockHeaderContext(header *wire.BlockHeader, prevNode HeaderCtx,
}

// checkBlockContext performs several validation checks on the block which depend
// on its position within the block chain.
// on its position within the blockchain.
//
// The flags modify the behavior of this function as follows:
// - BFFastAdd: The transaction are not checked to see if they are finalized
Expand Down Expand Up @@ -1067,7 +1087,7 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block, vi
//
// 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
// 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
Expand Down Expand Up @@ -1230,7 +1250,7 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block, vi
if csvState == ThresholdActive {
// If the CSV soft-fork is now active, then modify the
// scriptFlags to ensure that the CSV op code is properly
// validated during the script checks bleow.
// validated during the script checks below.
scriptFlags |= txscript.ScriptVerifyCheckSequenceVerify

// We obtain the MTP of the *previous* block in order to
Expand Down
5 changes: 5 additions & 0 deletions btcd.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ func btcdMain(serverChan chan<- *server) error {
// Show version at startup.
btcdLog.Infof("Version %s", version())

if cfg.TestNet3 {
btcdLog.Warn("Support for testnet3 is deprecated and will be removed in an upcoming release. " +
"Consider switching to testnet4.")
}

// Enable http profiling server if requested.
if cfg.Profile != "" {
go func() {
Expand Down
71 changes: 71 additions & 0 deletions chaincfg/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,77 @@ var testNet3GenesisBlock = wire.MsgBlock{
Transactions: []*wire.MsgTx{&genesisCoinbaseTx},
}

// testNet4GenesisTx is the transaction for the genesis blocks for test network (version 4).
var testNet4GenesisTx = wire.MsgTx{
Version: 1,
TxIn: []*wire.TxIn{
{
PreviousOutPoint: wire.OutPoint{
Hash: chainhash.Hash{},
Index: 0xffffffff,
},
SignatureScript: []byte{
// Message: `03/May/2024 000000000000000000001ebd58c244970b3aa9d783bb001011fbe8ea8e98e00e`
0x4, 0xff, 0xff, 0x0, 0x1d, 0x1, 0x4, 0x4c,
0x4c, 0x30, 0x33, 0x2f, 0x4d, 0x61, 0x79, 0x2f,
0x32, 0x30, 0x32, 0x34, 0x20, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x31, 0x65, 0x62, 0x64, 0x35, 0x38, 0x63,
0x32, 0x34, 0x34, 0x39, 0x37, 0x30, 0x62, 0x33,
0x61, 0x61, 0x39, 0x64, 0x37, 0x38, 0x33, 0x62,
0x62, 0x30, 0x30, 0x31, 0x30, 0x31, 0x31, 0x66,
0x62, 0x65, 0x38, 0x65, 0x61, 0x38, 0x65, 0x39,
0x38, 0x65, 0x30, 0x30, 0x65},
Sequence: 0xffffffff,
},
},
TxOut: []*wire.TxOut{
{
Value: 0x12a05f200,
PkScript: []byte{
0x21, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0xac},
},
},
LockTime: 0,
}

// testNet4GenesisHash is the hash of the first block in the block chain for the
// test network (version 4).
var testNet4GenesisHash = chainhash.Hash([chainhash.HashSize]byte{
0x43, 0xf0, 0x8b, 0xda, 0xb0, 0x50, 0xe3, 0x5b,
0x56, 0x7c, 0x86, 0x4b, 0x91, 0xf4, 0x7f, 0x50,
0xae, 0x72, 0x5a, 0xe2, 0xde, 0x53, 0xbc, 0xfb,
0xba, 0xf2, 0x84, 0xda, 0x00, 0x00, 0x00, 0x00})

// testNet4GenesisMerkleRoot is the hash of the first transaction in the genesis
// block for the test network (version 4). It is the same as the merkle root
// for the main network.
var testNet4GenesisMerkleRoot = chainhash.Hash([chainhash.HashSize]byte{ // Make go vet happy.
0x4e, 0x7b, 0x2b, 0x91, 0x28, 0xfe, 0x02, 0x91,
0xdb, 0x06, 0x93, 0xaf, 0x2a, 0xe4, 0x18, 0xb7,
0x67, 0xe6, 0x57, 0xcd, 0x40, 0x7e, 0x80, 0xcb,
0x14, 0x34, 0x22, 0x1e, 0xae, 0xa7, 0xa0, 0x7a,
})

// testNet4GenesisBlock defines the genesis block of the block chain which
// serves as the public transaction ledger for the test network (version 3).
var testNet4GenesisBlock = wire.MsgBlock{
Header: wire.BlockHeader{
Version: 1,
PrevBlock: chainhash.Hash{}, // 0000000000000000000000000000000000000000000000000000000000000000
MerkleRoot: testNet4GenesisMerkleRoot, // 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b
Timestamp: time.Unix(1714777860, 0), // 2024-05-03 23:11:00 +0000 UTC
Bits: 0x1d00ffff, // 486604799 [00000000ffff0000000000000000000000000000000000000000000000000000]
Nonce: 0x17780cbb, // 393743547
},
Transactions: []*wire.MsgTx{&testNet4GenesisTx},
}

// simNetGenesisHash is the hash of the first block in the block chain for the
// simulation test network.
var simNetGenesisHash = chainhash.Hash([chainhash.HashSize]byte{ // Make go vet happy.
Expand Down
67 changes: 67 additions & 0 deletions chaincfg/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package chaincfg

import (
"bytes"
"github.com/stretchr/testify/require"
"testing"

"github.com/davecgh/go-spew/spew"
Expand Down Expand Up @@ -91,6 +92,34 @@ func TestTestNet3GenesisBlock(t *testing.T) {
}
}

// TestTestNet4GenesisBlock tests the genesis block of the test network (version
// 4) for validity by checking the encoded bytes and hashes.
func TestTestNet4GenesisBlock(t *testing.T) {
// Encode the genesis block to raw bytes.
var buf bytes.Buffer
err := TestNet4Params.GenesisBlock.Serialize(&buf)
if err != nil {
t.Fatalf("TestTestNet4GenesisBlock: %v", err)
}

// Ensure the encoded block matches the expected bytes.
if !bytes.Equal(buf.Bytes(), testNet4GenesisBlockBytes) {
t.Fatalf("TestTestNet4GenesisBlock: Genesis block does not "+
"appear valid - got %v, want %v",
spew.Sdump(buf.Bytes()),
spew.Sdump(testNet4GenesisBlockBytes))
}

// Check hash of the block against expected hash.
hash := TestNet4Params.GenesisBlock.BlockHash()
if !TestNet4Params.GenesisHash.IsEqual(&hash) {
t.Fatalf("TestTestNet4GenesisBlock: Genesis block hash does "+
"not appear valid - got %v, want %v", spew.Sdump(hash),
spew.Sdump(TestNet4Params.GenesisHash))
}
require.Equal(t, "00000000da84f2bafbbc53dee25a72ae507ff4914b867c565be350b0da8bf043", hash.String())
}

// TestSimNetGenesisBlock tests the genesis block of the simulation test network
// for validity by checking the encoded bytes and hashes.
func TestSimNetGenesisBlock(t *testing.T) {
Expand Down Expand Up @@ -268,6 +297,44 @@ var testNet3GenesisBlockBytes = []byte{
0xac, 0x00, 0x00, 0x00, 0x00, /* |.....| */
}

// testNet4GenesisBlockBytes are the wire encoded bytes for the genesis block of
// the test network (version 4)
var testNet4GenesisBlockBytes = []byte{
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x4e, 0x7b, 0x2b, 0x91,
0x28, 0xfe, 0x02, 0x91, 0xdb, 0x06, 0x93, 0xaf,
0x2a, 0xe4, 0x18, 0xb7, 0x67, 0xe6, 0x57, 0xcd,
0x40, 0x7e, 0x80, 0xcb, 0x14, 0x34, 0x22, 0x1e,
0xae, 0xa7, 0xa0, 0x7a, 0x04, 0x6f, 0x35, 0x66,
0xff, 0xff, 0x00, 0x1d, 0xbb, 0x0c, 0x78, 0x17,
0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
0xff, 0xff, 0x55, 0x04, 0xff, 0xff, 0x00, 0x1d,
0x01, 0x04, 0x4c, 0x4c, 0x30, 0x33, 0x2f, 0x4d,
0x61, 0x79, 0x2f, 0x32, 0x30, 0x32, 0x34, 0x20,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x31, 0x65, 0x62, 0x64,
0x35, 0x38, 0x63, 0x32, 0x34, 0x34, 0x39, 0x37,
0x30, 0x62, 0x33, 0x61, 0x61, 0x39, 0x64, 0x37,
0x38, 0x33, 0x62, 0x62, 0x30, 0x30, 0x31, 0x30,
0x31, 0x31, 0x66, 0x62, 0x65, 0x38, 0x65, 0x61,
0x38, 0x65, 0x39, 0x38, 0x65, 0x30, 0x30, 0x65,
0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0xf2, 0x05,
0x2a, 0x01, 0x00, 0x00, 0x00, 0x23, 0x21, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xac, 0x00, 0x00, 0x00, 0x00,
}

// simNetGenesisBlockBytes are the wire encoded bytes for the genesis block of
// the simulation test network as of protocol version 70002.
var simNetGenesisBlockBytes = []byte{
Expand Down
Loading

0 comments on commit 730a170

Please sign in to comment.