1#!/usr/bin/env python3 2# Copyright (c) 2017-2018 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 7from io import BytesIO 8import math 9 10from test_framework.test_framework import BitcoinTestFramework 11from test_framework.messages import ( 12 BIP125_SEQUENCE_NUMBER, 13 COIN, 14 COutPoint, 15 CTransaction, 16 CTxOut, 17 MAX_BLOCK_BASE_SIZE, 18) 19from test_framework.script import ( 20 hash160, 21 CScript, 22 OP_0, 23 OP_EQUAL, 24 OP_HASH160, 25 OP_RETURN, 26) 27from test_framework.util import ( 28 assert_equal, 29 assert_raises_rpc_error, 30 bytes_to_hex_str, 31 hex_str_to_bytes, 32) 33 34 35class MempoolAcceptanceTest(BitcoinTestFramework): 36 def set_test_params(self): 37 self.num_nodes = 1 38 self.extra_args = [[ 39 '-txindex', 40 '-mempoolreplacement=1', 41 '-acceptnonstdtxn=0', # Try to mimic main-net 42 ]] * self.num_nodes 43 44 def skip_test_if_missing_module(self): 45 self.skip_if_no_wallet() 46 47 def check_mempool_result(self, result_expected, *args, **kwargs): 48 """Wrapper to check result of testmempoolaccept on node_0's mempool""" 49 result_test = self.nodes[0].testmempoolaccept(*args, **kwargs) 50 assert_equal(result_expected, result_test) 51 assert_equal(self.nodes[0].getmempoolinfo()['size'], self.mempool_size) # Must not change mempool state 52 53 def run_test(self): 54 node = self.nodes[0] 55 56 self.log.info('Start with empty mempool, and 200 blocks') 57 self.mempool_size = 0 58 assert_equal(node.getblockcount(), 200) 59 assert_equal(node.getmempoolinfo()['size'], self.mempool_size) 60 coins = node.listunspent() 61 62 self.log.info('Should not accept garbage to testmempoolaccept') 63 assert_raises_rpc_error(-3, 'Expected type array, got string', lambda: node.testmempoolaccept(rawtxs='ff00baar')) 64 assert_raises_rpc_error(-8, 'Array must contain exactly one raw transaction for now', lambda: node.testmempoolaccept(rawtxs=['ff00baar', 'ff22'])) 65 assert_raises_rpc_error(-22, 'TX decode failed', lambda: node.testmempoolaccept(rawtxs=['ff00baar'])) 66 67 self.log.info('A transaction already in the blockchain') 68 coin = coins.pop() # Pick a random coin(base) to spend 69 raw_tx_in_block = node.signrawtransactionwithwallet(node.createrawtransaction( 70 inputs=[{'txid': coin['txid'], 'vout': coin['vout']}], 71 outputs=[{node.getnewaddress(): 0.3}, {node.getnewaddress(): 49}], 72 ))['hex'] 73 txid_in_block = node.sendrawtransaction(hexstring=raw_tx_in_block, allowhighfees=True) 74 node.generate(1) 75 self.mempool_size = 0 76 self.check_mempool_result( 77 result_expected=[{'txid': txid_in_block, 'allowed': False, 'reject-reason': '18: txn-already-known'}], 78 rawtxs=[raw_tx_in_block], 79 ) 80 81 self.log.info('A transaction not in the mempool') 82 fee = 0.00000700 83 raw_tx_0 = node.signrawtransactionwithwallet(node.createrawtransaction( 84 inputs=[{"txid": txid_in_block, "vout": 0, "sequence": BIP125_SEQUENCE_NUMBER}], # RBF is used later 85 outputs=[{node.getnewaddress(): 0.3 - fee}], 86 ))['hex'] 87 tx = CTransaction() 88 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) 89 txid_0 = tx.rehash() 90 self.check_mempool_result( 91 result_expected=[{'txid': txid_0, 'allowed': True}], 92 rawtxs=[raw_tx_0], 93 ) 94 95 self.log.info('A final transaction not in the mempool') 96 coin = coins.pop() # Pick a random coin(base) to spend 97 raw_tx_final = node.signrawtransactionwithwallet(node.createrawtransaction( 98 inputs=[{'txid': coin['txid'], 'vout': coin['vout'], "sequence": 0xffffffff}], # SEQUENCE_FINAL 99 outputs=[{node.getnewaddress(): 0.025}], 100 locktime=node.getblockcount() + 2000, # Can be anything 101 ))['hex'] 102 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_final))) 103 self.check_mempool_result( 104 result_expected=[{'txid': tx.rehash(), 'allowed': True}], 105 rawtxs=[bytes_to_hex_str(tx.serialize())], 106 allowhighfees=True, 107 ) 108 node.sendrawtransaction(hexstring=raw_tx_final, allowhighfees=True) 109 self.mempool_size += 1 110 111 self.log.info('A transaction in the mempool') 112 node.sendrawtransaction(hexstring=raw_tx_0) 113 self.mempool_size += 1 114 self.check_mempool_result( 115 result_expected=[{'txid': txid_0, 'allowed': False, 'reject-reason': '18: txn-already-in-mempool'}], 116 rawtxs=[raw_tx_0], 117 ) 118 119 self.log.info('A transaction that replaces a mempool transaction') 120 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) 121 tx.vout[0].nValue -= int(fee * COIN) # Double the fee 122 tx.vin[0].nSequence = BIP125_SEQUENCE_NUMBER + 1 # Now, opt out of RBF 123 raw_tx_0 = node.signrawtransactionwithwallet(bytes_to_hex_str(tx.serialize()))['hex'] 124 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) 125 txid_0 = tx.rehash() 126 self.check_mempool_result( 127 result_expected=[{'txid': txid_0, 'allowed': True}], 128 rawtxs=[raw_tx_0], 129 ) 130 131 self.log.info('A transaction that conflicts with an unconfirmed tx') 132 # Send the transaction that replaces the mempool transaction and opts out of replaceability 133 node.sendrawtransaction(hexstring=bytes_to_hex_str(tx.serialize()), allowhighfees=True) 134 # take original raw_tx_0 135 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) 136 tx.vout[0].nValue -= int(4 * fee * COIN) # Set more fee 137 # skip re-signing the tx 138 self.check_mempool_result( 139 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '18: txn-mempool-conflict'}], 140 rawtxs=[bytes_to_hex_str(tx.serialize())], 141 allowhighfees=True, 142 ) 143 144 self.log.info('A transaction with missing inputs, that never existed') 145 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) 146 tx.vin[0].prevout = COutPoint(hash=int('ff' * 32, 16), n=14) 147 # skip re-signing the tx 148 self.check_mempool_result( 149 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'missing-inputs'}], 150 rawtxs=[bytes_to_hex_str(tx.serialize())], 151 ) 152 153 self.log.info('A transaction with missing inputs, that existed once in the past') 154 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) 155 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 156 raw_tx_1 = node.signrawtransactionwithwallet(bytes_to_hex_str(tx.serialize()))['hex'] 157 txid_1 = node.sendrawtransaction(hexstring=raw_tx_1, allowhighfees=True) 158 # Now spend both to "clearly hide" the outputs, ie. remove the coins from the utxo set by spending them 159 raw_tx_spend_both = node.signrawtransactionwithwallet(node.createrawtransaction( 160 inputs=[ 161 {'txid': txid_0, 'vout': 0}, 162 {'txid': txid_1, 'vout': 0}, 163 ], 164 outputs=[{node.getnewaddress(): 0.1}] 165 ))['hex'] 166 txid_spend_both = node.sendrawtransaction(hexstring=raw_tx_spend_both, allowhighfees=True) 167 node.generate(1) 168 self.mempool_size = 0 169 # Now see if we can add the coins back to the utxo set by sending the exact txs again 170 self.check_mempool_result( 171 result_expected=[{'txid': txid_0, 'allowed': False, 'reject-reason': 'missing-inputs'}], 172 rawtxs=[raw_tx_0], 173 ) 174 self.check_mempool_result( 175 result_expected=[{'txid': txid_1, 'allowed': False, 'reject-reason': 'missing-inputs'}], 176 rawtxs=[raw_tx_1], 177 ) 178 179 self.log.info('Create a signed "reference" tx for later use') 180 raw_tx_reference = node.signrawtransactionwithwallet(node.createrawtransaction( 181 inputs=[{'txid': txid_spend_both, 'vout': 0}], 182 outputs=[{node.getnewaddress(): 0.05}], 183 ))['hex'] 184 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) 185 # Reference tx should be valid on itself 186 self.check_mempool_result( 187 result_expected=[{'txid': tx.rehash(), 'allowed': True}], 188 rawtxs=[bytes_to_hex_str(tx.serialize())], 189 ) 190 191 self.log.info('A transaction with no outputs') 192 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) 193 tx.vout = [] 194 # Skip re-signing the transaction for context independent checks from now on 195 # tx.deserialize(BytesIO(hex_str_to_bytes(node.signrawtransactionwithwallet(bytes_to_hex_str(tx.serialize()))['hex']))) 196 self.check_mempool_result( 197 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-vout-empty'}], 198 rawtxs=[bytes_to_hex_str(tx.serialize())], 199 ) 200 201 self.log.info('A really large transaction') 202 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) 203 tx.vin = [tx.vin[0]] * math.ceil(MAX_BLOCK_BASE_SIZE / len(tx.vin[0].serialize())) 204 self.check_mempool_result( 205 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-oversize'}], 206 rawtxs=[bytes_to_hex_str(tx.serialize())], 207 ) 208 209 self.log.info('A transaction with negative output value') 210 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) 211 tx.vout[0].nValue *= -1 212 self.check_mempool_result( 213 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-vout-negative'}], 214 rawtxs=[bytes_to_hex_str(tx.serialize())], 215 ) 216 217 self.log.info('A transaction with too large output value') 218 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) 219 tx.vout[0].nValue = 84000000 * COIN + 1 220 self.check_mempool_result( 221 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-vout-toolarge'}], 222 rawtxs=[bytes_to_hex_str(tx.serialize())], 223 ) 224 225 self.log.info('A transaction with too large sum of output values') 226 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) 227 tx.vout = [tx.vout[0]] * 2 228 tx.vout[0].nValue = 84000000 * COIN 229 self.check_mempool_result( 230 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-txouttotal-toolarge'}], 231 rawtxs=[bytes_to_hex_str(tx.serialize())], 232 ) 233 234 self.log.info('A transaction with duplicate inputs') 235 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) 236 tx.vin = [tx.vin[0]] * 2 237 self.check_mempool_result( 238 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-inputs-duplicate'}], 239 rawtxs=[bytes_to_hex_str(tx.serialize())], 240 ) 241 242 self.log.info('A coinbase transaction') 243 # Pick the input of the first tx we signed, so it has to be a coinbase tx 244 raw_tx_coinbase_spent = node.getrawtransaction(txid=node.decoderawtransaction(hexstring=raw_tx_in_block)['vin'][0]['txid']) 245 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_coinbase_spent))) 246 self.check_mempool_result( 247 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: coinbase'}], 248 rawtxs=[bytes_to_hex_str(tx.serialize())], 249 ) 250 251 self.log.info('Some nonstandard transactions') 252 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) 253 tx.nVersion = 3 # A version currently non-standard 254 self.check_mempool_result( 255 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: version'}], 256 rawtxs=[bytes_to_hex_str(tx.serialize())], 257 ) 258 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) 259 tx.vout[0].scriptPubKey = CScript([OP_0]) # Some non-standard script 260 self.check_mempool_result( 261 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: scriptpubkey'}], 262 rawtxs=[bytes_to_hex_str(tx.serialize())], 263 ) 264 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) 265 tx.vin[0].scriptSig = CScript([OP_HASH160]) # Some not-pushonly scriptSig 266 self.check_mempool_result( 267 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: scriptsig-not-pushonly'}], 268 rawtxs=[bytes_to_hex_str(tx.serialize())], 269 ) 270 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) 271 output_p2sh_burn = CTxOut(nValue=540, scriptPubKey=CScript([OP_HASH160, hash160(b'burn'), OP_EQUAL])) 272 num_scripts = 100000 // len(output_p2sh_burn.serialize()) # Use enough outputs to make the tx too large for our policy 273 tx.vout = [output_p2sh_burn] * num_scripts 274 self.check_mempool_result( 275 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: tx-size'}], 276 rawtxs=[bytes_to_hex_str(tx.serialize())], 277 ) 278 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) 279 tx.vout[0] = output_p2sh_burn 280 tx.vout[0].nValue -= 1 # Make output smaller, such that it is dust for our policy 281 self.check_mempool_result( 282 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: dust'}], 283 rawtxs=[bytes_to_hex_str(tx.serialize())], 284 ) 285 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) 286 tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'\xff']) 287 tx.vout = [tx.vout[0]] * 2 288 self.check_mempool_result( 289 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: multi-op-return'}], 290 rawtxs=[bytes_to_hex_str(tx.serialize())], 291 ) 292 293 self.log.info('A timelocked transaction') 294 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) 295 tx.vin[0].nSequence -= 1 # Should be non-max, so locktime is not ignored 296 tx.nLockTime = node.getblockcount() + 1 297 self.check_mempool_result( 298 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: non-final'}], 299 rawtxs=[bytes_to_hex_str(tx.serialize())], 300 ) 301 302 self.log.info('A transaction that is locked by BIP68 sequence logic') 303 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) 304 tx.vin[0].nSequence = 2 # We could include it in the second block mined from now, but not the very next one 305 # Can skip re-signing the tx because of early rejection 306 self.check_mempool_result( 307 result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: non-BIP68-final'}], 308 rawtxs=[bytes_to_hex_str(tx.serialize())], 309 allowhighfees=True, 310 ) 311 312 313if __name__ == '__main__': 314 MempoolAcceptanceTest().main() 315