This commit is contained in:
Olaoluwa Osuntokun 2025-03-12 13:24:30 -05:00 committed by GitHub
commit d307d3a150
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 921 additions and 26 deletions

4
.gitignore vendored
View file

@ -46,6 +46,9 @@ btcutil/psbt/coverage.txt
*.swo *.swo
/.vim /.vim
#IDE
.idea
# Binaries produced by "make build" # Binaries produced by "make build"
/addblock /addblock
/btcctl /btcctl
@ -54,3 +57,4 @@ btcutil/psbt/coverage.txt
/gencerts /gencerts
.DS_Store .DS_Store
.aider*

View file

@ -191,12 +191,20 @@ func calcNextRequiredDifficulty(lastNode HeaderCtx, newBlockTime time.Time,
adjustedTimespan = c.MaxRetargetTimespan() adjustedTimespan = c.MaxRetargetTimespan()
} }
// 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())
}
// Calculate new target difficulty as: // Calculate new target difficulty as:
// currentDifficulty * (adjustedTimespan / targetTimespan) // currentDifficulty * (adjustedTimespan / targetTimespan)
// The result uses integer division which means it will be slightly // The result uses integer division which means it will be slightly
// rounded down. Bitcoind also uses integer division to calculate this // rounded down. Bitcoind also uses integer division to calculate this
// result. // result.
oldTarget := CompactToBig(lastNode.Bits())
newTarget := new(big.Int).Mul(oldTarget, big.NewInt(adjustedTimespan)) newTarget := new(big.Int).Mul(oldTarget, big.NewInt(adjustedTimespan))
targetTimeSpan := int64(c.ChainParams().TargetTimespan / time.Second) targetTimeSpan := int64(c.ChainParams().TargetTimespan / time.Second)
newTarget.Div(newTarget, big.NewInt(targetTimeSpan)) newTarget.Div(newTarget, big.NewInt(targetTimeSpan))

View file

@ -220,6 +220,10 @@ const (
// current chain tip. This is not a block validation rule, but is required // current chain tip. This is not a block validation rule, but is required
// for block proposals submitted via getblocktemplate RPC. // for block proposals submitted via getblocktemplate RPC.
ErrPrevBlockNotBest ErrPrevBlockNotBest
// ErrTimewarpAttack indicates a timewarp attack i.e.
// when block's timestamp is too early on diff adjustment block.
ErrTimewarpAttack
) )
// Map of ErrorCode values back to their constant names for pretty printing. // Map of ErrorCode values back to their constant names for pretty printing.

View file

@ -102,6 +102,11 @@ type thresholdConditionChecker interface {
// not the bit associated with the condition is set, but can be more // not the bit associated with the condition is set, but can be more
// complex as needed. // complex as needed.
Condition(*blockNode) (bool, error) Condition(*blockNode) (bool, error)
// ForceActive returns if the deployment should be forced to transition
// to the active state. This is useful on certain testnet, where we
// we'd like for a deployment to always be active.
ForceActive(*blockNode) bool
} }
// thresholdStateCache provides a type to cache the threshold states of each // thresholdStateCache provides a type to cache the threshold states of each
@ -279,7 +284,17 @@ func thresholdStateTransition(state ThresholdState, prevNode *blockNode,
// threshold states for previous windows are only calculated once. // threshold states for previous windows are only calculated once.
// //
// This function MUST be called with the chain state lock held (for writes). // This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) thresholdState(prevNode *blockNode, checker thresholdConditionChecker, cache *thresholdStateCache) (ThresholdState, error) { func (b *BlockChain) thresholdState(prevNode *blockNode,
checker thresholdConditionChecker,
cache *thresholdStateCache) (ThresholdState, error) {
// If the deployment has a nonzero AlwaysActiveHeight and the next
// blocks height is at or above that threshold, then force the state
// to Active.
if checker.ForceActive(prevNode) {
return ThresholdActive, nil
}
// The threshold state for the window that contains the genesis block is // The threshold state for the window that contains the genesis block is
// defined by definition. // defined by definition.
confirmationWindow := int32(checker.MinerConfirmationWindow()) confirmationWindow := int32(checker.MinerConfirmationWindow())

View file

@ -175,6 +175,10 @@ func (c customDeploymentChecker) Condition(_ *blockNode) (bool, error) {
return c.conditionTrue, nil return c.conditionTrue, nil
} }
func (c customDeploymentChecker) ForceActive(_ *blockNode) bool {
return false
}
// TestThresholdStateTransition tests that the thresholdStateTransition // TestThresholdStateTransition tests that the thresholdStateTransition
// properly implements the BIP 009 state machine, along with the speedy trial // properly implements the BIP 009 state machine, along with the speedy trial
// augments. // augments.

View file

