mempool_accept.py
1 #!/usr/bin/env python3 2 # Copyright (c) 2017-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 mempool acceptance of raw transactions.""" 6 7 from copy import deepcopy 8 from decimal import Decimal 9 import math 10 11 from test_framework.test_framework import BitcoinTestFramework 12 from test_framework.messages import ( 13 MAX_BIP125_RBF_SEQUENCE, 14 COIN, 15 COutPoint, 16 CTransaction, 17 CTxIn, 18 CTxInWitness, 19 CTxOut, 20 MAX_BLOCK_WEIGHT, 21 MAX_MONEY, 22 SEQUENCE_FINAL, 23 tx_from_hex, 24 ) 25 from test_framework.script import ( 26 CScript, 27 OP_0, 28 OP_HASH160, 29 OP_RETURN, 30 OP_TRUE, 31 ) 32 from test_framework.script_util import ( 33 DUMMY_MIN_OP_RETURN_SCRIPT, 34 keys_to_multisig_script, 35 MIN_PADDING, 36 MIN_STANDARD_TX_NONWITNESS_SIZE, 37 script_to_p2sh_script, 38 script_to_p2wsh_script, 39 ) 40 from test_framework.util import ( 41 assert_equal, 42 assert_greater_than, 43 assert_raises_rpc_error, 44 ) 45 from test_framework.wallet import MiniWallet 46 from test_framework.wallet_util import generate_keypair 47 48 49 class MempoolAcceptanceTest(BitcoinTestFramework): 50 def set_test_params(self): 51 self.num_nodes = 1 52 self.extra_args = [[ 53 '-txindex','-permitbaremultisig=0', 54 ]] * self.num_nodes 55 self.supports_cli = False 56 57 def check_mempool_result(self, result_expected, *args, **kwargs): 58 """Wrapper to check result of testmempoolaccept on node_0's mempool""" 59 result_test = self.nodes[0].testmempoolaccept(*args, **kwargs) 60 for r in result_test: 61 # Skip these checks for now 62 r.pop('wtxid') 63 if "fees" in r: 64 r["fees"].pop("effective-feerate") 65 r["fees"].pop("effective-includes") 66 assert_equal(result_expected, result_test) 67 assert_equal(self.nodes[0].getmempoolinfo()['size'], self.mempool_size) # Must not change mempool state 68 69 def run_test(self): 70 node = self.nodes[0] 71 self.wallet = MiniWallet(node) 72 73 self.log.info('Start with empty mempool, and 200 blocks') 74 self.mempool_size = 0 75 assert_equal(node.getblockcount(), 200) 76 assert_equal(node.getmempoolinfo()['size'], self.mempool_size) 77 78 self.log.info('Should not accept garbage to testmempoolaccept') 79 assert_raises_rpc_error(-3, 'JSON value of type string is not of expected type array', lambda: node.testmempoolaccept(rawtxs='ff00baar')) 80 assert_raises_rpc_error(-8, 'Array must contain between 1 and 25 transactions.', lambda: node.testmempoolaccept(rawtxs=['ff22']*26)) 81 assert_raises_rpc_error(-8, 'Array must contain between 1 and 25 transactions.', lambda: node.testmempoolaccept(rawtxs=[])) 82 assert_raises_rpc_error(-22, 'TX decode failed', lambda: node.testmempoolaccept(rawtxs=['ff00baar'])) 83 84 self.log.info('A transaction already in the blockchain') 85 tx = self.wallet.create_self_transfer()['tx'] # Pick a random coin(base) to spend 86 tx.vout.append(deepcopy(tx.vout[0])) 87 tx.vout[0].nValue = int(0.3 * COIN) 88 tx.vout[1].nValue = int(49 * COIN) 89 raw_tx_in_block = tx.serialize().hex() 90 txid_in_block = self.wallet.sendrawtransaction(from_node=node, tx_hex=raw_tx_in_block) 91 self.generate(node, 1) 92 self.mempool_size = 0 93 # Also check feerate. 1BTC/kvB fails 94 assert_raises_rpc_error(-8, "Fee rates larger than or equal to 1BTC/kvB are not accepted", lambda: self.check_mempool_result( 95 result_expected=None, 96 rawtxs=[raw_tx_in_block], 97 maxfeerate=1, 98 )) 99 # Check negative feerate 100 assert_raises_rpc_error(-3, "Amount out of range", lambda: self.check_mempool_result( 101 result_expected=None, 102 rawtxs=[raw_tx_in_block], 103 maxfeerate=-0.01, 104 )) 105 # ... 0.99 passes 106 self.check_mempool_result( 107 result_expected=[{'txid': txid_in_block, 'allowed': False, 'reject-reason': 'txn-already-known'}], 108 rawtxs=[raw_tx_in_block], 109 maxfeerate=0.99, 110 ) 111 112 self.log.info('A transaction not in the mempool') 113 fee = Decimal('0.000007') 114 utxo_to_spend = self.wallet.get_utxo(txid=txid_in_block) # use 0.3 BTC UTXO 115 tx = self.wallet.create_self_transfer(utxo_to_spend=utxo_to_spend, sequence=MAX_BIP125_RBF_SEQUENCE)['tx'] 116 tx.vout[0].nValue = int((Decimal('0.3') - fee) * COIN) 117 raw_tx_0 = tx.serialize().hex() 118 txid_0 = tx.rehash() 119 self.check_mempool_result( 120 result_expected=[{'txid': txid_0, 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': fee}}], 121 rawtxs=[raw_tx_0], 122 ) 123 124 self.log.info('A final transaction not in the mempool') 125 output_amount = Decimal('0.025') 126 tx = self.wallet.create_self_transfer( 127 sequence=SEQUENCE_FINAL, 128 locktime=node.getblockcount() + 2000, # Can be anything 129 )['tx'] 130 tx.vout[0].nValue = int(output_amount * COIN) 131 raw_tx_final = tx.serialize().hex() 132 tx = tx_from_hex(raw_tx_final) 133 fee_expected = Decimal('50.0') - output_amount 134 self.check_mempool_result( 135 result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': fee_expected}}], 136 rawtxs=[tx.serialize().hex()], 137 maxfeerate=0, 138 ) 139 node.sendrawtransaction(hexstring=raw_tx_final, maxfeerate=0) 140 self.mempool_size += 1 141 142 self.log.info('A transaction in the mempool') 143 node.sendrawtransaction(hexstring=raw_tx_0) 144 self.mempool_size += 1 145 self.check_mempool_result( 146 result_expected=[{'txid': txid_0, 'allowed': False, 'reject-reason': 'txn-already-in-mempool'}], 147 rawtxs=[raw_tx_0], 148 ) 149 150 self.log.info('A transaction that replaces a mempool transaction') 151 tx = tx_from_hex(raw_tx_0) 152 tx.vout[0].nValue -= int(fee * COIN) # Double the fee 153 tx.vin[0].nSequence = MAX_BIP125_RBF_SEQUENCE + 1 # Now, opt out of RBF 154 raw_tx_0 = tx.serialize().hex() 155 txid_0 = tx.rehash() 156 self.check_mempool_result( 157 result_expected=[{'txid': txid_0, 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': (2 * fee)}}], 158 rawtxs=[raw_tx_0], 159 ) 160 161 self.log.info('A transaction that conflicts with an unconfirmed tx') 162 # Send the transaction that replaces the mempool transaction and opts out of replaceability 163 node.sendrawtransaction(hexstring=tx.serialize().hex(), maxfeerate=0) 164 # take original raw_tx_0 165 tx = tx_from_hex(raw_tx_0) 166 tx.vout[0].nValue -= int(4 * fee * COIN) # Set more fee 167 self.check_mempool_result( 168 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'txn-mempool-conflict'}], 169 rawtxs=[tx.serialize().hex()], 170 maxfeerate=0, 171 ) 172 173 self.log.info('A transaction with missing inputs, that never existed') 174 tx = tx_from_hex(raw_tx_0) 175 tx.vin[0].prevout = COutPoint(hash=int('ff' * 32, 16), n=14) 176 self.check_mempool_result( 177 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'missing-inputs'}], 178 rawtxs=[tx.serialize().hex()], 179 ) 180 181 self.log.info('A transaction with missing inputs, that existed once in the past') 182 tx = tx_from_hex(raw_tx_0) 183 tx.vin[0].prevout.n = 1 # Set vout to 1, to spend the other outpoint (49 coins) of the in-chain-tx we want to double spend 184 raw_tx_1 = tx.serialize().hex() 185 txid_1 = node.sendrawtransaction(hexstring=raw_tx_1, maxfeerate=0) 186 # Now spend both to "clearly hide" the outputs, ie. remove the coins from the utxo set by spending them 187 tx = self.wallet.create_self_transfer()['tx'] 188 tx.vin.append(deepcopy(tx.vin[0])) 189 tx.wit.vtxinwit.append(deepcopy(tx.wit.vtxinwit[0])) 190 tx.vin[0].prevout = COutPoint(hash=int(txid_0, 16), n=0) 191 tx.vin[1].prevout = COutPoint(hash=int(txid_1, 16), n=0) 192 tx.vout[0].nValue = int(0.1 * COIN) 193 raw_tx_spend_both = tx.serialize().hex() 194 txid_spend_both = self.wallet.sendrawtransaction(from_node=node, tx_hex=raw_tx_spend_both) 195 self.generate(node, 1) 196 self.mempool_size = 0 197 # Now see if we can add the coins back to the utxo set by sending the exact txs again 198 self.check_mempool_result( 199 result_expected=[{'txid': txid_0, 'allowed': False, 'reject-reason': 'missing-inputs'}], 200 rawtxs=[raw_tx_0], 201 ) 202 self.check_mempool_result( 203 result_expected=[{'txid': txid_1, 'allowed': False, 'reject-reason': 'missing-inputs'}], 204 rawtxs=[raw_tx_1], 205 ) 206 207 self.log.info('Create a "reference" tx for later use') 208 utxo_to_spend = self.wallet.get_utxo(txid=txid_spend_both) 209 tx = self.wallet.create_self_transfer(utxo_to_spend=utxo_to_spend, sequence=SEQUENCE_FINAL)['tx'] 210 tx.vout[0].nValue = int(0.05 * COIN) 211 raw_tx_reference = tx.serialize().hex() 212 # Reference tx should be valid on itself 213 self.check_mempool_result( 214 result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': tx.get_vsize(), 'fees': { 'base': Decimal('0.1') - Decimal('0.05')}}], 215 rawtxs=[tx.serialize().hex()], 216 maxfeerate=0, 217 ) 218 219 self.log.info('A transaction with no outputs') 220 tx = tx_from_hex(raw_tx_reference) 221 tx.vout = [] 222 self.check_mempool_result( 223 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-vout-empty'}], 224 rawtxs=[tx.serialize().hex()], 225 ) 226 227 self.log.info('A really large transaction') 228 tx = tx_from_hex(raw_tx_reference) 229 tx.vin = [tx.vin[0]] * math.ceil(MAX_BLOCK_WEIGHT // 4 / len(tx.vin[0].serialize())) 230 self.check_mempool_result( 231 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-oversize'}], 232 rawtxs=[tx.serialize().hex()], 233 ) 234 235 self.log.info('A transaction with negative output value') 236 tx = tx_from_hex(raw_tx_reference) 237 tx.vout[0].nValue *= -1 238 self.check_mempool_result( 239 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-vout-negative'}], 240 rawtxs=[tx.serialize().hex()], 241 ) 242 243 # The following two validations prevent overflow of the output amounts (see CVE-2010-5139). 244 self.log.info('A transaction with too large output value') 245 tx = tx_from_hex(raw_tx_reference) 246 tx.vout[0].nValue = MAX_MONEY + 1 247 self.check_mempool_result( 248 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-vout-toolarge'}], 249 rawtxs=[tx.serialize().hex()], 250 ) 251 252 self.log.info('A transaction with too large sum of output values') 253 tx = tx_from_hex(raw_tx_reference) 254 tx.vout = [tx.vout[0]] * 2 255 tx.vout[0].nValue = MAX_MONEY 256 self.check_mempool_result( 257 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-txouttotal-toolarge'}], 258 rawtxs=[tx.serialize().hex()], 259 ) 260 261 self.log.info('A transaction with duplicate inputs') 262 tx = tx_from_hex(raw_tx_reference) 263 tx.vin = [tx.vin[0]] * 2 264 self.check_mempool_result( 265 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-inputs-duplicate'}], 266 rawtxs=[tx.serialize().hex()], 267 ) 268 269 self.log.info('A non-coinbase transaction with coinbase-like outpoint') 270 tx = tx_from_hex(raw_tx_reference) 271 tx.vin.append(CTxIn(COutPoint(hash=0, n=0xffffffff))) 272 self.check_mempool_result( 273 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-prevout-null'}], 274 rawtxs=[tx.serialize().hex()], 275 ) 276 277 self.log.info('A coinbase transaction') 278 # Pick the input of the first tx we created, so it has to be a coinbase tx 279 raw_tx_coinbase_spent = node.getrawtransaction(txid=node.decoderawtransaction(hexstring=raw_tx_in_block)['vin'][0]['txid']) 280 tx = tx_from_hex(raw_tx_coinbase_spent) 281 self.check_mempool_result( 282 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'coinbase'}], 283 rawtxs=[tx.serialize().hex()], 284 ) 285 286 self.log.info('Some nonstandard transactions') 287 tx = tx_from_hex(raw_tx_reference) 288 tx.nVersion = 3 # A version currently non-standard 289 self.check_mempool_result( 290 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'version'}], 291 rawtxs=[tx.serialize().hex()], 292 ) 293 tx = tx_from_hex(raw_tx_reference) 294 tx.vout[0].scriptPubKey = CScript([OP_0]) # Some non-standard script 295 self.check_mempool_result( 296 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'scriptpubkey'}], 297 rawtxs=[tx.serialize().hex()], 298 ) 299 tx = tx_from_hex(raw_tx_reference) 300 _, pubkey = generate_keypair() 301 tx.vout[0].scriptPubKey = keys_to_multisig_script([pubkey] * 3, k=2) # Some bare multisig script (2-of-3) 302 self.check_mempool_result( 303 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bare-multisig'}], 304 rawtxs=[tx.serialize().hex()], 305 ) 306 tx = tx_from_hex(raw_tx_reference) 307 tx.vin[0].scriptSig = CScript([OP_HASH160]) # Some not-pushonly scriptSig 308 self.check_mempool_result( 309 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'scriptsig-not-pushonly'}], 310 rawtxs=[tx.serialize().hex()], 311 ) 312 tx = tx_from_hex(raw_tx_reference) 313 tx.vin[0].scriptSig = CScript([b'a' * 1648]) # Some too large scriptSig (>1650 bytes) 314 self.check_mempool_result( 315 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'scriptsig-size'}], 316 rawtxs=[tx.serialize().hex()], 317 ) 318 tx = tx_from_hex(raw_tx_reference) 319 output_p2sh_burn = CTxOut(nValue=540, scriptPubKey=script_to_p2sh_script(b'burn')) 320 num_scripts = 100000 // len(output_p2sh_burn.serialize()) # Use enough outputs to make the tx too large for our policy 321 tx.vout = [output_p2sh_burn] * num_scripts 322 self.check_mempool_result( 323 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'tx-size'}], 324 rawtxs=[tx.serialize().hex()], 325 ) 326 tx = tx_from_hex(raw_tx_reference) 327 tx.vout[0] = output_p2sh_burn 328 tx.vout[0].nValue -= 1 # Make output smaller, such that it is dust for our policy 329 self.check_mempool_result( 330 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'dust'}], 331 rawtxs=[tx.serialize().hex()], 332 ) 333 tx = tx_from_hex(raw_tx_reference) 334 tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'\xff']) 335 tx.vout = [tx.vout[0]] * 2 336 self.check_mempool_result( 337 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'multi-op-return'}], 338 rawtxs=[tx.serialize().hex()], 339 ) 340 341 self.log.info('A timelocked transaction') 342 tx = tx_from_hex(raw_tx_reference) 343 tx.vin[0].nSequence -= 1 # Should be non-max, so locktime is not ignored 344 tx.nLockTime = node.getblockcount() + 1 345 self.check_mempool_result( 346 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'non-final'}], 347 rawtxs=[tx.serialize().hex()], 348 ) 349 350 self.log.info('A transaction that is locked by BIP68 sequence logic') 351 tx = tx_from_hex(raw_tx_reference) 352 tx.vin[0].nSequence = 2 # We could include it in the second block mined from now, but not the very next one 353 self.check_mempool_result( 354 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'non-BIP68-final'}], 355 rawtxs=[tx.serialize().hex()], 356 maxfeerate=0, 357 ) 358 359 # Prep for tiny-tx tests with wsh(OP_TRUE) output 360 seed_tx = self.wallet.send_to(from_node=node, scriptPubKey=script_to_p2wsh_script(CScript([OP_TRUE])), amount=COIN) 361 self.generate(node, 1) 362 363 self.log.info('A tiny transaction(in non-witness bytes) that is disallowed') 364 tx = CTransaction() 365 tx.vin.append(CTxIn(COutPoint(int(seed_tx["txid"], 16), seed_tx["sent_vout"]), b"", SEQUENCE_FINAL)) 366 tx.wit.vtxinwit = [CTxInWitness()] 367 tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])] 368 tx.vout.append(CTxOut(0, CScript([OP_RETURN] + ([OP_0] * (MIN_PADDING - 2))))) 369 # Note it's only non-witness size that matters! 370 assert_equal(len(tx.serialize_without_witness()), 64) 371 assert_equal(MIN_STANDARD_TX_NONWITNESS_SIZE - 1, 64) 372 assert_greater_than(len(tx.serialize()), 64) 373 374 self.check_mempool_result( 375 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'tx-size-small'}], 376 rawtxs=[tx.serialize().hex()], 377 maxfeerate=0, 378 ) 379 380 self.log.info('Minimally-small transaction(in non-witness bytes) that is allowed') 381 tx.vout[0] = CTxOut(COIN - 1000, DUMMY_MIN_OP_RETURN_SCRIPT) 382 assert_equal(len(tx.serialize_without_witness()), MIN_STANDARD_TX_NONWITNESS_SIZE) 383 self.check_mempool_result( 384 result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': tx.get_vsize(), 'fees': { 'base': Decimal('0.00001000')}}], 385 rawtxs=[tx.serialize().hex()], 386 maxfeerate=0, 387 ) 388 389 if __name__ == '__main__': 390 MempoolAcceptanceTest().main()