/ test / functional / mining_mainnet.py
mining_mainnet.py
  1  #!/usr/bin/env python3
  2  # Copyright (c) 2025-present The Bitcoin Core developers
  3  # Distributed under the MIT software license, see the accompanying
  4  # file COPYING or http://www.opensource.org/licenses/mit-license.php.
  5  """Test mining on an alternate mainnet
  6  
  7  Test mining related RPCs that involve difficulty adjustment, which
  8  regtest doesn't have.
  9  
 10  It uses an alternate mainnet chain. See data/README.md for how it was generated.
 11  
 12  Mine one retarget period worth of blocks with a short interval in
 13  order to maximally raise the difficulty. Verify this using the getmininginfo RPC.
 14  
 15  """
 16  
 17  from test_framework.test_framework import BitcoinTestFramework
 18  from test_framework.util import (
 19      assert_equal,
 20  )
 21  from test_framework.blocktools import (
 22      DIFF_1_N_BITS,
 23      DIFF_1_TARGET,
 24      DIFF_4_N_BITS,
 25      DIFF_4_TARGET,
 26      create_coinbase,
 27      nbits_str,
 28      target_str
 29  )
 30  
 31  from test_framework.messages import (
 32      CBlock,
 33      SEQUENCE_FINAL,
 34  )
 35  
 36  import json
 37  import os
 38  
 39  # See data/README.md
 40  COINBASE_SCRIPT_PUBKEY="76a914eadbac7f36c37e39361168b7aaee3cb24a25312d88ac"
 41  
 42  class MiningMainnetTest(BitcoinTestFramework):
 43  
 44      def set_test_params(self):
 45          self.num_nodes = 1
 46          self.setup_clean_chain = True
 47          self.chain = "" # main
 48  
 49      def add_options(self, parser):
 50          parser.add_argument(
 51              '--datafile',
 52              default='data/mainnet_alt.json',
 53              help='Block data file (default: %(default)s)',
 54          )
 55  
 56      def mine(self, height, prev_hash, blocks, node):
 57          self.log.debug(f"height={height}")
 58          block = CBlock()
 59          block.nVersion = 0x20000000
 60          block.hashPrevBlock = int(prev_hash, 16)
 61          block.nTime = blocks['timestamps'][height - 1]
 62          block.nBits = DIFF_1_N_BITS if height < 2016 else DIFF_4_N_BITS
 63          block.nNonce = blocks['nonces'][height - 1]
 64          block.vtx = [create_coinbase(height=height, script_pubkey=bytes.fromhex(COINBASE_SCRIPT_PUBKEY), halving_period=210000)]
 65          # The alternate mainnet chain was mined with non-timelocked coinbase txs.
 66          block.vtx[0].nLockTime = 0
 67          block.vtx[0].vin[0].nSequence = SEQUENCE_FINAL
 68          block.hashMerkleRoot = block.calc_merkle_root()
 69          block_hex = block.serialize(with_witness=False).hex()
 70          self.log.debug(block_hex)
 71          assert_equal(node.submitblock(block_hex), None)
 72          prev_hash = node.getbestblockhash()
 73          assert_equal(prev_hash, block.hash_hex)
 74          return prev_hash
 75  
 76  
 77      def run_test(self):
 78          node = self.nodes[0]
 79          # Clear disk space warning
 80          node.stderr.seek(0)
 81          node.stderr.truncate()
 82          self.log.info("Load alternative mainnet blocks")
 83          path = os.path.join(os.path.dirname(os.path.realpath(__file__)), self.options.datafile)
 84          prev_hash = node.getbestblockhash()
 85          blocks = None
 86          with open(path) as f:
 87              blocks = json.load(f)
 88              n_blocks = len(blocks['timestamps'])
 89              assert_equal(n_blocks, 2016)
 90  
 91          # Mine up to the last block of the first retarget period
 92          for i in range(2015):
 93              prev_hash = self.mine(i + 1, prev_hash, blocks, node)
 94  
 95          assert_equal(node.getblockcount(), 2015)
 96  
 97          self.log.info("Check difficulty adjustment with getmininginfo")
 98          mining_info = node.getmininginfo()
 99          assert_equal(mining_info['difficulty'], 1)
100          assert_equal(mining_info['bits'], nbits_str(DIFF_1_N_BITS))
101          assert_equal(mining_info['target'], target_str(DIFF_1_TARGET))
102  
103          assert_equal(mining_info['next']['height'], 2016)
104          assert_equal(mining_info['next']['difficulty'], 4)
105          assert_equal(mining_info['next']['bits'], nbits_str(DIFF_4_N_BITS))
106          assert_equal(mining_info['next']['target'], target_str(DIFF_4_TARGET))
107  
108          # Mine first block of the second retarget period
109          height = 2016
110          prev_hash = self.mine(height, prev_hash, blocks, node)
111          assert_equal(node.getblockcount(), height)
112  
113          mining_info = node.getmininginfo()
114          assert_equal(mining_info['difficulty'], 4)
115  
116          self.log.info("getblock RPC should show historical target")
117          block_info = node.getblock(node.getblockhash(1))
118  
119          assert_equal(block_info['difficulty'], 1)
120          assert_equal(block_info['bits'], nbits_str(DIFF_1_N_BITS))
121          assert_equal(block_info['target'], target_str(DIFF_1_TARGET))
122  
123  
124  if __name__ == '__main__':
125      MiningMainnetTest(__file__).main()