Skip to content

Commit 730a170

Browse files
committed
multi: add testnet4 support
Adds support of the Bitcoin testnet version 4 chain. Reference: https://bips.dev/94/
1 parent 684d64a commit 730a170

20 files changed

+390
-20
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ btcutil/psbt/coverage.txt
4646
*.swo
4747
/.vim
4848

49+
.idea
50+
4951
# Binaries produced by "make build"
5052
/addblock
5153
/btcctl

blockchain/difficulty.go

+11-1
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,22 @@ func calcNextRequiredDifficulty(lastNode HeaderCtx, newBlockTime time.Time,
191191
adjustedTimespan = c.MaxRetargetTimespan()
192192
}
193193

194+
var oldTarget *big.Int
195+
// Special difficulty rule for Testnet4
196+
if c.ChainParams().EnforceBIP94 {
197+
// Here we use the first block of the difficulty period. This way
198+
// the real difficulty is always preserved in the first block as
199+
// it is not allowed to use the min-difficulty exception.
200+
oldTarget = CompactToBig(firstNode.Bits())
201+
} else {
202+
oldTarget = CompactToBig(lastNode.Bits())
203+
}
204+
194205
// Calculate new target difficulty as:
195206
// currentDifficulty * (adjustedTimespan / targetTimespan)
196207
// The result uses integer division which means it will be slightly
197208
// rounded down. Bitcoind also uses integer division to calculate this
198209
// result.
199-
oldTarget := CompactToBig(lastNode.Bits())
200210
newTarget := new(big.Int).Mul(oldTarget, big.NewInt(adjustedTimespan))
201211
targetTimeSpan := int64(c.ChainParams().TargetTimespan / time.Second)
202212
newTarget.Div(newTarget, big.NewInt(targetTimeSpan))

blockchain/error.go

+2
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,8 @@ const (
220220
// current chain tip. This is not a block validation rule, but is required
221221
// for block proposals submitted via getblocktemplate RPC.
222222
ErrPrevBlockNotBest
223+
224+
ErrTimewarpAttack
223225
)
224226

225227
// Map of ErrorCode values back to their constant names for pretty printing.

blockchain/validate.go

+31-11
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ const (
4646
// coinbaseHeightAllocSize is the amount of bytes that the
4747
// ScriptBuilder will allocate when validating the coinbase height.
4848
coinbaseHeightAllocSize = 5
49+
50+
// maxTimeWarp is a maximum number of seconds that the timestamp of the first
51+
// block of a difficulty adjustment period is allowed to
52+
// be earlier than the last block of the previous period (BIP94).
53+
maxTimeWarp = 600 * time.Second
4954
)
5055

