0
0
Fork 0
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:
Sjors Provoost 2024-12-31 14:48:40 +01:00
parent c4f68c12e2
commit 4131f322ac
No known key found for this signature in database
GPG key ID: 57FF9BDBCC301009
5 changed files with 4196 additions and 2 deletions

View 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.

File diff suppressed because it is too large Load diff

105
test/functional/mining_mainnet.py Executable file
View 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()

View file

@ -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:

View file

@ -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',