mining_template_verification.py
1 #!/usr/bin/env python3 2 # Copyright (c) 2024-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 getblocktemplate RPC in proposal mode 6 7 Generate several blocks and test them against the getblocktemplate RPC. 8 """ 9 10 from concurrent.futures import ThreadPoolExecutor 11 12 import copy 13 14 from test_framework.blocktools import ( 15 create_block, 16 create_coinbase, 17 add_witness_commitment, 18 ) 19 20 from test_framework.test_framework import BitcoinTestFramework 21 from test_framework.util import ( 22 assert_equal, 23 assert_raises_rpc_error, 24 ) 25 26 from test_framework.messages import ( 27 BLOCK_HEADER_SIZE, 28 uint256_from_compact, 29 ) 30 31 from test_framework.wallet import ( 32 MiniWallet, 33 ) 34 35 def assert_template(node, block, expect, *, rehash=True, submit=True, solve=True, expect_submit=None): 36 if rehash: 37 block.hashMerkleRoot = block.calc_merkle_root() 38 39 rsp = node.getblocktemplate(template_request={ 40 'data': block.serialize().hex(), 41 'mode': 'proposal', 42 'rules': ['segwit'], 43 }) 44 assert_equal(rsp, expect) 45 # Only attempt to submit invalid templates 46 if submit and expect is not None: 47 # submitblock runs checks in a different order, so may not return 48 # the same error 49 if expect_submit is None: 50 expect_submit = expect 51 if solve: 52 block.solve() 53 assert_equal(node.submitblock(block.serialize().hex()), expect_submit) 54 55 class MiningTemplateVerificationTest(BitcoinTestFramework): 56 57 def set_test_params(self): 58 self.num_nodes = 1 59 60 def valid_block_test(self, node, block): 61 self.log.info("Valid block") 62 assert_template(node, block, None) 63 64 def cb_missing_test(self, node, block): 65 self.log.info("Bad input hash for coinbase transaction") 66 bad_block = copy.deepcopy(block) 67 bad_block.vtx[0].vin[0].prevout.hash += 1 68 assert_template(node, bad_block, 'bad-cb-missing') 69 70 def block_without_transactions_test(self, node, block): 71 self.log.info("Block with no transactions") 72 73 no_tx_block = copy.deepcopy(block) 74 no_tx_block.vtx.clear() 75 no_tx_block.hashMerkleRoot = 0 76 no_tx_block.solve() 77 assert_template(node, no_tx_block, 'bad-blk-length', rehash=False) 78 79 def truncated_final_transaction_test(self, node, block): 80 self.log.info("Truncated final transaction") 81 assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, 82 template_request={ 83 "data": block.serialize()[:-1].hex(), 84 "mode": "proposal", 85 "rules": ["segwit"], 86 } 87 ) 88 89 def duplicate_transaction_test(self, node, block): 90 self.log.info("Duplicate transaction") 91 bad_block = copy.deepcopy(block) 92 bad_block.vtx.append(bad_block.vtx[0]) 93 assert_template(node, bad_block, 'bad-txns-duplicate') 94 95 def thin_air_spending_test(self, node, block): 96 self.log.info("Transaction that spends from thin air") 97 bad_block = copy.deepcopy(block) 98 bad_tx = copy.deepcopy(bad_block.vtx[0]) 99 bad_tx.vin[0].prevout.hash = 255 100 bad_block.vtx.append(bad_tx) 101 assert_template(node, bad_block, 'bad-txns-inputs-missingorspent') 102 103 def non_final_transaction_test(self, node, block): 104 self.log.info("Non-final transaction") 105 bad_block = copy.deepcopy(block) 106 bad_block.vtx[0].nLockTime = 2**32 - 1 107 assert_template(node, bad_block, 'bad-txns-nonfinal') 108 109 def bad_tx_count_test(self, node, block): 110 self.log.info("Bad tx count") 111 # The tx count is immediately after the block header 112 bad_block_sn = bytearray(block.serialize()) 113 assert_equal(bad_block_sn[BLOCK_HEADER_SIZE], 1) 114 bad_block_sn[BLOCK_HEADER_SIZE] += 1 115 assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, { 116 'data': bad_block_sn.hex(), 117 'mode': 'proposal', 118 'rules': ['segwit'], 119 }) 120 121 def nbits_test(self, node, block): 122 self.log.info("Extremely high nBits") 123 bad_block = copy.deepcopy(block) 124 bad_block.nBits = 469762303 # impossible in the real world 125 assert_template(node, bad_block, "bad-diffbits", solve=False, expect_submit="high-hash") 126 127 self.log.info("Lowering nBits should make the block invalid") 128 bad_block = copy.deepcopy(block) 129 bad_block.nBits -= 1 130 assert_template(node, bad_block, "bad-diffbits") 131 132 def merkle_root_test(self, node, block): 133 self.log.info("Bad merkle root") 134 bad_block = copy.deepcopy(block) 135 bad_block.hashMerkleRoot += 1 136 assert_template(node, bad_block, 'bad-txnmrklroot', rehash=False) 137 138 def bad_timestamp_test(self, node, block): 139 self.log.info("Bad timestamps") 140 bad_block = copy.deepcopy(block) 141 bad_block.nTime = 2**32 - 1 142 assert_template(node, bad_block, 'time-too-new') 143 bad_block.nTime = 0 144 assert_template(node, bad_block, 'time-too-old') 145 146 def current_tip_test(self, node, block): 147 self.log.info("Block must build on the current tip") 148 bad_block = copy.deepcopy(block) 149 bad_block.hashPrevBlock = 123 150 bad_block.solve() 151 152 assert_template(node, bad_block, "inconclusive-not-best-prevblk", expect_submit="prev-blk-not-found") 153 154 def pow_test(self, node, block): 155 '''Modifies block with the generated PoW''' 156 self.log.info("Generate a block") 157 target = uint256_from_compact(block.nBits) 158 # Ensure that it doesn't meet the target by coincidence 159 while block.hash_int <= target: 160 block.nNonce += 1 161 self.log.debug("Found a nonce") 162 163 self.log.info("A block template doesn't need PoW") 164 assert_template(node, block, None) 165 166 self.log.info("Add proof of work") 167 block.solve() 168 assert_template(node, block, None) 169 170 def submit_test(self, node, block_0_height, block): 171 self.log.info("getblocktemplate call in previous tests did not submit the block") 172 assert_equal(node.getblockcount(), block_0_height + 1) 173 174 self.log.info("Submitting this block should succeed") 175 assert_equal(node.submitblock(block.serialize().hex()), None) 176 node.waitforblockheight(2) 177 178 def transaction_test(self, node, block_0_height, tx): 179 self.log.info("make block template with a transaction") 180 181 block_1 = node.getblock(node.getblockhash(block_0_height + 1)) 182 block_2_hash = node.getblockhash(block_0_height + 2) 183 184 block_3 = create_block( 185 int(block_2_hash, 16), 186 create_coinbase(block_0_height + 3), 187 block_1["mediantime"] + 1, 188 txlist=[tx["hex"]], 189 ) 190 assert_equal(len(block_3.vtx), 2) 191 add_witness_commitment(block_3) 192 block_3.solve() 193 assert_template(node, block_3, None) 194 195 self.log.info("checking block validity did not update the UTXO set") 196 # Call again to ensure the UTXO set wasn't updated 197 assert_template(node, block_3, None) 198 199 def overspending_transaction_test(self, node, block_0_height, tx): 200 self.log.info("Add an transaction that spends too much") 201 202 block_1 = node.getblock(node.getblockhash(block_0_height + 1)) 203 block_2_hash = node.getblockhash(block_0_height + 2) 204 205 bad_tx = copy.deepcopy(tx) 206 bad_tx["tx"].vout[0].nValue = 10000000000 207 bad_tx_hex = bad_tx["tx"].serialize().hex() 208 assert_equal( 209 node.testmempoolaccept([bad_tx_hex])[0]["reject-reason"], 210 "bad-txns-in-belowout", 211 ) 212 block_3 = create_block( 213 int(block_2_hash, 16), 214 create_coinbase(block_0_height + 3), 215 block_1["mediantime"] + 1, 216 txlist=[bad_tx_hex], 217 ) 218 assert_equal(len(block_3.vtx), 2) 219 add_witness_commitment(block_3) 220 block_3.solve() 221 222 assert_template(node, block_3, "bad-txns-in-belowout") 223 224 def spend_twice_test(self, node, block_0_height, tx): 225 block_1 = node.getblock(node.getblockhash(block_0_height + 1)) 226 block_2_hash = node.getblockhash(block_0_height + 2) 227 228 self.log.info("Can't spend coins twice") 229 tx_hex = tx["tx"].serialize().hex() 230 tx_2 = copy.deepcopy(tx) 231 tx_2_hex = tx_2["tx"].serialize().hex() 232 # Nothing wrong with these transactions individually 233 assert_equal(node.testmempoolaccept([tx_hex])[0]["allowed"], True) 234 assert_equal(node.testmempoolaccept([tx_2_hex])[0]["allowed"], True) 235 # But can't be combined 236 assert_equal( 237 node.testmempoolaccept([tx_hex, tx_2_hex])[0]["package-error"], 238 "package-contains-duplicates", 239 ) 240 block_3 = create_block( 241 int(block_2_hash, 16), 242 create_coinbase(block_0_height + 3), 243 block_1["mediantime"] + 1, 244 txlist=[tx_hex, tx_2_hex], 245 ) 246 assert_equal(len(block_3.vtx), 3) 247 add_witness_commitment(block_3) 248 249 assert_template(node, block_3, "bad-txns-inputs-missingorspent", submit=False) 250 251 return block_3 252 253 def parallel_test(self, node, block_3): 254 # Ensure that getblocktemplate can be called concurrently by many threads. 255 self.log.info("Check blocks in parallel") 256 check_50_blocks = lambda n: [ 257 assert_template(n, block_3, "bad-txns-inputs-missingorspent", submit=False) 258 for _ in range(50) 259 ] 260 rpcs = [node.cli for _ in range(6)] 261 with ThreadPoolExecutor(max_workers=len(rpcs)) as threads: 262 list(threads.map(check_50_blocks, rpcs)) 263 264 def run_test(self): 265 node = self.nodes[0] 266 267 block_0_height = node.getblockcount() 268 self.generate(node, sync_fun=self.no_op, nblocks=1) 269 block_1 = node.getblock(node.getbestblockhash()) 270 block_2 = create_block( 271 int(block_1["hash"], 16), 272 create_coinbase(block_0_height + 2), 273 block_1["mediantime"] + 1, 274 ) 275 276 self.valid_block_test(node, block_2) 277 self.cb_missing_test(node, block_2) 278 self.block_without_transactions_test(node, block_2) 279 self.truncated_final_transaction_test(node, block_2) 280 self.duplicate_transaction_test(node, block_2) 281 self.thin_air_spending_test(node, block_2) 282 self.non_final_transaction_test(node, block_2) 283 self.bad_tx_count_test(node, block_2) 284 self.nbits_test(node, block_2) 285 self.merkle_root_test(node, block_2) 286 self.bad_timestamp_test(node, block_2) 287 self.current_tip_test(node, block_2) 288 # This sets the PoW for the next test 289 self.pow_test(node, block_2) 290 self.submit_test(node, block_0_height, block_2) 291 292 self.log.info("Generate a transaction") 293 tx = MiniWallet(node).create_self_transfer() 294 295 self.transaction_test(node, block_0_height, tx) 296 self.overspending_transaction_test(node, block_0_height, tx) 297 block_3 = self.spend_twice_test(node, block_0_height, tx) 298 self.parallel_test(node, block_3) 299 300 if __name__ == "__main__": 301 MiningTemplateVerificationTest(__file__).main()