5156
var (
@@ -85,7 +90,7 @@ func ShouldHaveSerializedBlockHeight(header *wire.BlockHeader) bool {
8590

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

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

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

692+
// The height of this block is one more than the referenced previous
693+
// block.
694+
blockHeight := prevNode.Height() + 1
695+
687696
fastAdd := flags&BFFastAdd == BFFastAdd
688697
if !fastAdd {
689698
// Ensure the difficulty specified in the block header matches
@@ -710,11 +719,22 @@ func CheckBlockHeaderContext(header *wire.BlockHeader, prevNode HeaderCtx,
710719
str = fmt.Sprintf(str, header.Timestamp, medianTime)
711720
return ruleError(ErrTimeTooOld, str)
712721
}
713-
}
714722

715-
// The height of this block is one more than the referenced previous
716-
// block.
717-
blockHeight := prevNode.Height() + 1
723+
// Testnet4 only: Check timestamp against prev for difficulty-adjustment
724+
// blocks to prevent timewarp attacks.
725+
if c.ChainParams().EnforceBIP94 {
726+
// Check timestamp for the first block of each difficulty adjustment
727+
// interval, except the genesis block.
728+
if blockHeight%c.BlocksPerRetarget() == 0 {
729+
prevBlockTimestamp := time.Unix(prevNode.Timestamp(), 0)
730+
if header.Timestamp.Before(prevBlockTimestamp.Add(-maxTimeWarp)) {
731+
str := "block's timestamp %v is too early on diff adjustment block %v"
732+
str = fmt.Sprintf(str, header.Timestamp, prevBlockTimestamp)
733+
return ruleError(ErrTimewarpAttack, str)
734+
}
735+
}
736+
}
737+
}
718738

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

764784
// checkBlockContext performs several validation checks on the block which depend
765-
// on its position within the block chain.
785+
// on its position within the blockchain.
766786
//
767787
// The flags modify the behavior of this function as follows:
768788
// - BFFastAdd: The transaction are not checked to see if they are finalized
@@ -1067,7 +1087,7 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block, vi
10671087
//
10681088
// In addition, as of BIP0034, duplicate coinbases are no longer
10691089
// possible due to its requirement for including the block height in the
1070-
// coinbase and thus it is no longer possible to create transactions
1090+
// coinbase, and thus it is no longer possible to create transactions
10711091
// that 'overwrite' older ones. Therefore, only enforce the rule if
10721092
// BIP0034 is not yet active. This is a useful optimization because the
10731093
// BIP0030 check is expensive since it involves a ton of cache misses in
@@ -1230,7 +1250,7 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block, vi
12301250
if csvState == ThresholdActive {
12311251
// If the CSV soft-fork is now active, then modify the
12321252
// scriptFlags to ensure that the CSV op code is properly
1233-
// validated during the script checks bleow.
1253+
// validated during the script checks below.
12341254
scriptFlags |= txscript.ScriptVerifyCheckSequenceVerify
12351255

12361256
// We obtain the MTP of the *previous* block in order to

btcd.go

+5
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ func btcdMain(serverChan chan<- *server) error {
6464
// Show version at startup.
6565
btcdLog.Infof("Version %s", version())
6666

67+
if cfg.TestNet3 {
68+
btcdLog.Warn("Support for testnet3 is deprecated and will be removed in an upcoming release. " +
69+
"Consider switching to testnet4.")
70+
}
71+
6772
// Enable http profiling server if requested.
6873
if cfg.Profile != "" {
6974
go func() {

chaincfg/genesis.go

+71
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,77 @@ var testNet3GenesisBlock = wire.MsgBlock{
143143
Transactions: []*wire.MsgTx{&genesisCoinbaseTx},
144144
}
145145

146+
// testNet4GenesisTx is the transaction for the genesis blocks for test network (version 4).
147+
var testNet4GenesisTx = wire.MsgTx{
148+
Version: 1,
149+
TxIn: []*wire.TxIn{
150+
{
151+
PreviousOutPoint: wire.OutPoint{
152+
Hash: chainhash.Hash{},
153+
Index: 0xffffffff,
154+
},
155+
SignatureScript: []byte{
156+
// Message: `03/May/2024 000000000000000000001ebd58c244970b3aa9d783bb001011fbe8ea8e98e00e`
157+
0x4, 0xff, 0xff, 0x0, 0x1d, 0x1, 0x4, 0x4c,
158+
0x4c, 0x30, 0x33, 0x2f, 0x4d, 0x61, 0x79, 0x2f,
159+
0x32, 0x30, 0x32, 0x34, 0x20, 0x30, 0x30, 0x30,
160+
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
161+
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
162+
0x30, 0x31, 0x65, 0x62, 0x64, 0x35, 0x38, 0x63,
163+
0x32, 0x34, 0x34, 0x39, 0x37, 0x30, 0x62, 0x33,
164+
0x61, 0x61, 0x39, 0x64, 0x37, 0x38, 0x33, 0x62,
165+
0x62, 0x30, 0x30, 0x31, 0x30, 0x31, 0x31, 0x66,
166+
0x62, 0x65, 0x38, 0x65, 0x61, 0x38, 0x65, 0x39,
167+
0x38, 0x65, 0x30, 0x30, 0x65},
168+
Sequence: 0xffffffff,
169+
},
170+
},
171+
TxOut: []*wire.TxOut{
172+
{
173+
Value: 0x12a05f200,
174+
PkScript: []byte{
175+
0x21, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
176+
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
177+
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
178+
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
179+
0x0, 0x0, 0xac},
180+
},
181+
},
182+
LockTime: 0,
183+
}
184+
185+
// testNet4GenesisHash is the hash of the first block in the block chain for the
186+
// test network (version 4).
187+
var testNet4GenesisHash = chainhash.Hash([chainhash.HashSize]byte{
188+
0x43, 0xf0, 0x8b, 0xda, 0xb0, 0x50, 0xe3, 0x5b,
189+
0x56, 0x7c, 0x86, 0x4b, 0x91, 0xf4, 0x7f, 0x50,
190+
0xae, 0x72, 0x5a, 0xe2, 0xde, 0x53, 0xbc, 0xfb,
191+
0xba, 0xf2, 0x84, 0xda, 0x00, 0x00, 0x00, 0x00})
192+
193+
// testNet4GenesisMerkleRoot is the hash of the first transaction in the genesis
194+
// block for the test network (version 4). It is the same as the merkle root
195+
// for the main network.
196+
var testNet4GenesisMerkleRoot = chainhash.Hash([chainhash.HashSize]byte{ // Make go vet happy.
197+
0x4e, 0x7b, 0x2b, 0x91, 0x28, 0xfe, 0x02, 0x91,
198+
0xdb, 0x06, 0x93, 0xaf, 0x2a, 0xe4, 0x18, 0xb7,
199+
0x67, 0xe6, 0x57, 0xcd, 0x40, 0x7e, 0x80, 0xcb,
200+
0x14, 0x34, 0x22, 0x1e, 0xae, 0xa7, 0xa0, 0x7a,
201+
})
202+
203+
// testNet4GenesisBlock defines the genesis block of the block chain which
204+
// serves as the public transaction ledger for the test network (version 3).
205+
var testNet4GenesisBlock = wire.MsgBlock{
206+
Header: wire.BlockHeader{
207+
Version: 1,
208+
PrevBlock: chainhash.Hash{}, // 0000000000000000000000000000000000000000000000000000000000000000
209+
MerkleRoot: testNet4GenesisMerkleRoot, // 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b
210+
Timestamp: time.Unix(1714777860, 0), // 2024-05-03 23:11:00 +0000 UTC
211+
Bits: 0x1d00ffff, // 486604799 [00000000ffff0000000000000000000000000000000000000000000000000000]
212+
Nonce: 0x17780cbb, // 393743547
213+
},
214+
Transactions: []*wire.MsgTx{&testNet4GenesisTx},
215+
}
216+
146217
// simNetGenesisHash is the hash of the first block in the block chain for the
147218
// simulation test network.
148219
var simNetGenesisHash = chainhash.Hash([chainhash.HashSize]byte{ // Make go vet happy.

chaincfg/genesis_test.go

+67
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package chaincfg
66

77
import (
88
"bytes"
9+
"github.com/stretchr/testify/require"
910
"testing"
1011

1112
"github.com/davecgh/go-spew/spew"
@@ -91,6 +92,34 @@ func TestTestNet3GenesisBlock(t *testing.T) {
9192
}
9293
}
9394

95+
// TestTestNet4GenesisBlock tests the genesis block of the test network (version
96+
// 4) for validity by checking the encoded bytes and hashes.
97+
func TestTestNet4GenesisBlock(t *testing.T) {
98+
// Encode the genesis block to raw bytes.
99+
var buf bytes.Buffer
100+
err := TestNet4Params.GenesisBlock.Serialize(&buf)
101+
if err != nil {
102+
t.Fatalf("TestTestNet4GenesisBlock: %v", err)
103+
}
104+
105+
// Ensure the encoded block matches the expected bytes.
106+
if !bytes.Equal(buf.Bytes(), testNet4GenesisBlockBytes) {
107+
t.Fatalf("TestTestNet4GenesisBlock: Genesis block does not "+
108+
"appear valid - got %v, want %v",
109+
spew.Sdump(buf.Bytes()),
110+
spew.Sdump(testNet4GenesisBlockBytes))
111+
}
112+
113+
// Check hash of the block against expected hash.
114+
hash := TestNet4Params.GenesisBlock.BlockHash()
115+
if !TestNet4Params.GenesisHash.IsEqual(&hash) {
116+
t.Fatalf("TestTestNet4GenesisBlock: Genesis block hash does "+
117+
"not appear valid - got %v, want %v", spew.Sdump(hash),
118+
spew.Sdump(TestNet4Params.GenesisHash))
119+
}
120+
require.Equal(t, "00000000da84f2bafbbc53dee25a72ae507ff4914b867c565be350b0da8bf043", hash.String())
121+
}
122+
94123
// TestSimNetGenesisBlock tests the genesis block of the simulation test network
95124
// for validity by checking the encoded bytes and hashes.
96125
func TestSimNetGenesisBlock(t *testing.T) {
@@ -268,6 +297,44 @@ var testNet3GenesisBlockBytes = []byte{
268297
0xac, 0x00, 0x00, 0x00, 0x00, /* |.....| */
269298
}
270299

300+
// testNet4GenesisBlockBytes are the wire encoded bytes for the genesis block of
301+
// the test network (version 4)
302+
var testNet4GenesisBlockBytes = []byte{
303+
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
304+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
305+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
306+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
307+
0x00, 0x00, 0x00, 0x00, 0x4e, 0x7b, 0x2b, 0x91,
308+
0x28, 0xfe, 0x02, 0x91, 0xdb, 0x06, 0x93, 0xaf,
309+
0x2a, 0xe4, 0x18, 0xb7, 0x67, 0xe6, 0x57, 0xcd,
310+
0x40, 0x7e, 0x80, 0xcb, 0x14, 0x34, 0x22, 0x1e,
311+
0xae, 0xa7, 0xa0, 0x7a, 0x04, 0x6f, 0x35, 0x66,
312+
0xff, 0xff, 0x00, 0x1d, 0xbb, 0x0c, 0x78, 0x17,
313+
0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
314+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
315+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
316+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
317+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
318+
0xff, 0xff, 0x55, 0x04, 0xff, 0xff, 0x00, 0x1d,
319+
0x01, 0x04, 0x4c, 0x4c, 0x30, 0x33, 0x2f, 0x4d,
320+
0x61, 0x79, 0x2f, 0x32, 0x30, 0x32, 0x34, 0x20,
321+
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
322+
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
323+
0x30, 0x30, 0x30, 0x30, 0x31, 0x65, 0x62, 0x64,
324+
0x35, 0x38, 0x63, 0x32, 0x34, 0x34, 0x39, 0x37,
325+
0x30, 0x62, 0x33, 0x61, 0x61, 0x39, 0x64, 0x37,
326+
0x38, 0x33, 0x62, 0x62, 0x30, 0x30, 0x31, 0x30,
327+
0x31, 0x31, 0x66, 0x62, 0x65, 0x38, 0x65, 0x61,
328+
0x38, 0x65, 0x39, 0x38, 0x65, 0x30, 0x30, 0x65,
329+
0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0xf2, 0x05,
330+
0x2a, 0x01, 0x00, 0x00, 0x00, 0x23, 0x21, 0x00,
331+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
332+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
333+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
334+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
335+
0xac, 0x00, 0x00, 0x00, 0x00,
336+
}
337+
271338
// simNetGenesisBlockBytes are the wire encoded bytes for the genesis block of
272339
// the simulation test network as of protocol version 70002.
273340
var simNetGenesisBlockBytes = []byte{

0 commit comments

Comments
 (0)