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()