mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-03-04 13:55:23 -05:00
test: check difficulty adjustment using alternate mainnet
This commit is contained in:
parent
c4f68c12e2
commit
4131f322ac
5 changed files with 4196 additions and 2 deletions
42
test/functional/data/README.md
Normal file
42
test/functional/data/README.md
Normal file
|
@ -0,0 +1,42 @@
|
|||
# Various test vectors
|
||||
|
||||
## mainnet_alt.json
|
||||
|
||||
For easier testing the difficulty is maximally increased in the first (and only)
|
||||
regarget period, by producing blocks approximately 2 minutes apart.
|
||||
|
||||
The alternate mainnet chain was generated as follows:
|
||||
- use faketime to set node clock to 2 minutes after genesis block
|
||||
- mine a block using a CPU miner such as https://github.com/pooler/cpuminer
|
||||
- restart node with a faketime 2 minutes later
|
||||
|
||||
```sh
|
||||
for i in {1..2015}
|
||||
do
|
||||
faketime "`date -d @"$(( 1231006505 + $i * 120 ))" +'%Y-%m-%d %H:%M:%S'`" \
|
||||
bitcoind -connect=0 -nocheckpoints -stopatheight=$i
|
||||
done
|
||||
```
|
||||
|
||||
The CPU miner is kept running as follows:
|
||||
|
||||
```sh
|
||||
./minerd --coinbase-addr 1NQpH6Nf8QtR2HphLRcvuVqfhXBXsiWn8r --no-stratum --algo sha256d --no-longpoll --scantime 3 --retry-pause 1
|
||||
```
|
||||
|
||||
The payout address is derived from first BIP32 test vector master key:
|
||||
|
||||
```
|
||||
pkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/44h/0h/0h/<0;1>/*)#fkjtr0yn
|
||||
```
|
||||
|
||||
It uses `pkh()` because `tr()` outputs at low heights are not spendable (`unexpected-witness`).
|
||||
|
||||
This makes each block determinisic except for its timestamp and nonce, which
|
||||
are stored in `mainnet_alt.json` and used to reconstruct the chain without
|
||||
having to redo the proof-of-work.
|
||||
|
||||
The timestamp was not kept constant because at difficulty 1 it's not sufficient
|
||||
to only grind the nonce. Grinding the extra_nonce or version field instead
|
||||
would have required additional (stratum) software. It would also make it more
|
||||
complicated to reconstruct the blocks in this test.
|
4036
test/functional/data/mainnet_alt.json
Normal file
4036
test/functional/data/mainnet_alt.json
Normal file
File diff suppressed because it is too large
Load diff
105
test/functional/mining_mainnet.py
Executable file
105
test/functional/mining_mainnet.py
Executable file
|
@ -0,0 +1,105 @@
|
|||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2025 The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
"""Test mining on an alternate mainnet
|
||||
|
||||
Test mining related RPCs that involve difficulty adjustment, which
|
||||
regtest doesn't have.
|
||||
|
||||
It uses an alternate mainnet chain. See data/README.md for how it was generated.
|
||||
|
||||
Mine one retarget period worth of blocks with a short interval in
|
||||
order to maximally raise the difficulty. Verify this using the getmininginfo RPC.
|
||||
|
||||
"""
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
)
|
||||
from test_framework.blocktools import (
|
||||
DIFF_1_N_BITS,
|
||||
DIFF_1_TARGET,
|
||||
DIFF_4_N_BITS,
|
||||
DIFF_4_TARGET,
|
||||
create_coinbase,
|
||||
nbits_str,
|
||||
target_str
|
||||
)
|
||||
|
||||
from test_framework.messages import (
|
||||
CBlock,
|
||||
)
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
# See data/README.md
|
||||
COINBASE_SCRIPT_PUBKEY="76a914eadbac7f36c37e39361168b7aaee3cb24a25312d88ac"
|
||||
|
||||
class MiningMainnetTest(BitcoinTestFramework):
|
||||
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 1
|
||||
self.setup_clean_chain = True
|
||||
self.chain = "" # main
|
||||
|
||||
def add_options(self, parser):
|
||||
parser.add_argument(
|
||||
'--datafile',
|
||||
default='data/mainnet_alt.json',
|
||||
help='Block data file (default: %(default)s)',
|
||||
)
|
||||
|
||||
self.add_wallet_options(parser)
|
||||
|
||||
def mine(self, height, prev_hash, blocks, node, fees=0):
|
||||
self.log.debug(f"height={height}")
|
||||
block = CBlock()
|
||||
block.nVersion = 0x20000000
|
||||
block.hashPrevBlock = int(prev_hash, 16)
|
||||
block.nTime = blocks['timestamps'][height - 1]
|
||||
block.nBits = DIFF_1_N_BITS
|
||||
block.nNonce = blocks['nonces'][height - 1]
|
||||
block.vtx = [create_coinbase(height=height, script_pubkey=bytes.fromhex(COINBASE_SCRIPT_PUBKEY), retarget_period=2016)]
|
||||
block.hashMerkleRoot = block.calc_merkle_root()
|
||||
block.rehash()
|
||||
block_hex = block.serialize(with_witness=False).hex()
|
||||
self.log.debug(block_hex)
|
||||
assert_equal(node.submitblock(block_hex), None)
|
||||
prev_hash = node.getbestblockhash()
|
||||
assert_equal(prev_hash, block.hash)
|
||||
return prev_hash
|
||||
|
||||
|
||||
def run_test(self):
|
||||
node = self.nodes[0]
|
||||
# Clear disk space warning
|
||||
node.stderr.seek(0)
|
||||
node.stderr.truncate()
|
||||
self.log.info("Load alternative mainnet blocks")
|
||||
path = os.path.join(os.path.dirname(os.path.realpath(__file__)), self.options.datafile)
|
||||
prev_hash = node.getbestblockhash()
|
||||
with open(path, encoding='utf-8') as f:
|
||||
blocks = json.load(f)
|
||||
n_blocks = len(blocks['timestamps'])
|
||||
assert_equal(n_blocks, 2015)
|
||||
for i in range(2015):
|
||||
prev_hash = self.mine(i + 1, prev_hash, blocks, node)
|
||||
|
||||
assert_equal(node.getblockcount(), 2015)
|
||||
|
||||
self.log.info("Check difficulty adjustment with getmininginfo")
|
||||
mining_info = node.getmininginfo()
|
||||
assert_equal(mining_info['difficulty'], 1)
|
||||
assert_equal(mining_info['bits'], nbits_str(DIFF_1_N_BITS))
|
||||
assert_equal(mining_info['target'], target_str(DIFF_1_TARGET))
|
||||
|
||||
assert_equal(mining_info['next']['height'], 2016)
|
||||
assert_equal(mining_info['next']['difficulty'], 4)
|
||||
assert_equal(mining_info['next']['bits'], nbits_str(DIFF_4_N_BITS))
|
||||
assert_equal(mining_info['next']['target'], target_str(DIFF_4_TARGET))
|
||||
|
||||
if __name__ == '__main__':
|
||||
MiningMainnetTest(__file__).main()
|
|
@ -66,10 +66,20 @@ NORMAL_GBT_REQUEST_PARAMS = {"rules": ["segwit"]}
|
|||
VERSIONBITS_LAST_OLD_BLOCK_VERSION = 4
|
||||
MIN_BLOCKS_TO_KEEP = 288
|
||||
|
||||
REGTEST_RETARGET_PERIOD = 150
|
||||
|
||||
REGTEST_N_BITS = 0x207fffff # difficulty retargeting is disabled in REGTEST chainparams"
|
||||
REGTEST_TARGET = 0x7fffff0000000000000000000000000000000000000000000000000000000000
|
||||
assert_equal(uint256_from_compact(REGTEST_N_BITS), REGTEST_TARGET)
|
||||
|
||||
DIFF_1_N_BITS = 0x1d00ffff
|
||||
DIFF_1_TARGET = 0x00000000ffff0000000000000000000000000000000000000000000000000000
|
||||
assert_equal(uint256_from_compact(DIFF_1_N_BITS), DIFF_1_TARGET)
|
||||
|
||||
DIFF_4_N_BITS = 0x1c3fffc0
|
||||
DIFF_4_TARGET = int(DIFF_1_TARGET / 4)
|
||||
assert_equal(uint256_from_compact(DIFF_4_N_BITS), DIFF_4_TARGET)
|
||||
|
||||
def nbits_str(nbits):
|
||||
return f"{nbits:08x}"
|
||||
|
||||
|
@ -133,7 +143,7 @@ def script_BIP34_coinbase_height(height):
|
|||
return CScript([CScriptNum(height)])
|
||||
|
||||
|
||||
def create_coinbase(height, pubkey=None, *, script_pubkey=None, extra_output_script=None, fees=0, nValue=50):
|
||||
def create_coinbase(height, pubkey=None, *, script_pubkey=None, extra_output_script=None, fees=0, nValue=50, retarget_period=REGTEST_RETARGET_PERIOD):
|
||||
"""Create a coinbase transaction.
|
||||
|
||||
If pubkey is passed in, the coinbase output will be a P2PK output;
|
||||
|
@ -146,7 +156,7 @@ def create_coinbase(height, pubkey=None, *, script_pubkey=None, extra_output_scr
|
|||
coinbaseoutput = CTxOut()
|
||||
coinbaseoutput.nValue = nValue * COIN
|
||||
if nValue == 50:
|
||||
halvings = int(height / 150) # regtest
|
||||
halvings = int(height / retarget_period)
|
||||
coinbaseoutput.nValue >>= halvings
|
||||
coinbaseoutput.nValue += fees
|
||||
if pubkey is not None:
|
||||
|
|
|
@ -316,6 +316,7 @@ BASE_SCRIPTS = [
|
|||
'wallet_upgradewallet.py --legacy-wallet',
|
||||
'wallet_crosschain.py',
|
||||
'mining_basic.py',
|
||||
'mining_mainnet.py',
|
||||
'feature_signet.py',
|
||||
'p2p_mutated_blocks.py',
|
||||
'wallet_implicitsegwit.py --legacy-wallet',
|
||||
|
|
Loading…
Add table
Reference in a new issue