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