/ test / functional / mining_basic.py
mining_basic.py
  1  #!/usr/bin/env python3
  2  # Copyright (c) 2014-2022 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 RPCs
  6  
  7  - getmininginfo
  8  - getblocktemplate proposal mode
  9  - submitblock"""
 10  
 11  import copy
 12  from decimal import Decimal
 13  
 14  from test_framework.blocktools import (
 15      create_coinbase,
 16      get_witness_script,
 17      NORMAL_GBT_REQUEST_PARAMS,
 18      TIME_GENESIS_BLOCK,
 19  )
 20  from test_framework.messages import (
 21      BLOCK_HEADER_SIZE,
 22      CBlock,
 23      CBlockHeader,
 24      COIN,
 25      ser_uint256,
 26  )
 27  from test_framework.p2p import P2PDataStore
 28  from test_framework.test_framework import BitcoinTestFramework
 29  from test_framework.util import (
 30      assert_equal,
 31      assert_raises_rpc_error,
 32      get_fee,
 33  )
 34  from test_framework.wallet import MiniWallet
 35  
 36  
 37  VERSIONBITS_TOP_BITS = 0x20000000
 38  VERSIONBITS_DEPLOYMENT_TESTDUMMY_BIT = 28
 39  DEFAULT_BLOCK_MIN_TX_FEE = 1000  # default `-blockmintxfee` setting [sat/kvB]
 40  
 41  
 42  def assert_template(node, block, expect, rehash=True):
 43      if rehash:
 44          block.hashMerkleRoot = block.calc_merkle_root()
 45      rsp = node.getblocktemplate(template_request={
 46          'data': block.serialize().hex(),
 47          'mode': 'proposal',
 48          'rules': ['segwit'],
 49      })
 50      assert_equal(rsp, expect)
 51  
 52  
 53  class MiningTest(BitcoinTestFramework):
 54      def set_test_params(self):
 55          self.num_nodes = 2
 56          self.setup_clean_chain = True
 57          self.supports_cli = False
 58  
 59      def mine_chain(self):
 60          self.log.info('Create some old blocks')
 61          for t in range(TIME_GENESIS_BLOCK, TIME_GENESIS_BLOCK + 200 * 600, 600):
 62              self.nodes[0].setmocktime(t)
 63              self.generate(self.wallet, 1, sync_fun=self.no_op)
 64          mining_info = self.nodes[0].getmininginfo()
 65          assert_equal(mining_info['blocks'], 200)
 66          assert_equal(mining_info['currentblocktx'], 0)
 67          assert_equal(mining_info['currentblockweight'], 4000)
 68  
 69          self.log.info('test blockversion')
 70          self.restart_node(0, extra_args=[f'-mocktime={t}', '-blockversion=1337'])
 71          self.connect_nodes(0, 1)
 72          assert_equal(1337, self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)['version'])
 73          self.restart_node(0, extra_args=[f'-mocktime={t}'])
 74          self.connect_nodes(0, 1)
 75          assert_equal(VERSIONBITS_TOP_BITS + (1 << VERSIONBITS_DEPLOYMENT_TESTDUMMY_BIT), self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)['version'])
 76          self.restart_node(0)
 77          self.connect_nodes(0, 1)
 78  
 79      def test_blockmintxfee_parameter(self):
 80          self.log.info("Test -blockmintxfee setting")
 81          self.restart_node(0, extra_args=['-minrelaytxfee=0', '-persistmempool=0'])
 82          node = self.nodes[0]
 83  
 84          # test default (no parameter), zero and a bunch of arbitrary blockmintxfee rates [sat/kvB]
 85          for blockmintxfee_sat_kvb in (DEFAULT_BLOCK_MIN_TX_FEE, 0, 50, 100, 500, 2500, 5000, 21000, 333333, 2500000):
 86              blockmintxfee_btc_kvb = blockmintxfee_sat_kvb / Decimal(COIN)
 87              if blockmintxfee_sat_kvb == DEFAULT_BLOCK_MIN_TX_FEE:
 88                  self.log.info(f"-> Default -blockmintxfee setting ({blockmintxfee_sat_kvb} sat/kvB)...")
 89              else:
 90                  blockmintxfee_parameter = f"-blockmintxfee={blockmintxfee_btc_kvb:.8f}"
 91                  self.log.info(f"-> Test {blockmintxfee_parameter} ({blockmintxfee_sat_kvb} sat/kvB)...")
 92                  self.restart_node(0, extra_args=[blockmintxfee_parameter, '-minrelaytxfee=0', '-persistmempool=0'])
 93                  self.wallet.rescan_utxos()  # to avoid spending outputs of txs that are not in mempool anymore after restart
 94  
 95              # submit one tx with exactly the blockmintxfee rate, and one slightly below
 96              tx_with_min_feerate = self.wallet.send_self_transfer(from_node=node, fee_rate=blockmintxfee_btc_kvb)
 97              assert_equal(tx_with_min_feerate["fee"], get_fee(tx_with_min_feerate["tx"].get_vsize(), blockmintxfee_btc_kvb))
 98              if blockmintxfee_btc_kvb > 0:
 99                  lowerfee_btc_kvb = blockmintxfee_btc_kvb - Decimal(10)/COIN  # 0.01 sat/vbyte lower
100                  tx_below_min_feerate = self.wallet.send_self_transfer(from_node=node, fee_rate=lowerfee_btc_kvb)
101                  assert_equal(tx_below_min_feerate["fee"], get_fee(tx_below_min_feerate["tx"].get_vsize(), lowerfee_btc_kvb))
102              else:  # go below zero fee by using modified fees
103                  tx_below_min_feerate = self.wallet.send_self_transfer(from_node=node, fee_rate=blockmintxfee_btc_kvb)
104                  node.prioritisetransaction(tx_below_min_feerate["txid"], 0, -1)
105  
106              # check that tx below specified fee-rate is neither in template nor in the actual block
107              block_template = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)
108              block_template_txids = [tx['txid'] for tx in block_template['transactions']]
109              self.generate(self.wallet, 1, sync_fun=self.no_op)
110              block = node.getblock(node.getbestblockhash(), verbosity=2)
111              block_txids = [tx['txid'] for tx in block['tx']]
112  
113              assert tx_with_min_feerate['txid'] in block_template_txids
114              assert tx_with_min_feerate['txid'] in block_txids
115              assert tx_below_min_feerate['txid'] not in block_template_txids
116              assert tx_below_min_feerate['txid'] not in block_txids
117  
118      def run_test(self):
119          node = self.nodes[0]
120          self.wallet = MiniWallet(node)
121          self.mine_chain()
122  
123          def assert_submitblock(block, result_str_1, result_str_2=None):
124              block.solve()
125              result_str_2 = result_str_2 or 'duplicate-invalid'
126              assert_equal(result_str_1, node.submitblock(hexdata=block.serialize().hex()))
127              assert_equal(result_str_2, node.submitblock(hexdata=block.serialize().hex()))
128  
129          self.log.info('getmininginfo')
130          mining_info = node.getmininginfo()
131          assert_equal(mining_info['blocks'], 200)
132          assert_equal(mining_info['chain'], self.chain)
133          assert 'currentblocktx' not in mining_info
134          assert 'currentblockweight' not in mining_info
135          assert_equal(mining_info['difficulty'], Decimal('4.656542373906925E-10'))
136          assert_equal(mining_info['networkhashps'], Decimal('0.003333333333333334'))
137          assert_equal(mining_info['pooledtx'], 0)
138  
139          self.log.info("getblocktemplate: Test default witness commitment")
140          txid = int(self.wallet.send_self_transfer(from_node=node)['wtxid'], 16)
141          tmpl = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)
142  
143          # Check that default_witness_commitment is present.
144          assert 'default_witness_commitment' in tmpl
145          witness_commitment = tmpl['default_witness_commitment']
146  
147          # Check that default_witness_commitment is correct.
148          witness_root = CBlock.get_merkle_root([ser_uint256(0),
149                                                 ser_uint256(txid)])
150          script = get_witness_script(witness_root, 0)
151          assert_equal(witness_commitment, script.hex())
152  
153          # Mine a block to leave initial block download and clear the mempool
154          self.generatetoaddress(node, 1, node.get_deterministic_priv_key().address)
155          tmpl = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)
156          self.log.info("getblocktemplate: Test capability advertised")
157          assert 'proposal' in tmpl['capabilities']
158          assert 'coinbasetxn' not in tmpl
159  
160          next_height = int(tmpl["height"])
161          coinbase_tx = create_coinbase(height=next_height)
162          # sequence numbers must not be max for nLockTime to have effect
163          coinbase_tx.vin[0].nSequence = 2**32 - 2
164          coinbase_tx.rehash()
165  
166          block = CBlock()
167          block.nVersion = tmpl["version"]
168          block.hashPrevBlock = int(tmpl["previousblockhash"], 16)
169          block.nTime = tmpl["curtime"]
170          block.nBits = int(tmpl["bits"], 16)
171          block.nNonce = 0
172          block.vtx = [coinbase_tx]
173  
174          self.log.info("getblocktemplate: segwit rule must be set")
175          assert_raises_rpc_error(-8, "getblocktemplate must be called with the segwit rule set", node.getblocktemplate, {})
176  
177          self.log.info("getblocktemplate: Test valid block")
178          assert_template(node, block, None)
179  
180          self.log.info("submitblock: Test block decode failure")
181          assert_raises_rpc_error(-22, "Block decode failed", node.submitblock, block.serialize()[:-15].hex())
182  
183          self.log.info("getblocktemplate: Test bad input hash for coinbase transaction")
184          bad_block = copy.deepcopy(block)
185          bad_block.vtx[0].vin[0].prevout.hash += 1
186          bad_block.vtx[0].rehash()
187          assert_template(node, bad_block, 'bad-cb-missing')
188  
189          self.log.info("submitblock: Test invalid coinbase transaction")
190          assert_raises_rpc_error(-22, "Block does not start with a coinbase", node.submitblock, CBlock().serialize().hex())
191          assert_raises_rpc_error(-22, "Block does not start with a coinbase", node.submitblock, bad_block.serialize().hex())
192  
193          self.log.info("getblocktemplate: Test truncated final transaction")
194          assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, {
195              'data': block.serialize()[:-1].hex(),
196              'mode': 'proposal',
197              'rules': ['segwit'],
198          })
199  
200          self.log.info("getblocktemplate: Test duplicate transaction")
201          bad_block = copy.deepcopy(block)
202          bad_block.vtx.append(bad_block.vtx[0])
203          assert_template(node, bad_block, 'bad-txns-duplicate')
204          assert_submitblock(bad_block, 'bad-txns-duplicate', 'bad-txns-duplicate')
205  
206          self.log.info("getblocktemplate: Test invalid transaction")
207          bad_block = copy.deepcopy(block)
208          bad_tx = copy.deepcopy(bad_block.vtx[0])
209          bad_tx.vin[0].prevout.hash = 255
210          bad_tx.rehash()
211          bad_block.vtx.append(bad_tx)
212          assert_template(node, bad_block, 'bad-txns-inputs-missingorspent')
213          assert_submitblock(bad_block, 'bad-txns-inputs-missingorspent')
214  
215          self.log.info("getblocktemplate: Test nonfinal transaction")
216          bad_block = copy.deepcopy(block)
217          bad_block.vtx[0].nLockTime = 2**32 - 1
218          bad_block.vtx[0].rehash()
219          assert_template(node, bad_block, 'bad-txns-nonfinal')
220          assert_submitblock(bad_block, 'bad-txns-nonfinal')
221  
222          self.log.info("getblocktemplate: Test bad tx count")
223          # The tx count is immediately after the block header
224          bad_block_sn = bytearray(block.serialize())
225          assert_equal(bad_block_sn[BLOCK_HEADER_SIZE], 1)
226          bad_block_sn[BLOCK_HEADER_SIZE] += 1
227          assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, {
228              'data': bad_block_sn.hex(),
229              'mode': 'proposal',
230              'rules': ['segwit'],
231          })
232  
233          self.log.info("getblocktemplate: Test bad bits")
234          bad_block = copy.deepcopy(block)
235          bad_block.nBits = 469762303  # impossible in the real world
236          assert_template(node, bad_block, 'bad-diffbits')
237  
238          self.log.info("getblocktemplate: Test bad merkle root")
239          bad_block = copy.deepcopy(block)
240          bad_block.hashMerkleRoot += 1
241          assert_template(node, bad_block, 'bad-txnmrklroot', False)
242          assert_submitblock(bad_block, 'bad-txnmrklroot', 'bad-txnmrklroot')
243  
244          self.log.info("getblocktemplate: Test bad timestamps")
245          bad_block = copy.deepcopy(block)
246          bad_block.nTime = 2**32 - 1
247          assert_template(node, bad_block, 'time-too-new')
248          assert_submitblock(bad_block, 'time-too-new', 'time-too-new')
249          bad_block.nTime = 0
250          assert_template(node, bad_block, 'time-too-old')
251          assert_submitblock(bad_block, 'time-too-old', 'time-too-old')
252  
253          self.log.info("getblocktemplate: Test not best block")
254          bad_block = copy.deepcopy(block)
255          bad_block.hashPrevBlock = 123
256          assert_template(node, bad_block, 'inconclusive-not-best-prevblk')
257          assert_submitblock(bad_block, 'prev-blk-not-found', 'prev-blk-not-found')
258  
259          self.log.info('submitheader tests')
260          assert_raises_rpc_error(-22, 'Block header decode failed', lambda: node.submitheader(hexdata='xx' * BLOCK_HEADER_SIZE))
261          assert_raises_rpc_error(-22, 'Block header decode failed', lambda: node.submitheader(hexdata='ff' * (BLOCK_HEADER_SIZE-2)))
262          assert_raises_rpc_error(-25, 'Must submit previous header', lambda: node.submitheader(hexdata=super(CBlock, bad_block).serialize().hex()))
263  
264          block.nTime += 1
265          block.solve()
266  
267          def chain_tip(b_hash, *, status='headers-only', branchlen=1):
268              return {'hash': b_hash, 'height': 202, 'branchlen': branchlen, 'status': status}
269  
270          assert chain_tip(block.hash) not in node.getchaintips()
271          node.submitheader(hexdata=block.serialize().hex())
272          assert chain_tip(block.hash) in node.getchaintips()
273          node.submitheader(hexdata=CBlockHeader(block).serialize().hex())  # Noop
274          assert chain_tip(block.hash) in node.getchaintips()
275  
276          bad_block_root = copy.deepcopy(block)
277          bad_block_root.hashMerkleRoot += 2
278          bad_block_root.solve()
279          assert chain_tip(bad_block_root.hash) not in node.getchaintips()
280          node.submitheader(hexdata=CBlockHeader(bad_block_root).serialize().hex())
281          assert chain_tip(bad_block_root.hash) in node.getchaintips()
282          # Should still reject invalid blocks, even if we have the header:
283          assert_equal(node.submitblock(hexdata=bad_block_root.serialize().hex()), 'bad-txnmrklroot')
284          assert_equal(node.submitblock(hexdata=bad_block_root.serialize().hex()), 'bad-txnmrklroot')
285          assert chain_tip(bad_block_root.hash) in node.getchaintips()
286          # We know the header for this invalid block, so should just return early without error:
287          node.submitheader(hexdata=CBlockHeader(bad_block_root).serialize().hex())
288          assert chain_tip(bad_block_root.hash) in node.getchaintips()
289  
290          bad_block_lock = copy.deepcopy(block)
291          bad_block_lock.vtx[0].nLockTime = 2**32 - 1
292          bad_block_lock.vtx[0].rehash()
293          bad_block_lock.hashMerkleRoot = bad_block_lock.calc_merkle_root()
294          bad_block_lock.solve()
295          assert_equal(node.submitblock(hexdata=bad_block_lock.serialize().hex()), 'bad-txns-nonfinal')
296          assert_equal(node.submitblock(hexdata=bad_block_lock.serialize().hex()), 'duplicate-invalid')
297          # Build a "good" block on top of the submitted bad block
298          bad_block2 = copy.deepcopy(block)
299          bad_block2.hashPrevBlock = bad_block_lock.sha256
300          bad_block2.solve()
301          assert_raises_rpc_error(-25, 'bad-prevblk', lambda: node.submitheader(hexdata=CBlockHeader(bad_block2).serialize().hex()))
302  
303          # Should reject invalid header right away
304          bad_block_time = copy.deepcopy(block)
305          bad_block_time.nTime = 1
306          bad_block_time.solve()
307          assert_raises_rpc_error(-25, 'time-too-old', lambda: node.submitheader(hexdata=CBlockHeader(bad_block_time).serialize().hex()))
308  
309          # Should ask for the block from a p2p node, if they announce the header as well:
310          peer = node.add_p2p_connection(P2PDataStore())
311          peer.wait_for_getheaders(timeout=5)  # Drop the first getheaders
312          peer.send_blocks_and_test(blocks=[block], node=node)
313          # Must be active now:
314          assert chain_tip(block.hash, status='active', branchlen=0) in node.getchaintips()
315  
316          # Building a few blocks should give the same results
317          self.generatetoaddress(node, 10, node.get_deterministic_priv_key().address)
318          assert_raises_rpc_error(-25, 'time-too-old', lambda: node.submitheader(hexdata=CBlockHeader(bad_block_time).serialize().hex()))
319          assert_raises_rpc_error(-25, 'bad-prevblk', lambda: node.submitheader(hexdata=CBlockHeader(bad_block2).serialize().hex()))
320          node.submitheader(hexdata=CBlockHeader(block).serialize().hex())
321          node.submitheader(hexdata=CBlockHeader(bad_block_root).serialize().hex())
322          assert_equal(node.submitblock(hexdata=block.serialize().hex()), 'duplicate')  # valid
323  
324          self.test_blockmintxfee_parameter()
325  
326  
327  if __name__ == '__main__':
328      MiningTest().main()