mempool_accept_wtxid.py
1 #!/usr/bin/env python3 2 # Copyright (c) 2021 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 """ 6 Test mempool acceptance in case of an already known transaction 7 with identical non-witness data but different witness. 8 """ 9 10 from copy import deepcopy 11 from test_framework.messages import ( 12 COIN, 13 COutPoint, 14 CTransaction, 15 CTxIn, 16 CTxInWitness, 17 CTxOut, 18 sha256, 19 ) 20 from test_framework.p2p import P2PTxInvStore 21 from test_framework.script import ( 22 CScript, 23 OP_0, 24 OP_ELSE, 25 OP_ENDIF, 26 OP_EQUAL, 27 OP_HASH160, 28 OP_IF, 29 OP_TRUE, 30 hash160, 31 ) 32 from test_framework.test_framework import BitcoinTestFramework 33 from test_framework.util import ( 34 assert_not_equal, 35 assert_equal, 36 ) 37 38 class MempoolWtxidTest(BitcoinTestFramework): 39 def set_test_params(self): 40 self.num_nodes = 1 41 self.setup_clean_chain = True 42 43 def run_test(self): 44 node = self.nodes[0] 45 46 self.log.info('Start with empty mempool and 101 blocks') 47 # The last 100 coinbase transactions are premature 48 blockhash = self.generate(node, 101)[0] 49 txid = node.getblock(blockhash=blockhash, verbosity=2)["tx"][0]["txid"] 50 assert_equal(node.getmempoolinfo()['size'], 0) 51 52 self.log.info("Submit parent with multiple script branches to mempool") 53 hashlock = hash160(b'Preimage') 54 witness_script = CScript([OP_IF, OP_HASH160, hashlock, OP_EQUAL, OP_ELSE, OP_TRUE, OP_ENDIF]) 55 witness_program = sha256(witness_script) 56 script_pubkey = CScript([OP_0, witness_program]) 57 58 parent = CTransaction() 59 parent.vin.append(CTxIn(COutPoint(int(txid, 16), 0), b"")) 60 parent.vout.append(CTxOut(int(9.99998 * COIN), script_pubkey)) 61 parent.rehash() 62 63 privkeys = [node.get_deterministic_priv_key().key] 64 raw_parent = node.signrawtransactionwithkey(hexstring=parent.serialize().hex(), privkeys=privkeys)['hex'] 65 parent_txid = node.sendrawtransaction(hexstring=raw_parent, maxfeerate=0) 66 self.generate(node, 1) 67 68 peer_wtxid_relay = node.add_p2p_connection(P2PTxInvStore()) 69 70 # Create a new transaction with witness solving first branch 71 child_witness_script = CScript([OP_TRUE]) 72 child_witness_program = sha256(child_witness_script) 73 child_script_pubkey = CScript([OP_0, child_witness_program]) 74 75 child_one = CTransaction() 76 child_one.vin.append(CTxIn(COutPoint(int(parent_txid, 16), 0), b"")) 77 child_one.vout.append(CTxOut(int(9.99996 * COIN), child_script_pubkey)) 78 child_one.wit.vtxinwit.append(CTxInWitness()) 79 child_one.wit.vtxinwit[0].scriptWitness.stack = [b'Preimage', b'\x01', witness_script] 80 child_one_wtxid = child_one.getwtxid() 81 child_one_txid = child_one.rehash() 82 83 # Create another identical transaction with witness solving second branch 84 child_two = deepcopy(child_one) 85 child_two.wit.vtxinwit[0].scriptWitness.stack = [b'', witness_script] 86 child_two_wtxid = child_two.getwtxid() 87 child_two_txid = child_two.rehash() 88 89 assert_equal(child_one_txid, child_two_txid) 90 assert_not_equal(child_one_wtxid, child_two_wtxid) 91 92 self.log.info("Submit child_one to the mempool") 93 txid_submitted = node.sendrawtransaction(child_one.serialize().hex()) 94 assert_equal(node.getmempoolentry(txid_submitted)['wtxid'], child_one_wtxid) 95 96 peer_wtxid_relay.wait_for_broadcast([child_one_wtxid]) 97 assert_equal(node.getmempoolinfo()["unbroadcastcount"], 0) 98 99 # testmempoolaccept reports the "already in mempool" error 100 assert_equal(node.testmempoolaccept([child_one.serialize().hex()]), [{ 101 "txid": child_one_txid, 102 "wtxid": child_one_wtxid, 103 "allowed": False, 104 "reject-reason": "txn-already-in-mempool", 105 "reject-details": "txn-already-in-mempool" 106 }]) 107 assert_equal(node.testmempoolaccept([child_two.serialize().hex()])[0], { 108 "txid": child_two_txid, 109 "wtxid": child_two_wtxid, 110 "allowed": False, 111 "reject-reason": "txn-same-nonwitness-data-in-mempool", 112 "reject-details": "txn-same-nonwitness-data-in-mempool" 113 }) 114 115 # sendrawtransaction will not throw but quits early when the exact same transaction is already in mempool 116 node.sendrawtransaction(child_one.serialize().hex()) 117 118 self.log.info("Connect another peer that hasn't seen child_one before") 119 peer_wtxid_relay_2 = node.add_p2p_connection(P2PTxInvStore()) 120 121 self.log.info("Submit child_two to the mempool") 122 # sendrawtransaction will not throw but quits early when a transaction with the same non-witness data is already in mempool 123 node.sendrawtransaction(child_two.serialize().hex()) 124 125 # The node should rebroadcast the transaction using the wtxid of the correct transaction 126 # (child_one, which is in its mempool). 127 peer_wtxid_relay_2.wait_for_broadcast([child_one_wtxid]) 128 assert_equal(node.getmempoolinfo()["unbroadcastcount"], 0) 129 130 if __name__ == '__main__': 131 MempoolWtxidTest(__file__).main()