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