Skip to content

Commit d86e7a1

Browse files
author
Antoine Riard
committed
[functional] Add mempool_wtxid.py
Broadcast consequently two identical child transactions with size-divergent witnesses and thus different feerate. The second broadcast should successfully replace the first one. Add assert_not_equal helper in test_framework/util.py
1 parent d02c78a commit d86e7a1

File tree

3 files changed

+116
-0
lines changed

3 files changed

+116
-0
lines changed

test/functional/mempool_wtxid.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2020 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 wtxid acceptance in case of an already known transaction
6+
with identical non-witness data but lower feerate witness."""
7+
8+
from codecs import encode
9+
from decimal import Decimal
10+
11+
from test_framework.test_framework import BitcoinTestFramework
12+
13+
from test_framework.messages import (
14+
CTransaction,
15+
CTxIn,
16+
CTxInWitness,
17+
CTxOut,
18+
COutPoint,
19+
sha256,
20+
COIN,
21+
hash256,
22+
)
23+
24+
from test_framework.util import (
25+
assert_equal,
26+
assert_not_equal,
27+
)
28+
29+
from test_framework.script import (
30+
CScript,
31+
OP_0,
32+
OP_TRUE,
33+
OP_IF,
34+
OP_HASH160,
35+
OP_EQUAL,
36+
OP_ELSE,
37+
OP_ENDIF,
38+
hash160,
39+
)
40+
41+
class MempoolWtxidTest(BitcoinTestFramework):
42+
def set_test_params(self):
43+
self.num_nodes = 1
44+
self.supports_cli = False
45+
self.extra_args = [["-incrementalrelayfee=0"]]
46+
47+
def skip_test_if_missing_module(self):
48+
self.skip_if_no_wallet()
49+
50+
def run_test(self):
51+
node = self.nodes[0]
52+
53+
self.log.info('Start with empty mempool, and 200 blocks')
54+
self.mempool_size = 0
55+
assert_equal(node.getblockcount(), 200)
56+
assert_equal(node.getmempoolinfo()['size'], self.mempool_size)
57+
58+
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10"))
59+
60+
# Create a Segwit output
61+
hashlock = hash160(b'Preimage')
62+
witness_script = CScript([OP_IF, OP_HASH160, hashlock, OP_EQUAL, OP_ELSE, OP_TRUE, OP_ENDIF])
63+
witness_program = sha256(witness_script)
64+
script_pubkey = CScript([OP_0, witness_program])
65+
66+
parent = CTransaction()
67+
parent.vin.append(CTxIn(COutPoint(int(txid, 16), 0), b""))
68+
parent.vout.append(CTxOut(int(9.99998 * COIN), script_pubkey))
69+
parent.rehash()
70+
71+
# Confirm parent with alternative script branches witnessScript
72+
raw_parent = self.nodes[0].signrawtransactionwithwallet(parent.serialize().hex())['hex']
73+
parent_txid = self.nodes[0].sendrawtransaction(hexstring=raw_parent, maxfeerate=0)
74+
self.nodes[0].generate(1)
75+
76+
# Create a new segwit transaction with witness solving first branch
77+
child_witness_script = CScript([OP_TRUE])
78+
child_witness_program = sha256(child_witness_script)
79+
child_script_pubkey = CScript([OP_0, child_witness_program])
80+
81+
child_one = CTransaction()
82+
child_one.vin.append(CTxIn(COutPoint(int(parent_txid, 16), 0), b""))
83+
child_one.vout.append(CTxOut(int(9.99996 * COIN), child_script_pubkey))
84+
child_one.wit.vtxinwit.append(CTxInWitness())
85+
child_one.wit.vtxinwit[0].scriptWitness.stack = [b'Preimage', b'\x01', witness_script]
86+
child_one_txid = self.nodes[0].sendrawtransaction(hexstring=child_one.serialize().hex())
87+
child_one_wtxid = encode(hash256(child_one.serialize_with_witness())[::-1], 'hex_codec').decode('ascii')
88+
89+
self.log.info('Verify that transaction with lower-feerate witness gets in the mempool')
90+
in_mempool_wtxid = self.nodes[0].getrawmempool(True)[child_one_txid]['wtxid']
91+
assert_equal(child_one_wtxid, in_mempool_wtxid)
92+
93+
# Create another identical segwit transaction with witness solving second branch
94+
child_two = CTransaction()
95+
child_two.vin.append(CTxIn(COutPoint(int(parent_txid, 16), 0), b""))
96+
child_two.vout.append(CTxOut(int(9.99996 * COIN), child_script_pubkey))
97+
child_two.wit.vtxinwit.append(CTxInWitness())
98+
child_two.wit.vtxinwit[0].scriptWitness.stack = [b'', witness_script]
99+
child_two_txid = self.nodes[0].sendrawtransaction(hexstring=child_two.serialize().hex())
100+
child_two_wtxid = encode(hash256(child_two.serialize_with_witness())[::-1], 'hex_codec').decode('ascii')
101+
102+
assert_equal(child_one_txid, child_two_txid)
103+
assert_not_equal(child_one_wtxid, child_two_wtxid)
104+
105+
self.log.info('Verify that identical transaction with higher-feerate witness gets in the mempool')
106+
in_mempool_wtxid = self.nodes[0].getrawmempool(True)[child_one_txid]['wtxid']
107+
assert_equal(child_two_wtxid, in_mempool_wtxid)
108+
109+
if __name__ == '__main__':
110+
MempoolWtxidTest().main()

test/functional/test_framework/util.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ def assert_equal(thing1, thing2, *args):
4949
raise AssertionError("not(%s)" % " == ".join(str(arg) for arg in (thing1, thing2) + args))
5050

5151

52+
def assert_not_equal(thing1, thing2, *args):
53+
if thing1 == thing2 or any(thing1 == arg for arg in args):
54+
raise AssertionError("not(%s)" % " != ".join(str(arg) for arg in (thing1, thing2) + args))
55+
56+
5257
def assert_greater_than(thing1, thing2):
5358
if thing1 <= thing2:
5459
raise AssertionError("%s <= %s" % (str(thing1), str(thing2)))

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@
234234
'feature_asmap.py',
235235
'mempool_unbroadcast.py',
236236
'mempool_compatibility.py',
237+
'mempool_wtxid.py',
237238
'rpc_deriveaddresses.py',
238239
'rpc_deriveaddresses.py --usecli',
239240
'p2p_ping.py',

0 commit comments

Comments
 (0)