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