@ -46,6 +46,11 @@ const (
// coinbaseHeightAllocSize is the amount of bytes that the // coinbaseHeightAllocSize is the amount of bytes that the
// ScriptBuilder will allocate when validating the coinbase height. // ScriptBuilder will allocate when validating the coinbase height.
coinbaseHeightAllocSize = 5 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 ( var (
@ -684,6 +689,12 @@ func compareScript(height int32, script []byte) error {
func CheckBlockHeaderContext(header *wire.BlockHeader, prevNode HeaderCtx, func CheckBlockHeaderContext(header *wire.BlockHeader, prevNode HeaderCtx,
flags BehaviorFlags, c ChainCtx, skipCheckpoint bool) error { flags BehaviorFlags, c ChainCtx, skipCheckpoint bool) error {
// The height of this block is one more than the referenced previous
// block.
blockHeight := prevNode.Height() + 1
params := c.ChainParams()
fastAdd := flags&BFFastAdd == BFFastAdd fastAdd := flags&BFFastAdd == BFFastAdd
if !fastAdd { if !fastAdd {
// Ensure the difficulty specified in the block header matches // Ensure the difficulty specified in the block header matches
@ -710,16 +721,24 @@ func CheckBlockHeaderContext(header *wire.BlockHeader, prevNode HeaderCtx,
str = fmt.Sprintf(str, header.Timestamp, medianTime) str = fmt.Sprintf(str, header.Timestamp, medianTime)
return ruleError(ErrTimeTooOld, str) return ruleError(ErrTimeTooOld, str)
} }
}
// The height of this block is one more than the referenced previous // Testnet4 only: Check timestamp against prev for
// block. // difficulty-adjustment blocks to prevent timewarp attacks.
blockHeight := prevNode.Height() + 1 if params.EnforceBIP94 {
err := assertNoTimeWarp(
blockHeight, c.BlocksPerRetarget(),
header.Timestamp,
time.Unix(prevNode.Timestamp(), 0),
)
if err != nil {
return err
}
}
}
// Reject outdated block versions once a majority of the network // Reject outdated block versions once a majority of the network
// has upgraded. These were originally voted on by BIP0034, // has upgraded. These were originally voted on by BIP0034,
// BIP0065, and BIP0066. // BIP0065, and BIP0066.
params := c.ChainParams()
if header.Version < 2 && blockHeight >= params.BIP0034Height || if header.Version < 2 && blockHeight >= params.BIP0034Height ||
header.Version < 3 && blockHeight >= params.BIP0066Height || header.Version < 3 && blockHeight >= params.BIP0066Height ||
header.Version < 4 && blockHeight >= params.BIP0065Height { header.Version < 4 && blockHeight >= params.BIP0065Height {
@ -761,6 +780,30 @@ func CheckBlockHeaderContext(header *wire.BlockHeader, prevNode HeaderCtx,
return nil return nil
} }
// assertNoTimeWarp checks the timestamp of the block against the previous
// block's timestamp for the first block of each difficulty adjustment interval
// to prevent timewarp attacks. This is defined in BIP-0094.
func assertNoTimeWarp(blockHeight, blocksPerReTarget int32, headerTimestamp,
prevBlockTimestamp time.Time) error {
// If this isn't the first block of the difficulty adjustment interval,
// then we can exit early.
if blockHeight%blocksPerReTarget != 0 {
return nil
}
// Check timestamp for the first block of each difficulty adjustment
// interval, except the genesis block.
if headerTimestamp.Before(prevBlockTimestamp.Add(-maxTimeWarp)) {
str := "block's timestamp %v is too early on diff adjustment " +
"block %v"
str = fmt.Sprintf(str, headerTimestamp, prevBlockTimestamp)
return ruleError(ErrTimewarpAttack, str)
}
return nil
}
// checkBlockContext performs several validation checks on the block which depend // checkBlockContext performs several validation checks on the block which depend
// on its position within the block chain. // on its position within the block chain.
// //
@ -1230,7 +1273,7 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block, vi
if csvState == ThresholdActive { if csvState == ThresholdActive {
// If the CSV soft-fork is now active, then modify the // If the CSV soft-fork is now active, then modify the
// scriptFlags to ensure that the CSV op code is properly // 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 scriptFlags |= txscript.ScriptVerifyCheckSequenceVerify
// We obtain the MTP of the *previous* block in order to // We obtain the MTP of the *previous* block in order to

View file

@ -0,0 +1,339 @@
package blockchain
import (
"testing"
"time"
"github.com/stretchr/testify/require"
"pgregory.net/rapid"
)
// TestAssertNoTimeWarpProperties uses property-based testing to verify that
// the assertNoTimeWarp function correctly implements the BIP-94 rule. This
// helps catch edge cases that might be missed with regular unit tests.
func TestAssertNoTimeWarpProperties(t *testing.T) {
t.Parallel()
// Define constant for blocks per retarget (similar to Bitcoin's 2016).
const blocksPerRetarget = 2016
// Rapid test that only the retarget blocks are checked.
t.Run("only_checks_retarget_blocks", rapid.MakeCheck(func(t *rapid.T) {
// Generate block height that is not a retarget block.
height := rapid.Int32Range(
1, 1000000,
).Filter(func(h int32) bool {
return h%blocksPerRetarget != 0
}).Draw(t, "height")
// Even with an "extreme" time warp, the function should return
// nil because it only applies the check to retarget blocks.
// Define headerTime as the Unix epoch start.
headerTime := time.Unix(0, 0)
// Define prevBlockTime as the current time (creating an
// extreme gap).
prevBlockTime := time.Now()
err := assertNoTimeWarp(
height, blocksPerRetarget, headerTime, prevBlockTime,
)
require.NoError(
t, err, "expected nil error for non-retarget block "+
"but got: %v.", err,
)
}))
// Rapid test that retarget blocks with acceptable timestamps pass
// validation.
t.Run("valid_timestamps_pass", rapid.MakeCheck(func(t *rapid.T) {
// Generate block height that is a retarget block
height := rapid.Int32Range(blocksPerRetarget, 1000000).
Filter(func(h int32) bool {
return h%blocksPerRetarget == 0
}).Draw(t, "height")
// Generate a previous block timestamp.
prevTimeUnix := rapid.Int64Range(
1000000, 2000000000,
).Draw(t, "prev_time")
prevBlockTime := time.Unix(prevTimeUnix, 0)
// Generate a header timestamp that is not more than
// maxTimeWarp earlier than the previous block timestamp.
minValidHeaderTime := prevBlockTime.Add(
-maxTimeWarp,
).Add(time.Second)
// Generate any valid header time between the minimum valid
// time and prevBlockTime to ensure it passes the time warp
// check.
minTimeUnix := minValidHeaderTime.Unix()
maxTimeUnix := prevBlockTime.Unix()
// Ensure min is always less than max.
if minTimeUnix >= maxTimeUnix {
// If a valid range cannot be generated, use the
// previous block time which is guaranteed to pass the
// test.
headerTime := prevBlockTime
err := assertNoTimeWarp(
height, blocksPerRetarget, headerTime, prevBlockTime,
)
require.NoError(t, err, "expected valid timestamps to "+
"pass but got: %v.")
return
}
headerTimeUnix := rapid.Int64Range(
minTimeUnix, maxTimeUnix,
).Draw(t, "header_time_unix")
headerTime := time.Unix(headerTimeUnix, 0)
err := assertNoTimeWarp(
height, blocksPerRetarget, headerTime, prevBlockTime,
)
require.NoError(t, err, "expected valid timestamps to pass but "+
"got: %v.")
}))
// Rapid test that retarget blocks with invalid timestamps fail
t.Run("invalid_timestamps_fail", rapid.MakeCheck(func(t *rapid.T) {
// validation.
// Generate block height that is a retarget block.
height := rapid.Int32Range(blocksPerRetarget, 1000000).
Filter(func(h int32) bool {
return h%blocksPerRetarget == 0
}).Draw(t, "height")
// Generate a previous block timestamp.
prevTimeUnix := rapid.Int64Range(
1000000, 2000000000,
).Draw(t, "prev_time")
prevBlockTime := time.Unix(prevTimeUnix, 0)
// Invalid header timestamp: more than maxTimeWarp earlier than
// prevBlockTime Ensure we generate a time that is definitely
// beyond the maxTimeWarp (which is 600 seconds) by using at
// least 601 seconds.
invalidDelta := time.Duration(
-rapid.Int64Range(601, 86400).Draw(t, "invalid_delta"),
) * time.Second
headerTime := prevBlockTime.Add(invalidDelta)
err := assertNoTimeWarp(
height, blocksPerRetarget, headerTime, prevBlockTime,
)
require.Error(t, err, "expected error for time-warped header but got nil.")
// Verify the correct error type is returned.
require.IsType(
t, RuleError{}, err, "expected RuleError but got: %T.", err,
)
// Verify it's the expected ErrTimewarpAttack error.
ruleErr, ok := err.(RuleError)
require.True(t, ok, "expected RuleError but got: %T.", err)
require.Equal(
t, ErrTimewarpAttack, ruleErr.ErrorCode, "expected "+
"ErrTimewarpAttack but got: %v.", ruleErr.ErrorCode,
)
}))
// Test the edge case right at the boundary of maxTimeWarp.
t.Run("boundary_timestamps", rapid.MakeCheck(func(t *rapid.T) {
// Generate block height that is a retarget block.
height := rapid.Int32Range(blocksPerRetarget, 1000000).
Filter(func(h int32) bool {
return h%blocksPerRetarget == 0
}).Draw(t, "height")
// Generate a previous block timestamp with enough padding
// to avoid time.Time precision issues.
prevTimeUnix := rapid.Int64Range(
1000000, 2000000000,
).Draw(t, "prev_time")
prevBlockTime := time.Unix(prevTimeUnix, 0)
// Test exact boundary: headerTime is exactly maxTimeWarp earlier.
headerTime := prevBlockTime.Add(-maxTimeWarp)
// Check the actual implementation (looking at
// validate.go:797-798) The comparison is
// "headerTimestamp.Before(prevBlockTimestamp.Add(-maxTimeWarp))"
// This means at exact boundary (headerTime ==
// prevBlockTime.Add(-maxTimeWarp)) it should NOT fail, since
// Before() is strict < not <=.
err := assertNoTimeWarp(
height, blocksPerRetarget, headerTime, prevBlockTime,
)
require.NoError(
t, err, "expected no error at exact boundary but "+
"got: %v.",
)
// Test 1 nanosecond BEYOND the boundary (which should fail).
headerTime = prevBlockTime.Add(-maxTimeWarp).Add(
-time.Nanosecond,
)
// This should fail as it is just beyond the maxTimeWarp limit.
err = assertNoTimeWarp(
height, blocksPerRetarget, headerTime, prevBlockTime,
)
require.Error(
t, err, "expected error just beyond boundary but "+
"got nil.",
)
}))
}
// TestAssertNoTimeWarpInvariants uses property-based testing to verify the
// invariants of the assertNoTimeWarp function regardless of inputs.
func TestAssertNoTimeWarpInvariants(t *testing.T) {
t.Parallel()
// Invariant: The function should never panic regardless of input.
t.Run("never_panics", rapid.MakeCheck(func(t *rapid.T) {
// Generate any possible inputs
height := rapid.Int32().Draw(t, "height")
blocksPerRetarget := rapid.Int32Range(
1, 10000,
).Draw(t, "blocks_per_retarget")
headerTimeUnix := rapid.Int64().Draw(t, "header_time")
prevTimeUnix := rapid.Int64().Draw(t, "prev_time")
headerTime := time.Unix(headerTimeUnix, 0)
prevBlockTime := time.Unix(prevTimeUnix, 0)
// The function should never panic regardless of input
_ = assertNoTimeWarp(
height, blocksPerRetarget, headerTime, prevBlockTime,
)
}))
// Invariant: For non-retarget blocks, the function always returns nil.
// nolint:lll.
t.Run("non_retarget_blocks_return_nil", rapid.MakeCheck(func(t *rapid.T) {
// Generate height and blocksPerRetarget such that height is
// not a multiple of blocksPerRetarget.
blocksPerRetarget := rapid.Int32Range(2, 10000).Draw(
t, "blocks_per_retarget",
)
// Ensure height is not a multiple of blocksPerRetarget.
remainders := rapid.Int32Range(1, blocksPerRetarget-1).Draw(
t, "remainder",
)
height := rapid.Int32Range(0, 1000000).Draw(
t, "base",
)*blocksPerRetarget + remainders
// Generate any timestamps, even invalid ones.
headerTime := time.Unix(rapid.Int64().Draw(t, "header_time"), 0)
prevBlockTime := time.Unix(
rapid.Int64().Draw(t, "prev_time"), 0,
)
// For non-retarget blocks, should always return nil.
err := assertNoTimeWarp(
height, blocksPerRetarget, headerTime, prevBlockTime,
)
require.NoError(
t, err, "expected nil for non-retarget block "+
"(height=%d, blocks_per_retarget=%d) but "+
"got: %v.", height, blocksPerRetarget, err,
)
}))
}
// TestAssertNoTimeWarpSecurity tests the security properties of the
// assertNoTimeWarp function. This verifies that the function properly prevents
// "time warp" attacks where miners might attempt to manipulate timestamps for
// difficulty adjustment blocks.
func TestAssertNoTimeWarpSecurity(t *testing.T) {
t.Parallel()
const blocksPerRetarget = 2016
// Test that all difficulty adjustment blocks are protected from timewarp.
t.Run("all_retarget_blocks_protected", rapid.MakeCheck(func(t *rapid.T) { //nolint:lll
// Generate any retarget block height (multiples of
// blocksPerRetarget).
multiplier := rapid.Int32Range(1, 1000).Draw(t, "multiplier")
height := multiplier * blocksPerRetarget
// Generate a reasonable previous block timestamp.
prevTimeUnix := rapid.Int64Range(
1000000, 2000000000,
).Draw(t, "prev_time")
prevBlockTime := time.Unix(prevTimeUnix, 0)
// Generate a test header timestamp that's significantly before
// the previous timestamp This should always be rejected for
// retarget blocks.
timeDiff := rapid.Int64Range(
int64(maxTimeWarp+time.Second),
int64(maxTimeWarp+time.Hour*24*7),
).Draw(t, "warp_amount")
invalidDelta := time.Duration(-timeDiff)
headerTime := prevBlockTime.Add(invalidDelta)
// This should always fail with ErrTimewarpAttack for any retarget block.
err := assertNoTimeWarp(
height, blocksPerRetarget, headerTime, prevBlockTime,
)
require.Error(
t, err, "security vulnerability: Time warp attack not "+
"detected for height %d.", height,
)
// Verify it's the expected error type.
ruleErr, ok := err.(RuleError)
require.True(t, ok, "expected RuleError but got: %T.", err)
require.Equal(
t, ErrTimewarpAttack, ruleErr.ErrorCode,
"expected ErrTimewarpAttack but got: %v.",
ruleErr.ErrorCode,
)
}))
// Test that non-adjustment blocks are not subject to the same check.
// nolint:lll.
t.Run("non_retarget_blocks_not_affected", rapid.MakeCheck(func(t *rapid.T) {
// Generate any non-retarget block height.
baseHeight := rapid.Int32Range(0, 1000).Draw(
t, "base_height",
) * blocksPerRetarget
offset := rapid.Int32Range(1, blocksPerRetarget-1).Draw(
t, "offset",
)
height := baseHeight + offset
// Generate a reasonable previous block timestamp.
prevTimeUnix := rapid.Int64Range(1000000, 2000000000).Draw(
t, "prev_time",
)
prevBlockTime := time.Unix(prevTimeUnix, 0)
// Generate a test header timestamp that's significantly before
// the previous timestamp. Even though this would be rejected
// for retarget blocks, it shouldn't matter here.
timeDiff := rapid.Int64Range(
int64(maxTimeWarp+time.Second),
int64(maxTimeWarp+time.Hour*24*7),
).Draw(t, "warp_amount")
invalidDelta := time.Duration(-timeDiff)
headerTime := prevBlockTime.Add(invalidDelta)
// This should NOT fail for non-retarget blocks, even with
// extreme timewarp.
err := assertNoTimeWarp(
height, blocksPerRetarget, headerTime, prevBlockTime,
)
require.NoError(
t, err, "non-retarget blocks should not be affected "+
"by time warp check, but got: %v.", err,
)
}))
}

View file

@ -134,6 +134,13 @@ func (c bitConditionChecker) IsSpeedy() bool {
return false return false
} }
// ForceActive returns if the deployment should be forced to transition to the
// active state. This is useful on certain testnet, where we we'd like for a
// deployment to always be active.
func (c bitConditionChecker) ForceActive(node *blockNode) bool {
return false
}
// deploymentChecker provides a thresholdConditionChecker which can be used to // deploymentChecker provides a thresholdConditionChecker which can be used to
// test a specific deployment rule. This is required for properly detecting // test a specific deployment rule. This is required for properly detecting
// and activating consensus rule changes. // and activating consensus rule changes.
@ -207,15 +214,9 @@ func (c deploymentChecker) MinerConfirmationWindow() uint32 {
} }
// EligibleToActivate returns true if a custom deployment can transition from // EligibleToActivate returns true if a custom deployment can transition from
// the LockedIn to the Active state. For normal deployments, this always // the LockedIn to the Active state. In addition to the traditional minimum
// returns true. However, some deployments add extra rules like a minimum // activation height (MinActivationHeight), an optional AlwaysActiveHeight can
// activation height, which can be abstracted into a generic arbitrary check at // force the deployment to be active after a specified height.
// the final state via this method.
//
// This implementation always returns true, unless a minimum activation height
// is specified.
//
// This is part of the thresholdConditionChecker interface implementation.
func (c deploymentChecker) EligibleToActivate(blkNode *blockNode) bool { func (c deploymentChecker) EligibleToActivate(blkNode *blockNode) bool {
// No activation height, so it's always ready to go. // No activation height, so it's always ready to go.
if c.deployment.MinActivationHeight == 0 { if c.deployment.MinActivationHeight == 0 {
@ -249,6 +250,28 @@ func (c deploymentChecker) Condition(node *blockNode) (bool, error) {
nil nil
} }
// ForceActive returns if the deployment should be forced to transition to the
// active state. This is useful on certain testnet, where we we'd like for a
// deployment to always be active.
func (c deploymentChecker) ForceActive(node *blockNode) bool {
if node == nil {
return false
}
// If the deployment has a nonzero AlwaysActiveHeight and the next
// blocks height is at or above that threshold, then force the state
// to Active.
effectiveHeight := c.deployment.EffectiveAlwaysActiveHeight()
if uint32(node.height)+1 >= effectiveHeight {
log.Debugf("Force activating deployment: next block "+
"height %d >= EffectiveAlwaysActiveHeight %d",
uint32(node.height)+1, effectiveHeight)
return true
}
return false
}
// calcNextBlockVersion calculates the expected version of the block after the // calcNextBlockVersion calculates the expected version of the block after the
// passed previous block node based on the state of started and locked in // passed previous block node based on the state of started and locked in
// rule change deployments. // rule change deployments.

View file

@ -143,6 +143,77 @@ var testNet3GenesisBlock = wire.MsgBlock{
Transactions: []*wire.MsgTx{&genesisCoinbaseTx}, 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 // simNetGenesisHash is the hash of the first block in the block chain for the
// simulation test network. // simulation test network.
var simNetGenesisHash = chainhash.Hash([chainhash.HashSize]byte{ // Make go vet happy. var simNetGenesisHash = chainhash.Hash([chainhash.HashSize]byte{ // Make go vet happy.

View file

@ -9,6 +9,7 @@ import (
"testing" "testing"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/stretchr/testify/require"
) )
// TestGenesisBlock tests the genesis block of the main network for validity by // TestGenesisBlock tests the genesis block of the main network for validity by
@ -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)
require.NoError(t, 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))
}
expectedHash := "00000000da84f2bafbbc53dee25a72ae507ff4914b867c565be3" +
"50b0da8bf043"
require.Equal(t, expectedHash, hash.String())
}
// TestSimNetGenesisBlock tests the genesis block of the simulation test network // TestSimNetGenesisBlock tests the genesis block of the simulation test network
// for validity by checking the encoded bytes and hashes. // for validity by checking the encoded bytes and hashes.
func TestSimNetGenesisBlock(t *testing.T) { func TestSimNetGenesisBlock(t *testing.T) {
@ -268,6 +297,44 @@ var testNet3GenesisBlockBytes = []byte{
0xac, 0x00, 0x00, 0x00, 0x00, /* |.....| */ 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, /* |....N{+.| */
0x28, 0xfe, 0x02, 0x91, 0xdb, 0x06, 0x93, 0xaf, /* |(.......| */
0x2a, 0xe4, 0x18, 0xb7, 0x67, 0xe6, 0x57, 0xcd, /* |*...g.W.| */
0x40, 0x7e, 0x80, 0xcb, 0x14, 0x34, 0x22, 0x1e, /* |@~...4".| */
0xae, 0xa7, 0xa0, 0x7a, 0x04, 0x6f, 0x35, 0x66, /* |...z.o5f| */
0xff, 0xff, 0x00, 0x1d, 0xbb, 0x0c, 0x78, 0x17, /* |......x.| */
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, /* |..U.....| */
0x01, 0x04, 0x4c, 0x4c, 0x30, 0x33, 0x2f, 0x4d, /* |..LL03/M| */
0x61, 0x79, 0x2f, 0x32, 0x30, 0x32, 0x34, 0x20, /* |ay/2024 | */
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, /* |00000000| */
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, /* |00000000| */
0x30, 0x30, 0x30, 0x30, 0x31, 0x65, 0x62, 0x64, /* |00001ebd| */
0x35, 0x38, 0x63, 0x32, 0x34, 0x34, 0x39, 0x37, /* |58c24497| */
0x30, 0x62, 0x33, 0x61, 0x61, 0x39, 0x64, 0x37, /* |0b3aa9d7| */
0x38, 0x33, 0x62, 0x62, 0x30, 0x30, 0x31, 0x30, /* |83bb0010| */
0x31, 0x31, 0x66, 0x62, 0x65, 0x38, 0x65, 0x61, /* |11fbe8ea| */
0x38, 0x65, 0x39, 0x38, 0x65, 0x30, 0x30, 0x65, /* |8e98e00e| */
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 // simNetGenesisBlockBytes are the wire encoded bytes for the genesis block of
// the simulation test network as of protocol version 70002. // the simulation test network as of protocol version 70002.
var simNetGenesisBlockBytes = []byte{ var simNetGenesisBlockBytes = []byte{

View file

@ -8,6 +8,7 @@ import (
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"errors" "errors"
"math"
"math/big" "math/big"
"strings" "strings"
"time" "time"
@ -78,6 +79,16 @@ type Checkpoint struct {
Hash *chainhash.Hash Hash *chainhash.Hash
} }
// EffectiveAlwaysActiveHeight returns the effective activation height for the
// deployment. If AlwaysActiveHeight is unset (i.e. zero), it returns
// the maximum uint32 value to indicate that it does not force activation.
func (d *ConsensusDeployment) EffectiveAlwaysActiveHeight() uint32 {
if d.AlwaysActiveHeight == 0 {
return math.MaxUint32
}
return d.AlwaysActiveHeight
}
// DNSSeed identifies a DNS seed. // DNSSeed identifies a DNS seed.
type DNSSeed struct { type DNSSeed struct {
// Host defines the hostname of the seed. // Host defines the hostname of the seed.
@ -108,6 +119,11 @@ type ConsensusDeployment struct {
// activation. A value of 1815 block denotes a 90% threshold. // activation. A value of 1815 block denotes a 90% threshold.
CustomActivationThreshold uint32 CustomActivationThreshold uint32
// AlwaysActiveHeight defines an optional block threshold at which the
// deployment is forced to be active. If unset (0), it defaults to
// math.MaxUint32, meaning the deployment does not force activation.
AlwaysActiveHeight uint32
// DeploymentStarter is used to determine if the given // DeploymentStarter is used to determine if the given
// ConsensusDeployment has started or not. // ConsensusDeployment has started or not.
DeploymentStarter ConsensusDeploymentStarter DeploymentStarter ConsensusDeploymentStarter
@ -146,6 +162,10 @@ const (
// the deployment of BIPS 340, 341 and 342. // the deployment of BIPS 340, 341 and 342.
DeploymentTaproot DeploymentTaproot
// DeploymentTestDummyAlwaysActive is a dummy deployment that is meant
// to always be active.
DeploymentTestDummyAlwaysActive
// NOTE: DefinedDeployments must always come last since it is used to // NOTE: DefinedDeployments must always come last since it is used to
// determine how many defined deployments there currently are. // determine how many defined deployments there currently are.
@ -189,6 +209,10 @@ type Params struct {
// regtest like networks. // regtest like networks.
PoWNoRetargeting bool PoWNoRetargeting bool
// EnforceBIP94 enforces timewarp attack mitigation and on testnet4
// this also enforces the block storm mitigation.
EnforceBIP94 bool
// These fields define the block heights at which the specified softfork // These fields define the block heights at which the specified softfork
// BIP became active. // BIP became active.
BIP0034Height int32 BIP0034Height int32
@ -375,6 +399,16 @@ var MainNetParams = Params{
time.Time{}, // Never expires time.Time{}, // Never expires
), ),
}, },
DeploymentTestDummyAlwaysActive: {
BitNumber: 30,
DeploymentStarter: NewMedianTimeDeploymentStarter(
time.Time{}, // Always available for vote
),
DeploymentEnder: NewMedianTimeDeploymentEnder(
time.Time{}, // Never expires
),
AlwaysActiveHeight: 1,
},
DeploymentCSV: { DeploymentCSV: {
BitNumber: 0, BitNumber: 0,
DeploymentStarter: NewMedianTimeDeploymentStarter( DeploymentStarter: NewMedianTimeDeploymentStarter(
@ -486,6 +520,16 @@ var RegressionNetParams = Params{
time.Time{}, // Never expires time.Time{}, // Never expires
), ),
}, },
DeploymentTestDummyAlwaysActive: {
BitNumber: 30,
DeploymentStarter: NewMedianTimeDeploymentStarter(
time.Time{}, // Always available for vote
),
DeploymentEnder: NewMedianTimeDeploymentEnder(
time.Time{}, // Never expires
),
AlwaysActiveHeight: 1,
},
DeploymentCSV: { DeploymentCSV: {
BitNumber: 0, BitNumber: 0,
DeploymentStarter: NewMedianTimeDeploymentStarter( DeploymentStarter: NewMedianTimeDeploymentStarter(
@ -620,6 +664,16 @@ var TestNet3Params = Params{
time.Time{}, // Never expires time.Time{}, // Never expires
), ),
}, },
DeploymentTestDummyAlwaysActive: {
BitNumber: 30,
DeploymentStarter: NewMedianTimeDeploymentStarter(
time.Time{}, // Always available for vote
),
DeploymentEnder: NewMedianTimeDeploymentEnder(
time.Time{}, // Never expires
),
AlwaysActiveHeight: 1,
},
DeploymentCSV: { DeploymentCSV: {
BitNumber: 0, BitNumber: 0,
DeploymentStarter: NewMedianTimeDeploymentStarter( DeploymentStarter: NewMedianTimeDeploymentStarter(
@ -673,6 +727,131 @@ var TestNet3Params = Params{
HDCoinType: 1, HDCoinType: 1,
} }
// TestNet4Params defines the network parameters for the test Bitcoin network
// (version 4).
var TestNet4Params = Params{
Name: "testnet4",
Net: wire.TestNet4,
DefaultPort: "48333",
DNSSeeds: []DNSSeed{
{"seed.testnet4.bitcoin.sprovoost.nl", true},
{"seed.testnet4.wiz.biz", true},
},
// Chain parameters
GenesisBlock: &testNet4GenesisBlock,
GenesisHash: &testNet4GenesisHash,
PowLimit: testNet3PowLimit,
PowLimitBits: 0x1d00ffff,
EnforceBIP94: true,
BIP0034Height: 1,
BIP0065Height: 1,
BIP0066Height: 1,
CoinbaseMaturity: 100,
SubsidyReductionInterval: 210000,
TargetTimespan: time.Hour * 24 * 14, // 14 days
TargetTimePerBlock: time.Minute * 10, // 10 minutes
RetargetAdjustmentFactor: 4, // 25% less, 400% more
ReduceMinDifficulty: true,
MinDiffReductionTime: time.Minute * 20, // TargetTimePerBlock * 2
GenerateSupported: false,
// Checkpoints ordered from oldest to newest.
Checkpoints: []Checkpoint{},
// Consensus rule change deployments.
//
// The miner confirmation window is defined as:
// target proof of work timespan / target proof of work spacing
RuleChangeActivationThreshold: 1512, // 75% of MinerConfirmationWindow
MinerConfirmationWindow: 2016,
Deployments: [DefinedDeployments]ConsensusDeployment{
DeploymentTestDummy: {
BitNumber: 28,
DeploymentStarter: NewMedianTimeDeploymentStarter(
time.Unix(1199145601, 0), // January 1, 2008 UTC
),
DeploymentEnder: NewMedianTimeDeploymentEnder(
time.Unix(1230767999, 0), // December 31, 2008 UTC
),
},
DeploymentTestDummyMinActivation: {
BitNumber: 22,
CustomActivationThreshold: 1815, // Only needs 90% hash rate.
MinActivationHeight: 10_0000, // Can only activate after height 10k.
DeploymentStarter: NewMedianTimeDeploymentStarter(
time.Time{}, // Always available for vote
),
DeploymentEnder: NewMedianTimeDeploymentEnder(
time.Time{}, // Never expires
),
},
DeploymentTestDummyAlwaysActive: {
BitNumber: 30,
DeploymentStarter: NewMedianTimeDeploymentStarter(
time.Time{}, // Always available for vote
),
DeploymentEnder: NewMedianTimeDeploymentEnder(
time.Time{}, // Never expires
),
AlwaysActiveHeight: 1,
},
DeploymentCSV: {
BitNumber: 29,
DeploymentStarter: NewMedianTimeDeploymentStarter(
time.Time{}, // Always available for vote
),
DeploymentEnder: NewMedianTimeDeploymentEnder(
time.Time{}, // Never expires
),
AlwaysActiveHeight: 1,
},
DeploymentSegwit: {
BitNumber: 29,
DeploymentStarter: NewMedianTimeDeploymentStarter(
time.Time{}, // Always available for vote
),
DeploymentEnder: NewMedianTimeDeploymentEnder(
time.Time{}, // Never expires
),
AlwaysActiveHeight: 1,
},
DeploymentTaproot: {
BitNumber: 2,
DeploymentStarter: NewMedianTimeDeploymentStarter(
time.Time{}, // Always available for vote
),
DeploymentEnder: NewMedianTimeDeploymentEnder(
time.Time{}, // Never expires
),
MinActivationHeight: 0,
AlwaysActiveHeight: 1,
},
},
// Mempool parameters
RelayNonStdTxs: true,
// Human-readable part for Bech32 encoded segwit addresses, as defined in
// BIP 173.
Bech32HRPSegwit: "tb", // always tb for test net
// Address encoding magics
PubKeyHashAddrID: 0x6f, // starts with m or n
ScriptHashAddrID: 0xc4, // starts with 2
WitnessPubKeyHashAddrID: 0x03, // starts with QW
WitnessScriptHashAddrID: 0x28, // starts with T7n
PrivateKeyID: 0xef, // starts with 9 (uncompressed) or c (compressed)
// BIP32 hierarchical deterministic extended key magics
HDPrivateKeyID: [4]byte{0x04, 0x35, 0x83, 0x94}, // starts with tprv
HDPublicKeyID: [4]byte{0x04, 0x35, 0x87, 0xcf}, // starts with tpub
// BIP44 coin type used in the hierarchical deterministic path for
// address generation.
HDCoinType: 1,
}
// SimNetParams defines the network parameters for the simulation test Bitcoin // SimNetParams defines the network parameters for the simulation test Bitcoin
// network. This network is similar to the normal test network except it is // network. This network is similar to the normal test network except it is
// intended for private use within a group of individuals doing simulation // intended for private use within a group of individuals doing simulation
@ -761,6 +940,16 @@ var SimNetParams = Params{
), ),
CustomActivationThreshold: 75, // Only needs 75% hash rate. CustomActivationThreshold: 75, // Only needs 75% hash rate.
}, },
DeploymentTestDummyAlwaysActive: {
BitNumber: 29,
DeploymentStarter: NewMedianTimeDeploymentStarter(
time.Time{}, // Always available for vote
),
DeploymentEnder: NewMedianTimeDeploymentEnder(
time.Time{}, // Never expires
),
AlwaysActiveHeight: 1,
},
}, },
// Mempool parameters // Mempool parameters
@ -861,6 +1050,16 @@ func CustomSignetParams(challenge []byte, dnsSeeds []DNSSeed) Params {
time.Time{}, // Never expires time.Time{}, // Never expires
), ),
}, },
DeploymentTestDummyAlwaysActive: {
BitNumber: 30,
DeploymentStarter: NewMedianTimeDeploymentStarter(
time.Time{}, // Always available for vote
),
DeploymentEnder: NewMedianTimeDeploymentEnder(
time.Time{}, // Never expires
),
AlwaysActiveHeight: 1,
},
DeploymentCSV: { DeploymentCSV: {
BitNumber: 29, BitNumber: 29,
DeploymentStarter: NewMedianTimeDeploymentStarter( DeploymentStarter: NewMedianTimeDeploymentStarter(
@ -1075,6 +1274,7 @@ func init() {
// Register all default networks when the package is initialized. // Register all default networks when the package is initialized.
mustRegister(&MainNetParams) mustRegister(&MainNetParams)
mustRegister(&TestNet3Params) mustRegister(&TestNet3Params)
mustRegister(&TestNet4Params)
mustRegister(&RegressionNetParams) mustRegister(&RegressionNetParams)
mustRegister(&SimNetParams) mustRegister(&SimNetParams)
} }

View file

@ -68,6 +68,11 @@ func TestRegister(t *testing.T) {
params: &TestNet3Params, params: &TestNet3Params,
err: ErrDuplicateNet, err: ErrDuplicateNet,
}, },
{
name: "duplicate testnet4",
params: &TestNet4Params,
err: ErrDuplicateNet,
},
{ {
name: "duplicate simnet", name: "duplicate simnet",
params: &SimNetParams, params: &SimNetParams,
@ -83,6 +88,10 @@ func TestRegister(t *testing.T) {
magic: TestNet3Params.PubKeyHashAddrID, magic: TestNet3Params.PubKeyHashAddrID,
valid: true, valid: true,
}, },
{
magic: TestNet4Params.PubKeyHashAddrID,
valid: true,
},
{ {
magic: RegressionNetParams.PubKeyHashAddrID, magic: RegressionNetParams.PubKeyHashAddrID,
valid: true, valid: true,
@ -109,6 +118,10 @@ func TestRegister(t *testing.T) {
magic: TestNet3Params.ScriptHashAddrID, magic: TestNet3Params.ScriptHashAddrID,
valid: true, valid: true,
}, },
{
magic: TestNet4Params.ScriptHashAddrID,
valid: true,
},
{ {
magic: RegressionNetParams.ScriptHashAddrID, magic: RegressionNetParams.ScriptHashAddrID,
valid: true, valid: true,
@ -135,6 +148,10 @@ func TestRegister(t *testing.T) {
prefix: TestNet3Params.Bech32HRPSegwit + "1", prefix: TestNet3Params.Bech32HRPSegwit + "1",
valid: true, valid: true,
}, },
{
prefix: TestNet4Params.Bech32HRPSegwit + "1",
valid: true,
},
{ {
prefix: RegressionNetParams.Bech32HRPSegwit + "1", prefix: RegressionNetParams.Bech32HRPSegwit + "1",
valid: true, valid: true,
@ -175,6 +192,11 @@ func TestRegister(t *testing.T) {
want: TestNet3Params.HDPublicKeyID[:], want: TestNet3Params.HDPublicKeyID[:],
err: nil, err: nil,
}, },
{
priv: TestNet4Params.HDPrivateKeyID[:],
want: TestNet4Params.HDPublicKeyID[:],
err: nil,
},
{ {
priv: RegressionNetParams.HDPrivateKeyID[:], priv: RegressionNetParams.HDPrivateKeyID[:],
want: RegressionNetParams.HDPublicKeyID[:], want: RegressionNetParams.HDPublicKeyID[:],
@ -217,6 +239,10 @@ func TestRegister(t *testing.T) {
magic: TestNet3Params.PubKeyHashAddrID, magic: TestNet3Params.PubKeyHashAddrID,
valid: true, valid: true,
}, },
{
magic: TestNet4Params.PubKeyHashAddrID,
valid: true,
},
{ {
magic: RegressionNetParams.PubKeyHashAddrID, magic: RegressionNetParams.PubKeyHashAddrID,
valid: true, valid: true,
@ -243,6 +269,10 @@ func TestRegister(t *testing.T) {
magic: TestNet3Params.ScriptHashAddrID, magic: TestNet3Params.ScriptHashAddrID,
valid: true, valid: true,
}, },
{
magic: TestNet4Params.ScriptHashAddrID,
valid: true,
},
{ {
magic: RegressionNetParams.ScriptHashAddrID, magic: RegressionNetParams.ScriptHashAddrID,
valid: true, valid: true,
@ -269,6 +299,10 @@ func TestRegister(t *testing.T) {
prefix: TestNet3Params.Bech32HRPSegwit + "1", prefix: TestNet3Params.Bech32HRPSegwit + "1",
valid: true, valid: true,
}, },
{
prefix: TestNet4Params.Bech32HRPSegwit + "1",
valid: true,
},
{ {
prefix: RegressionNetParams.Bech32HRPSegwit + "1", prefix: RegressionNetParams.Bech32HRPSegwit + "1",
valid: true, valid: true,
@ -324,6 +358,11 @@ func TestRegister(t *testing.T) {
params: &TestNet3Params, params: &TestNet3Params,
err: ErrDuplicateNet, err: ErrDuplicateNet,
}, },
{
name: "duplicate testnet4",
params: &TestNet4Params,
err: ErrDuplicateNet,
},
{ {
name: "duplicate simnet", name: "duplicate simnet",
params: &SimNetParams, params: &SimNetParams,
@ -344,6 +383,10 @@ func TestRegister(t *testing.T) {
magic: TestNet3Params.PubKeyHashAddrID, magic: TestNet3Params.PubKeyHashAddrID,
valid: true, valid: true,
}, },
{
magic: TestNet4Params.PubKeyHashAddrID,
valid: true,
},
{ {
magic: RegressionNetParams.PubKeyHashAddrID, magic: RegressionNetParams.PubKeyHashAddrID,
valid: true, valid: true,
@ -370,6 +413,10 @@ func TestRegister(t *testing.T) {
magic: TestNet3Params.ScriptHashAddrID, magic: TestNet3Params.ScriptHashAddrID,
valid: true, valid: true,
}, },
{
magic: TestNet4Params.ScriptHashAddrID,
valid: true,
},
{ {
magic: RegressionNetParams.ScriptHashAddrID, magic: RegressionNetParams.ScriptHashAddrID,
valid: true, valid: true,
@ -396,6 +443,10 @@ func TestRegister(t *testing.T) {
prefix: TestNet3Params.Bech32HRPSegwit + "1", prefix: TestNet3Params.Bech32HRPSegwit + "1",
valid: true, valid: true,
}, },
{
prefix: TestNet4Params.Bech32HRPSegwit + "1",
valid: true,
},
{ {
prefix: RegressionNetParams.Bech32HRPSegwit + "1", prefix: RegressionNetParams.Bech32HRPSegwit + "1",
valid: true, valid: true,
@ -436,6 +487,11 @@ func TestRegister(t *testing.T) {
want: TestNet3Params.HDPublicKeyID[:], want: TestNet3Params.HDPublicKeyID[:],
err: nil, err: nil,
}, },
{
priv: TestNet4Params.HDPrivateKeyID[:],
want: TestNet4Params.HDPublicKeyID[:],
err: nil,
},
{ {
priv: RegressionNetParams.HDPrivateKeyID[:], priv: RegressionNetParams.HDPrivateKeyID[:],
want: RegressionNetParams.HDPublicKeyID[:], want: RegressionNetParams.HDPublicKeyID[:],

View file

@ -31,7 +31,7 @@ var (
activeNetParams = &chaincfg.MainNetParams activeNetParams = &chaincfg.MainNetParams
) )
// config defines the configuration options for findcheckpoint. // config defines the configuration options for addblock.
// //
// See loadConfig for details on the configuration load process. // See loadConfig for details on the configuration load process.
type config struct { type config struct {
@ -42,7 +42,8 @@ type config struct {
Progress int `short:"p" long:"progress" description:"Show a progress message each time this number of seconds have passed -- Use 0 to disable progress announcements"` Progress int `short:"p" long:"progress" description:"Show a progress message each time this number of seconds have passed -- Use 0 to disable progress announcements"`
RegressionTest bool `long:"regtest" description:"Use the regression test network"` RegressionTest bool `long:"regtest" description:"Use the regression test network"`
SimNet bool `long:"simnet" description:"Use the simulation test network"` SimNet bool `long:"simnet" description:"Use the simulation test network"`
TestNet3 bool `long:"testnet" description:"Use the test network"` TestNet3 bool `long:"testnet" description:"Use the test network (version 3)"`
TestNet4 bool `long:"testnet4" description:"Use the test network (version 4)"`
TxIndex bool `long:"txindex" description:"Build a full hash-based transaction index which makes all transactions available via the getrawtransaction RPC"` TxIndex bool `long:"txindex" description:"Build a full hash-based transaction index which makes all transactions available via the getrawtransaction RPC"`
} }
@ -108,6 +109,10 @@ func loadConfig() (*config, []string, error) {
numNets++ numNets++
activeNetParams = &chaincfg.TestNet3Params activeNetParams = &chaincfg.TestNet3Params
} }
if cfg.TestNet4 {
numNets++
activeNetParams = &chaincfg.TestNet4Params
}
if cfg.RegressionTest { if cfg.RegressionTest {
numNets++ numNets++
activeNetParams = &chaincfg.RegressionNetParams activeNetParams = &chaincfg.RegressionNetParams

View file

@ -106,7 +106,8 @@ type config struct {
RPCUser string `short:"u" long:"rpcuser" description:"RPC username"` RPCUser string `short:"u" long:"rpcuser" description:"RPC username"`
SimNet bool `long:"simnet" description:"Connect to the simulation test network"` SimNet bool `long:"simnet" description:"Connect to the simulation test network"`
TLSSkipVerify bool `long:"skipverify" description:"Do not verify tls certificates (not recommended!)"` TLSSkipVerify bool `long:"skipverify" description:"Do not verify tls certificates (not recommended!)"`
TestNet3 bool `long:"testnet" description:"Connect to testnet"` TestNet3 bool `long:"testnet" description:"Connect to testnet (version 3)"`
TestNet4 bool `long:"testnet4" description:"Connect to testnet (version 4)"`
SigNet bool `long:"signet" description:"Connect to signet"` SigNet bool `long:"signet" description:"Connect to signet"`
ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"` ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`
Wallet bool `long:"wallet" description:"Connect to wallet"` Wallet bool `long:"wallet" description:"Connect to wallet"`
@ -125,6 +126,12 @@ func normalizeAddress(addr string, chain *chaincfg.Params, useWallet bool) (stri
} else { } else {
defaultPort = "18334" defaultPort = "18334"
} }
case &chaincfg.TestNet4Params:
if useWallet {
defaultPort = "48332"
} else {
defaultPort = "48334"
}
case &chaincfg.SimNetParams: case &chaincfg.SimNetParams:
if useWallet { if useWallet {
defaultPort = "18554" defaultPort = "18554"
@ -272,6 +279,10 @@ func loadConfig() (*config, []string, error) {
numNets++ numNets++
network = &chaincfg.TestNet3Params network = &chaincfg.TestNet3Params
} }
if cfg.TestNet4 {
numNets++
network = &chaincfg.TestNet4Params
}
if cfg.SimNet { if cfg.SimNet {
numNets++ numNets++
network = &chaincfg.SimNetParams network = &chaincfg.SimNetParams

View file

@ -42,7 +42,8 @@ type config struct {
NumCandidates int `short:"n" long:"numcandidates" description:"Max num of checkpoint candidates to show {1-20}"` NumCandidates int `short:"n" long:"numcandidates" description:"Max num of checkpoint candidates to show {1-20}"`
RegressionTest bool `long:"regtest" description:"Use the regression test network"` RegressionTest bool `long:"regtest" description:"Use the regression test network"`
SimNet bool `long:"simnet" description:"Use the simulation test network"` SimNet bool `long:"simnet" description:"Use the simulation test network"`
TestNet3 bool `long:"testnet" description:"Use the test network"` TestNet3 bool `long:"testnet" description:"Use the test network (version 3)"`
TestNet4 bool `long:"testnet4" description:"Use the test network (version 4)"`
} }
// validDbType returns whether or not dbType is a supported database type. // validDbType returns whether or not dbType is a supported database type.
@ -96,6 +97,10 @@ func loadConfig() (*config, []string, error) {
numNets++ numNets++
activeNetParams = &chaincfg.TestNet3Params activeNetParams = &chaincfg.TestNet3Params
} }
if cfg.TestNet4 {
numNets++
activeNetParams = &chaincfg.TestNet4Params
}
if cfg.RegressionTest { if cfg.RegressionTest {
numNets++ numNets++
activeNetParams = &chaincfg.RegressionNetParams activeNetParams = &chaincfg.RegressionNetParams

View file

@ -171,7 +171,8 @@ type config struct {
SigNet bool `long:"signet" description:"Use the signet test network"` SigNet bool `long:"signet" description:"Use the signet test network"`
SigNetChallenge string `long:"signetchallenge" description:"Connect to a custom signet network defined by this challenge instead of using the global default signet test network -- Can be specified multiple times"` SigNetChallenge string `long:"signetchallenge" description:"Connect to a custom signet network defined by this challenge instead of using the global default signet test network -- Can be specified multiple times"`
SigNetSeedNode []string `long:"signetseednode" description:"Specify a seed node for the signet network instead of using the global default signet network seed nodes"` SigNetSeedNode []string `long:"signetseednode" description:"Specify a seed node for the signet network instead of using the global default signet network seed nodes"`
TestNet3 bool `long:"testnet" description:"Use the test network"` TestNet3 bool `long:"testnet" description:"Use the test network (version 3)"`
TestNet4 bool `long:"testnet4" description:"Use the test network (version 4)"`
TorIsolation bool `long:"torisolation" description:"Enable Tor stream isolation by randomizing user credentials for each connection."` TorIsolation bool `long:"torisolation" description:"Enable Tor stream isolation by randomizing user credentials for each connection."`
TrickleInterval time.Duration `long:"trickleinterval" description:"Minimum time between attempts to send new inventory to a connected peer"` TrickleInterval time.Duration `long:"trickleinterval" description:"Minimum time between attempts to send new inventory to a connected peer"`
UtxoCacheMaxSizeMiB uint `long:"utxocachemaxsize" description:"The maximum size in MiB of the UTXO cache"` UtxoCacheMaxSizeMiB uint `long:"utxocachemaxsize" description:"The maximum size in MiB of the UTXO cache"`
@ -548,6 +549,10 @@ func loadConfig() (*config, []string, error) {
numNets++ numNets++
activeNetParams = &testNet3Params activeNetParams = &testNet3Params
} }
if cfg.TestNet4 {
numNets++
activeNetParams = &testNet4Params
}
if cfg.RegressionTest { if cfg.RegressionTest {
numNets++ numNets++
activeNetParams = &regressionNetParams activeNetParams = &regressionNetParams

View file

@ -37,6 +37,7 @@ type config struct {
RegressionTest bool `long:"regtest" description:"Use the regression test network"` RegressionTest bool `long:"regtest" description:"Use the regression test network"`
SimNet bool `long:"simnet" description:"Use the simulation test network"` SimNet bool `long:"simnet" description:"Use the simulation test network"`
TestNet3 bool `long:"testnet" description:"Use the test network"` TestNet3 bool `long:"testnet" description:"Use the test network"`
TestNet4 bool `long:"testnet4" description:"Use the test network (version 4)"`
} }
// fileExists reports whether the named file or directory exists. // fileExists reports whether the named file or directory exists.
@ -84,6 +85,10 @@ func setupGlobalConfig() error {
numNets++ numNets++
activeNetParams = &chaincfg.TestNet3Params activeNetParams = &chaincfg.TestNet3Params
} }
if cfg.TestNet4 {
numNets++
activeNetParams = &chaincfg.TestNet4Params
}
if cfg.RegressionTest { if cfg.RegressionTest {
numNets++ numNets++
activeNetParams = &chaincfg.RegressionNetParams activeNetParams = &chaincfg.RegressionNetParams

1
go.mod
View file

@ -18,6 +18,7 @@ require (
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
golang.org/x/crypto v0.22.0 golang.org/x/crypto v0.22.0
golang.org/x/sys v0.19.0 golang.org/x/sys v0.19.0
pgregory.net/rapid v1.2.0
) )
require ( require (

2
go.sum
View file

@ -139,3 +139,5 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=

View file

@ -139,6 +139,14 @@ func testBIP0009(t *testing.T, forkKey string, deploymentID uint32) {
} }
defer r.TearDown() defer r.TearDown()
// If the deployment is meant to be always active, then it should be
// active from the very first block.
if deploymentID == chaincfg.DeploymentTestDummyAlwaysActive {
assertChainHeight(r, t, 0)
assertSoftForkStatus(r, t, forkKey, blockchain.ThresholdActive)
return
}
// *** ThresholdDefined *** // *** ThresholdDefined ***
// //
// Assert the chain height is the expected value and the soft fork // Assert the chain height is the expected value and the soft fork
@ -340,6 +348,7 @@ func TestBIP0009(t *testing.T) {
testBIP0009(t, "dummy", chaincfg.DeploymentTestDummy) testBIP0009(t, "dummy", chaincfg.DeploymentTestDummy)
testBIP0009(t, "dummy-min-activation", chaincfg.DeploymentTestDummyMinActivation) testBIP0009(t, "dummy-min-activation", chaincfg.DeploymentTestDummyMinActivation)
testBIP0009(t, "dummy-always-active", chaincfg.DeploymentTestDummyAlwaysActive)
testBIP0009(t, "segwit", chaincfg.DeploymentSegwit) testBIP0009(t, "segwit", chaincfg.DeploymentSegwit)
} }

View file

@ -21,7 +21,7 @@ type params struct {
} }
// mainNetParams contains parameters specific to the main network // mainNetParams contains parameters specific to the main network
// (wire.MainNet). NOTE: The RPC port is intentionally different than the // (wire.MainNet). NOTE: The RPC port is intentionally different from the
// reference implementation because btcd does not handle wallet requests. The // reference implementation because btcd does not handle wallet requests. The
// separate wallet process listens on the well-known port and forwards requests // separate wallet process listens on the well-known port and forwards requests
// it does not handle on to btcd. This approach allows the wallet process // it does not handle on to btcd. This approach allows the wallet process
@ -41,13 +41,21 @@ var regressionNetParams = params{
} }
// testNet3Params contains parameters specific to the test network (version 3) // testNet3Params contains parameters specific to the test network (version 3)
// (wire.TestNet3). NOTE: The RPC port is intentionally different than the // (wire.TestNet3). NOTE: The RPC port is intentionally different from the
// reference implementation - see the mainNetParams comment for details. // reference implementation - see the mainNetParams comment for details.
var testNet3Params = params{ var testNet3Params = params{
Params: &chaincfg.TestNet3Params, Params: &chaincfg.TestNet3Params,
rpcPort: "18334", rpcPort: "18334",
} }
// testNet4Params contains parameters specific to the test network (version 4)
// (wire.TestNet4). NOTE: The RPC port is intentionally different from the
// reference implementation - see the mainNetParams comment for details.
var testNet4Params = params{
Params: &chaincfg.TestNet4Params,
rpcPort: "48334",
}
// simNetParams contains parameters specific to the simulation test network // simNetParams contains parameters specific to the simulation test network
// (wire.SimNet). // (wire.SimNet).
var simNetParams = params{ var simNetParams = params{

View file

@ -1529,6 +1529,8 @@ func New(config *ConnConfig, ntfnHandlers *NotificationHandlers) (*Client, error
client.chainParams = &chaincfg.MainNetParams client.chainParams = &chaincfg.MainNetParams
case chaincfg.TestNet3Params.Name: case chaincfg.TestNet3Params.Name:
client.chainParams = &chaincfg.TestNet3Params client.chainParams = &chaincfg.TestNet3Params
case chaincfg.TestNet4Params.Name:
client.chainParams = &chaincfg.TestNet4Params
case chaincfg.RegressionNetParams.Name: case chaincfg.RegressionNetParams.Name:
client.chainParams = &chaincfg.RegressionNetParams client.chainParams = &chaincfg.RegressionNetParams
case chaincfg.SigNetParams.Name: case chaincfg.SigNetParams.Name:

View file

@ -1258,6 +1258,9 @@ func handleGetBlockChainInfo(s *rpcServer, cmd interface{}, closeChan <-chan str
case chaincfg.DeploymentTestDummyMinActivation: case chaincfg.DeploymentTestDummyMinActivation:
forkName = "dummy-min-activation" forkName = "dummy-min-activation"
case chaincfg.DeploymentTestDummyAlwaysActive:
forkName = "dummy-always-active"
case chaincfg.DeploymentCSV: case chaincfg.DeploymentCSV:
forkName = "csv" forkName = "csv"
@ -2358,7 +2361,7 @@ func handleGetInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (in
Connections: s.cfg.ConnMgr.ConnectedCount(), Connections: s.cfg.ConnMgr.ConnectedCount(),
Proxy: cfg.Proxy, Proxy: cfg.Proxy,
Difficulty: getDifficultyRatio(best.Bits, s.cfg.ChainParams), Difficulty: getDifficultyRatio(best.Bits, s.cfg.ChainParams),
TestNet: cfg.TestNet3, TestNet: cfg.TestNet3 || cfg.TestNet4,
RelayFee: cfg.minRelayTxFee.ToBTC(), RelayFee: cfg.minRelayTxFee.ToBTC(),
} }
@ -2413,7 +2416,7 @@ func handleGetMiningInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{
HashesPerSec: s.cfg.CPUMiner.HashesPerSecond(), HashesPerSec: s.cfg.CPUMiner.HashesPerSecond(),
NetworkHashPS: networkHashesPerSec, NetworkHashPS: networkHashesPerSec,
PooledTx: uint64(s.cfg.TxMemPool.Count()), PooledTx: uint64(s.cfg.TxMemPool.Count()),
TestNet: cfg.TestNet3, TestNet: cfg.TestNet3 || cfg.TestNet4,
} }
return &result, nil return &result, nil
} }

View file

@ -179,6 +179,9 @@ const (
// TestNet3 represents the test network (version 3). // TestNet3 represents the test network (version 3).
TestNet3 BitcoinNet = 0x0709110b TestNet3 BitcoinNet = 0x0709110b
// TestNet4 represents the test network (version 4).
TestNet4 BitcoinNet = 0x283f161c
// SigNet represents the public default SigNet. For custom signets, // SigNet represents the public default SigNet. For custom signets,
// see CustomSignetParams. // see CustomSignetParams.
SigNet BitcoinNet = 0x40CF030A SigNet BitcoinNet = 0x40CF030A
@ -193,6 +196,7 @@ var bnStrings = map[BitcoinNet]string{
MainNet: "MainNet", MainNet: "MainNet",
TestNet: "TestNet", TestNet: "TestNet",
TestNet3: "TestNet3", TestNet3: "TestNet3",
TestNet4: "TestNet4",
SigNet: "SigNet", SigNet: "SigNet",
SimNet: "SimNet", SimNet: "SimNet",
} }

View file

@ -49,6 +49,7 @@ func TestBitcoinNetStringer(t *testing.T) {
{MainNet, "MainNet"}, {MainNet, "MainNet"},
{TestNet, "TestNet"}, {TestNet, "TestNet"},
{TestNet3, "TestNet3"}, {TestNet3, "TestNet3"},
{TestNet4, "TestNet4"},
{SigNet, "SigNet"}, {SigNet, "SigNet"},
{SimNet, "SimNet"}, {SimNet, "SimNet"},
{0xffffffff, "Unknown BitcoinNet (4294967295)"}, {0xffffffff, "Unknown BitcoinNet (4294967295)"},