diff --git a/.gitignore b/.gitignore index 0e1de2f7..acfb8c4b 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,9 @@ btcutil/psbt/coverage.txt *.swo /.vim +#IDE +.idea + # Binaries produced by "make build" /addblock /btcctl @@ -54,3 +57,4 @@ btcutil/psbt/coverage.txt /gencerts .DS_Store +.aider* diff --git a/blockchain/difficulty.go b/blockchain/difficulty.go index b1e39b9d..56de7780 100644 --- a/blockchain/difficulty.go +++ b/blockchain/difficulty.go @@ -191,12 +191,20 @@ func calcNextRequiredDifficulty(lastNode HeaderCtx, newBlockTime time.Time, 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: // 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)) diff --git a/blockchain/error.go b/blockchain/error.go index dc402222..8a7d4a7d 100644 --- a/blockchain/error.go +++ b/blockchain/error.go @@ -220,6 +220,10 @@ const ( // current chain tip. This is not a block validation rule, but is required // for block proposals submitted via getblocktemplate RPC. 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. diff --git a/blockchain/thresholdstate.go b/blockchain/thresholdstate.go index d62c2de3..88031019 100644 --- a/blockchain/thresholdstate.go +++ b/blockchain/thresholdstate.go @@ -102,6 +102,11 @@ type thresholdConditionChecker interface { // not the bit associated with the condition is set, but can be more // complex as needed. 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 @@ -279,7 +284,17 @@ func thresholdStateTransition(state ThresholdState, prevNode *blockNode, // threshold states for previous windows are only calculated once. // // 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 + // block’s 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 // defined by definition. confirmationWindow := int32(checker.MinerConfirmationWindow()) diff --git a/blockchain/thresholdstate_test.go b/blockchain/thresholdstate_test.go index 8d527137..28f417a1 100644 --- a/blockchain/thresholdstate_test.go +++ b/blockchain/thresholdstate_test.go @@ -175,6 +175,10 @@ func (c customDeploymentChecker) Condition(_ *blockNode) (bool, error) { return c.conditionTrue, nil } +func (c customDeploymentChecker) ForceActive(_ *blockNode) bool { + return false +} + // TestThresholdStateTransition tests that the thresholdStateTransition // properly implements the BIP 009 state machine, along with the speedy trial // augments. diff --git a/blockchain/validate.go b/blockchain/validate.go index 5e24405e..1cc0ec33 100644 --- a/blockchain/validate.go +++ b/blockchain/validate.go @@ -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 ( @@ -684,6 +689,12 @@ 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 + + params := c.ChainParams() + fastAdd := flags&BFFastAdd == BFFastAdd if !fastAdd { // 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) 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 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 // has upgraded. These were originally voted on by BIP0034, // BIP0065, and BIP0066. - params := c.ChainParams() if header.Version < 2 && blockHeight >= params.BIP0034Height || header.Version < 3 && blockHeight >= params.BIP0066Height || header.Version < 4 && blockHeight >= params.BIP0065Height { @@ -761,6 +780,30 @@ func CheckBlockHeaderContext(header *wire.BlockHeader, prevNode HeaderCtx, 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 // 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 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 diff --git a/blockchain/validate_rapid_test.go b/blockchain/validate_rapid_test.go new file mode 100644 index 00000000..11b13571 --- /dev/null +++ b/blockchain/validate_rapid_test.go @@ -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, + ) + })) +} diff --git a/blockchain/versionbits.go b/blockchain/versionbits.go index 371d4f20..493787a7 100644 --- a/blockchain/versionbits.go +++ b/blockchain/versionbits.go @@ -134,6 +134,13 @@ func (c bitConditionChecker) IsSpeedy() bool { 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 // test a specific deployment rule. This is required for properly detecting // and activating consensus rule changes. @@ -207,15 +214,9 @@ func (c deploymentChecker) MinerConfirmationWindow() uint32 { } // EligibleToActivate returns true if a custom deployment can transition from -// the LockedIn to the Active state. For normal deployments, this always -// returns true. However, some deployments add extra rules like a minimum -// activation height, which can be abstracted into a generic arbitrary check at -// 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. +// the LockedIn to the Active state. In addition to the traditional minimum +// activation height (MinActivationHeight), an optional AlwaysActiveHeight can +// force the deployment to be active after a specified height. func (c deploymentChecker) EligibleToActivate(blkNode *blockNode) bool { // No activation height, so it's always ready to go. if c.deployment.MinActivationHeight == 0 { @@ -249,6 +250,28 @@ func (c deploymentChecker) Condition(node *blockNode) (bool, error) { 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 + // block’s 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 // passed previous block node based on the state of started and locked in // rule change deployments. diff --git a/chaincfg/genesis.go b/chaincfg/genesis.go index 73d28610..f734feb2 100644 --- a/chaincfg/genesis.go +++ b/chaincfg/genesis.go @@ -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. diff --git a/chaincfg/genesis_test.go b/chaincfg/genesis_test.go index 1daf8479..b2975ffb 100644 --- a/chaincfg/genesis_test.go +++ b/chaincfg/genesis_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/davecgh/go-spew/spew" + "github.com/stretchr/testify/require" ) // 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 // for validity by checking the encoded bytes and hashes. func TestSimNetGenesisBlock(t *testing.T) { @@ -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, /* |....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 // the simulation test network as of protocol version 70002. var simNetGenesisBlockBytes = []byte{ diff --git a/chaincfg/params.go b/chaincfg/params.go index 1c329cb5..eb4f062e 100644 --- a/chaincfg/params.go +++ b/chaincfg/params.go @@ -8,6 +8,7 @@ import ( "encoding/binary" "encoding/hex" "errors" + "math" "math/big" "strings" "time" @@ -78,6 +79,16 @@ type Checkpoint struct { 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. type DNSSeed struct { // Host defines the hostname of the seed. @@ -108,6 +119,11 @@ type ConsensusDeployment struct { // activation. A value of 1815 block denotes a 90% threshold. 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 // ConsensusDeployment has started or not. DeploymentStarter ConsensusDeploymentStarter @@ -146,6 +162,10 @@ const ( // the deployment of BIPS 340, 341 and 342. 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 // determine how many defined deployments there currently are. @@ -189,6 +209,10 @@ type Params struct { // regtest like networks. 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 // BIP became active. BIP0034Height int32 @@ -375,6 +399,16 @@ var MainNetParams = Params{ 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: 0, DeploymentStarter: NewMedianTimeDeploymentStarter( @@ -486,6 +520,16 @@ var RegressionNetParams = Params{ 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: 0, DeploymentStarter: NewMedianTimeDeploymentStarter( @@ -620,6 +664,16 @@ var TestNet3Params = Params{ 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: 0, DeploymentStarter: NewMedianTimeDeploymentStarter( @@ -673,6 +727,131 @@ var TestNet3Params = Params{ 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 // network. This network is similar to the normal test network except it is // 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. }, + DeploymentTestDummyAlwaysActive: { + BitNumber: 29, + DeploymentStarter: NewMedianTimeDeploymentStarter( + time.Time{}, // Always available for vote + ), + DeploymentEnder: NewMedianTimeDeploymentEnder( + time.Time{}, // Never expires + ), + AlwaysActiveHeight: 1, + }, }, // Mempool parameters @@ -861,6 +1050,16 @@ func CustomSignetParams(challenge []byte, dnsSeeds []DNSSeed) Params { 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( @@ -1075,6 +1274,7 @@ func init() { // Register all default networks when the package is initialized. mustRegister(&MainNetParams) mustRegister(&TestNet3Params) + mustRegister(&TestNet4Params) mustRegister(&RegressionNetParams) mustRegister(&SimNetParams) } diff --git a/chaincfg/register_test.go b/chaincfg/register_test.go index bcb5b3c6..db254415 100644 --- a/chaincfg/register_test.go +++ b/chaincfg/register_test.go @@ -68,6 +68,11 @@ func TestRegister(t *testing.T) { params: &TestNet3Params, err: ErrDuplicateNet, }, + { + name: "duplicate testnet4", + params: &TestNet4Params, + err: ErrDuplicateNet, + }, { name: "duplicate simnet", params: &SimNetParams, @@ -83,6 +88,10 @@ func TestRegister(t *testing.T) { magic: TestNet3Params.PubKeyHashAddrID, valid: true, }, + { + magic: TestNet4Params.PubKeyHashAddrID, + valid: true, + }, { magic: RegressionNetParams.PubKeyHashAddrID, valid: true, @@ -109,6 +118,10 @@ func TestRegister(t *testing.T) { magic: TestNet3Params.ScriptHashAddrID, valid: true, }, + { + magic: TestNet4Params.ScriptHashAddrID, + valid: true, + }, { magic: RegressionNetParams.ScriptHashAddrID, valid: true, @@ -135,6 +148,10 @@ func TestRegister(t *testing.T) { prefix: TestNet3Params.Bech32HRPSegwit + "1", valid: true, }, + { + prefix: TestNet4Params.Bech32HRPSegwit + "1", + valid: true, + }, { prefix: RegressionNetParams.Bech32HRPSegwit + "1", valid: true, @@ -175,6 +192,11 @@ func TestRegister(t *testing.T) { want: TestNet3Params.HDPublicKeyID[:], err: nil, }, + { + priv: TestNet4Params.HDPrivateKeyID[:], + want: TestNet4Params.HDPublicKeyID[:], + err: nil, + }, { priv: RegressionNetParams.HDPrivateKeyID[:], want: RegressionNetParams.HDPublicKeyID[:], @@ -217,6 +239,10 @@ func TestRegister(t *testing.T) { magic: TestNet3Params.PubKeyHashAddrID, valid: true, }, + { + magic: TestNet4Params.PubKeyHashAddrID, + valid: true, + }, { magic: RegressionNetParams.PubKeyHashAddrID, valid: true, @@ -243,6 +269,10 @@ func TestRegister(t *testing.T) { magic: TestNet3Params.ScriptHashAddrID, valid: true, }, + { + magic: TestNet4Params.ScriptHashAddrID, + valid: true, + }, { magic: RegressionNetParams.ScriptHashAddrID, valid: true, @@ -269,6 +299,10 @@ func TestRegister(t *testing.T) { prefix: TestNet3Params.Bech32HRPSegwit + "1", valid: true, }, + { + prefix: TestNet4Params.Bech32HRPSegwit + "1", + valid: true, + }, { prefix: RegressionNetParams.Bech32HRPSegwit + "1", valid: true, @@ -324,6 +358,11 @@ func TestRegister(t *testing.T) { params: &TestNet3Params, err: ErrDuplicateNet, }, + { + name: "duplicate testnet4", + params: &TestNet4Params, + err: ErrDuplicateNet, + }, { name: "duplicate simnet", params: &SimNetParams, @@ -344,6 +383,10 @@ func TestRegister(t *testing.T) { magic: TestNet3Params.PubKeyHashAddrID, valid: true, }, + { + magic: TestNet4Params.PubKeyHashAddrID, + valid: true, + }, { magic: RegressionNetParams.PubKeyHashAddrID, valid: true, @@ -370,6 +413,10 @@ func TestRegister(t *testing.T) { magic: TestNet3Params.ScriptHashAddrID, valid: true, }, + { + magic: TestNet4Params.ScriptHashAddrID, + valid: true, + }, { magic: RegressionNetParams.ScriptHashAddrID, valid: true, @@ -396,6 +443,10 @@ func TestRegister(t *testing.T) { prefix: TestNet3Params.Bech32HRPSegwit + "1", valid: true, }, + { + prefix: TestNet4Params.Bech32HRPSegwit + "1", + valid: true, + }, { prefix: RegressionNetParams.Bech32HRPSegwit + "1", valid: true, @@ -436,6 +487,11 @@ func TestRegister(t *testing.T) { want: TestNet3Params.HDPublicKeyID[:], err: nil, }, + { + priv: TestNet4Params.HDPrivateKeyID[:], + want: TestNet4Params.HDPublicKeyID[:], + err: nil, + }, { priv: RegressionNetParams.HDPrivateKeyID[:], want: RegressionNetParams.HDPublicKeyID[:], diff --git a/cmd/addblock/config.go b/cmd/addblock/config.go index 51cd6c7e..5f479003 100644 --- a/cmd/addblock/config.go +++ b/cmd/addblock/config.go @@ -31,7 +31,7 @@ var ( 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. 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"` RegressionTest bool `long:"regtest" description:"Use the regression 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"` } @@ -108,6 +109,10 @@ func loadConfig() (*config, []string, error) { numNets++ activeNetParams = &chaincfg.TestNet3Params } + if cfg.TestNet4 { + numNets++ + activeNetParams = &chaincfg.TestNet4Params + } if cfg.RegressionTest { numNets++ activeNetParams = &chaincfg.RegressionNetParams diff --git a/cmd/btcctl/config.go b/cmd/btcctl/config.go index 44d28c60..023dd93a 100644 --- a/cmd/btcctl/config.go +++ b/cmd/btcctl/config.go @@ -106,7 +106,8 @@ type config struct { RPCUser string `short:"u" long:"rpcuser" description:"RPC username"` SimNet bool `long:"simnet" description:"Connect to the simulation test network"` 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"` ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"` Wallet bool `long:"wallet" description:"Connect to wallet"` @@ -125,6 +126,12 @@ func normalizeAddress(addr string, chain *chaincfg.Params, useWallet bool) (stri } else { defaultPort = "18334" } + case &chaincfg.TestNet4Params: + if useWallet { + defaultPort = "48332" + } else { + defaultPort = "48334" + } case &chaincfg.SimNetParams: if useWallet { defaultPort = "18554" @@ -272,6 +279,10 @@ func loadConfig() (*config, []string, error) { numNets++ network = &chaincfg.TestNet3Params } + if cfg.TestNet4 { + numNets++ + network = &chaincfg.TestNet4Params + } if cfg.SimNet { numNets++ network = &chaincfg.SimNetParams diff --git a/cmd/findcheckpoint/config.go b/cmd/findcheckpoint/config.go index fad01a03..d4ae5dfc 100644 --- a/cmd/findcheckpoint/config.go +++ b/cmd/findcheckpoint/config.go @@ -42,7 +42,8 @@ type config struct { 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"` 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. @@ -96,6 +97,10 @@ func loadConfig() (*config, []string, error) { numNets++ activeNetParams = &chaincfg.TestNet3Params } + if cfg.TestNet4 { + numNets++ + activeNetParams = &chaincfg.TestNet4Params + } if cfg.RegressionTest { numNets++ activeNetParams = &chaincfg.RegressionNetParams diff --git a/config.go b/config.go index 8e88fd59..04f926c2 100644 --- a/config.go +++ b/config.go @@ -171,7 +171,8 @@ type config struct { 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"` 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."` 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"` @@ -548,6 +549,10 @@ func loadConfig() (*config, []string, error) { numNets++ activeNetParams = &testNet3Params } + if cfg.TestNet4 { + numNets++ + activeNetParams = &testNet4Params + } if cfg.RegressionTest { numNets++ activeNetParams = ®ressionNetParams diff --git a/database/cmd/dbtool/globalconfig.go b/database/cmd/dbtool/globalconfig.go index db7f1324..bcea56a2 100644 --- a/database/cmd/dbtool/globalconfig.go +++ b/database/cmd/dbtool/globalconfig.go @@ -37,6 +37,7 @@ type config struct { RegressionTest bool `long:"regtest" description:"Use the regression test network"` SimNet bool `long:"simnet" description:"Use the simulation 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. @@ -84,6 +85,10 @@ func setupGlobalConfig() error { numNets++ activeNetParams = &chaincfg.TestNet3Params } + if cfg.TestNet4 { + numNets++ + activeNetParams = &chaincfg.TestNet4Params + } if cfg.RegressionTest { numNets++ activeNetParams = &chaincfg.RegressionNetParams diff --git a/go.mod b/go.mod index 04110578..f8b8e95f 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 golang.org/x/crypto v0.22.0 golang.org/x/sys v0.19.0 + pgregory.net/rapid v1.2.0 ) require ( diff --git a/go.sum b/go.sum index b92ab408..19f62db8 100644 --- a/go.sum +++ b/go.sum @@ -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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 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= diff --git a/integration/bip0009_test.go b/integration/bip0009_test.go index 5b644804..8f8b59a5 100644 --- a/integration/bip0009_test.go +++ b/integration/bip0009_test.go @@ -139,6 +139,14 @@ func testBIP0009(t *testing.T, forkKey string, deploymentID uint32) { } 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 *** // // 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-min-activation", chaincfg.DeploymentTestDummyMinActivation) + testBIP0009(t, "dummy-always-active", chaincfg.DeploymentTestDummyAlwaysActive) testBIP0009(t, "segwit", chaincfg.DeploymentSegwit) } diff --git a/params.go b/params.go index b4d1453d..30daec8d 100644 --- a/params.go +++ b/params.go @@ -21,7 +21,7 @@ type params struct { } // 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 // 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 @@ -41,13 +41,21 @@ var regressionNetParams = params{ } // 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. var testNet3Params = params{ Params: &chaincfg.TestNet3Params, 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 // (wire.SimNet). var simNetParams = params{ diff --git a/rpcclient/infrastructure.go b/rpcclient/infrastructure.go index f26d2c56..373ffad4 100644 --- a/rpcclient/infrastructure.go +++ b/rpcclient/infrastructure.go @@ -1529,6 +1529,8 @@ func New(config *ConnConfig, ntfnHandlers *NotificationHandlers) (*Client, error client.chainParams = &chaincfg.MainNetParams case chaincfg.TestNet3Params.Name: client.chainParams = &chaincfg.TestNet3Params + case chaincfg.TestNet4Params.Name: + client.chainParams = &chaincfg.TestNet4Params case chaincfg.RegressionNetParams.Name: client.chainParams = &chaincfg.RegressionNetParams case chaincfg.SigNetParams.Name: diff --git a/rpcserver.go b/rpcserver.go index e9fec435..736f1459 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1258,6 +1258,9 @@ func handleGetBlockChainInfo(s *rpcServer, cmd interface{}, closeChan <-chan str case chaincfg.DeploymentTestDummyMinActivation: forkName = "dummy-min-activation" + case chaincfg.DeploymentTestDummyAlwaysActive: + forkName = "dummy-always-active" + case chaincfg.DeploymentCSV: forkName = "csv" @@ -2358,7 +2361,7 @@ func handleGetInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (in Connections: s.cfg.ConnMgr.ConnectedCount(), Proxy: cfg.Proxy, Difficulty: getDifficultyRatio(best.Bits, s.cfg.ChainParams), - TestNet: cfg.TestNet3, + TestNet: cfg.TestNet3 || cfg.TestNet4, RelayFee: cfg.minRelayTxFee.ToBTC(), } @@ -2413,7 +2416,7 @@ func handleGetMiningInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{ HashesPerSec: s.cfg.CPUMiner.HashesPerSecond(), NetworkHashPS: networkHashesPerSec, PooledTx: uint64(s.cfg.TxMemPool.Count()), - TestNet: cfg.TestNet3, + TestNet: cfg.TestNet3 || cfg.TestNet4, } return &result, nil } diff --git a/wire/protocol.go b/wire/protocol.go index b6e5ea92..65d19f52 100644 --- a/wire/protocol.go +++ b/wire/protocol.go @@ -179,6 +179,9 @@ const ( // TestNet3 represents the test network (version 3). TestNet3 BitcoinNet = 0x0709110b + // TestNet4 represents the test network (version 4). + TestNet4 BitcoinNet = 0x283f161c + // SigNet represents the public default SigNet. For custom signets, // see CustomSignetParams. SigNet BitcoinNet = 0x40CF030A @@ -193,6 +196,7 @@ var bnStrings = map[BitcoinNet]string{ MainNet: "MainNet", TestNet: "TestNet", TestNet3: "TestNet3", + TestNet4: "TestNet4", SigNet: "SigNet", SimNet: "SimNet", } diff --git a/wire/protocol_test.go b/wire/protocol_test.go index 5ab5b9dd..46498849 100644 --- a/wire/protocol_test.go +++ b/wire/protocol_test.go @@ -49,6 +49,7 @@ func TestBitcoinNetStringer(t *testing.T) { {MainNet, "MainNet"}, {TestNet, "TestNet"}, {TestNet3, "TestNet3"}, + {TestNet4, "TestNet4"}, {SigNet, "SigNet"}, {SimNet, "SimNet"}, {0xffffffff, "Unknown BitcoinNet (4294967295)"},