Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add testnet4 chain support #2274

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use full sentences, including punctuation, for comments. Also, if a comment isn't at the beginning of a block, it's nice to have an empty line before it to have some vertical breathing room.
Finally, else is kind of discouraged in Go if it can be written in a different way, as it often leads to less readable code.
So I'd write this the following way:

	// Special difficulty rule for Testnet4
	oldTarget := CompactToBig(lastNode.Bits())
	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())
	}

Of course the assumption here is that CompactToBig(lastNode.Bits()) isn't computationally expensive to wanting to avoid calling it twice in the worst case.

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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: missing Godoc comment.

)

// 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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert these. This specifically refer to the chain of blocks, not the package name. IMO "Blockchain" is a marketing term used by people trying to sell you something that isn't Bitcoin.

// 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)
}
}
}
}
Comment on lines +725 to +737
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: line length. Also, sounds like this whole block could be extracted into a standalone function. Something like checkBip94().


// 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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I agree with that whole block.

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"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: please sort this with the block below where non-std-lib imports go.

"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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should use require.NoError() and require.Equal() for new test code.

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
Loading