Skip to content

Commit 09e86d7

Browse files
committed
Merge bitcoin/bitcoin#27200: test: psbt: check non-witness UTXO removal for segwit v1 input
3dd2f64 test: psbt: check non-witness UTXO removal for segwit v1 input (Sebastian Falbesoner) dd78e3f test: speedup rpc_psbt.py by whitelisting peers (immediate tx relay) (Sebastian Falbesoner) e194e3e test: PSBT: eliminate magic numbers for global unsigned tx key (0) (Sebastian Falbesoner) Pull request description: This PR adds missing test coverage for dropping non-witness UTXOs from PSBTs for segwit v1+ inputs (see commit 103c6fd). The formerly [disabled](bitcoin/bitcoin@4600479) method `test_utxo_conversion` is re-enabled and adapted to spend a Taproot (`bech32m`) instead of a wrapped SegWit (`p2sh-segwit`) output. Note that in contrast to the original test, we have to add the non-witness UTXO manually here using the test framework's PSBT module, since the constructing node knows that the output is segwit v1 and hence doesn't add the non-witness UTXO in the first place (see also [BIP371]( https://github.com/bitcoin/bips/blob/master/bip-0371.mediawiki#user-content-UTXO_Types)). I strongly assume that most wallets would behave the same as Bitcoin Core here and wouldn't create PSBTs with non-witness UTXOs for Taproot inputs, but it's still good to test everything works as expected if it's still done and that the non-witness UTXO is simply dropped in that case. The first two commits contain a small refactor (magic number elimination in PSBT module) and test speedup of ~2-3x (using whitelisting peers / immediate tx relay). ACKs for top commit: achow101: ACK 3dd2f64 instagibbs: ACK 3dd2f64 Tree-SHA512: b8d7f7ea5d7d21def024b70dfca61991cc96a4193be8857018b4d7cf3ca1465d185619fd4a77623803d9da309aa489c53273e9b7683d970ce12e2399b5b50031
2 parents e695d85 + 3dd2f64 commit 09e86d7

File tree

2 files changed

+31
-21
lines changed

2 files changed

+31
-21
lines changed

test/functional/rpc_psbt.py

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55
"""Test the Partially Signed Transaction RPCs.
66
"""
7-
87
from decimal import Decimal
98
from itertools import product
109

@@ -27,6 +26,7 @@
2726
PSBT_IN_SHA256,
2827
PSBT_IN_HASH160,
2928
PSBT_IN_HASH256,
29+
PSBT_IN_NON_WITNESS_UTXO,
3030
PSBT_IN_WITNESS_UTXO,
3131
PSBT_OUT_TAP_TREE,
3232
)
@@ -59,13 +59,16 @@ def set_test_params(self):
5959
["-walletrbf=0", "-changetype=legacy"],
6060
[]
6161
]
62+
# whitelist peers to speed up tx relay / mempool sync
63+
for args in self.extra_args:
64+
args.append("[email protected]")
6265
self.supports_cli = False
6366

6467
def skip_test_if_missing_module(self):
6568
self.skip_if_no_wallet()
6669

67-
# TODO: Re-enable this test with segwit v1
6870
def test_utxo_conversion(self):
71+
self.log.info("Check that non-witness UTXOs are removed for segwit v1+ inputs")
6972
mining_node = self.nodes[2]
7073
offline_node = self.nodes[0]
7174
online_node = self.nodes[1]
@@ -77,34 +80,41 @@ def test_utxo_conversion(self):
7780
# Create watchonly on online_node
7881
online_node.createwallet(wallet_name='wonline', disable_private_keys=True)
7982
wonline = online_node.get_wallet_rpc('wonline')
80-
w2 = online_node.get_wallet_rpc('')
83+
w2 = online_node.get_wallet_rpc(self.default_wallet_name)
8184

8285
# Mine a transaction that credits the offline address
83-
offline_addr = offline_node.getnewaddress(address_type="p2sh-segwit")
84-
online_addr = w2.getnewaddress(address_type="p2sh-segwit")
86+
offline_addr = offline_node.getnewaddress(address_type="bech32m")
87+
online_addr = w2.getnewaddress(address_type="bech32m")
8588
wonline.importaddress(offline_addr, "", False)
86-
mining_node.sendtoaddress(address=offline_addr, amount=1.0)
87-
self.generate(mining_node, nblocks=1)
89+
mining_wallet = mining_node.get_wallet_rpc(self.default_wallet_name)
90+
mining_wallet.sendtoaddress(address=offline_addr, amount=1.0)
91+
self.generate(mining_node, nblocks=1, sync_fun=lambda: self.sync_all([online_node, mining_node]))
8892

89-
# Construct an unsigned PSBT on the online node (who doesn't know the output is Segwit, so will include a non-witness UTXO)
93+
# Construct an unsigned PSBT on the online node
9094
utxos = wonline.listunspent(addresses=[offline_addr])
9195
raw = wonline.createrawtransaction([{"txid":utxos[0]["txid"], "vout":utxos[0]["vout"]}],[{online_addr:0.9999}])
9296
psbt = wonline.walletprocesspsbt(online_node.converttopsbt(raw))["psbt"]
93-
assert "non_witness_utxo" in mining_node.decodepsbt(psbt)["inputs"][0]
97+
assert not "not_witness_utxo" in mining_node.decodepsbt(psbt)["inputs"][0]
98+
99+
# add non-witness UTXO manually
100+
psbt_new = PSBT.from_base64(psbt)
101+
prev_tx = wonline.gettransaction(utxos[0]["txid"])["hex"]
102+
psbt_new.i[0].map[PSBT_IN_NON_WITNESS_UTXO] = bytes.fromhex(prev_tx)
103+
assert "non_witness_utxo" in mining_node.decodepsbt(psbt_new.to_base64())["inputs"][0]
94104

95-
# Have the offline node sign the PSBT (which will update the UTXO to segwit)
96-
signed_psbt = offline_node.walletprocesspsbt(psbt)["psbt"]
97-
assert "witness_utxo" in mining_node.decodepsbt(signed_psbt)["inputs"][0]
105+
# Have the offline node sign the PSBT (which will remove the non-witness UTXO)
106+
signed_psbt = offline_node.walletprocesspsbt(psbt_new.to_base64())["psbt"]
107+
assert not "non_witness_utxo" in mining_node.decodepsbt(signed_psbt)["inputs"][0]
98108

99109
# Make sure we can mine the resulting transaction
100110
txid = mining_node.sendrawtransaction(mining_node.finalizepsbt(signed_psbt)["hex"])
101-
self.generate(mining_node, 1)
111+
self.generate(mining_node, nblocks=1, sync_fun=lambda: self.sync_all([online_node, mining_node]))
102112
assert_equal(online_node.gettxout(txid,0)["confirmations"], 1)
103113

104114
wonline.unloadwallet()
105115

106116
# Reconnect
107-
self.connect_nodes(0, 1)
117+
self.connect_nodes(1, 0)
108118
self.connect_nodes(0, 2)
109119

110120
def test_input_confs_control(self):
@@ -571,8 +581,8 @@ def run_test(self):
571581
for i, signer in enumerate(signers):
572582
self.nodes[2].unloadwallet("wallet{}".format(i))
573583

574-
# TODO: Re-enable this for segwit v1
575-
# self.test_utxo_conversion()
584+
if self.options.descriptors:
585+
self.test_utxo_conversion()
576586

577587
self.test_input_confs_control()
578588

test/functional/test_framework/psbt.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ def __init__(self, *, g=None, i=None, o=None):
105105
def deserialize(self, f):
106106
assert f.read(5) == b"psbt\xff"
107107
self.g = from_binary(PSBTMap, f)
108-
assert 0 in self.g.map
109-
self.tx = from_binary(CTransaction, self.g.map[0])
108+
assert PSBT_GLOBAL_UNSIGNED_TX in self.g.map
109+
self.tx = from_binary(CTransaction, self.g.map[PSBT_GLOBAL_UNSIGNED_TX])
110110
self.i = [from_binary(PSBTMap, f) for _ in self.tx.vin]
111111
self.o = [from_binary(PSBTMap, f) for _ in self.tx.vout]
112112
return self
@@ -115,8 +115,8 @@ def serialize(self):
115115
assert isinstance(self.g, PSBTMap)
116116
assert isinstance(self.i, list) and all(isinstance(x, PSBTMap) for x in self.i)
117117
assert isinstance(self.o, list) and all(isinstance(x, PSBTMap) for x in self.o)
118-
assert 0 in self.g.map
119-
tx = from_binary(CTransaction, self.g.map[0])
118+
assert PSBT_GLOBAL_UNSIGNED_TX in self.g.map
119+
tx = from_binary(CTransaction, self.g.map[PSBT_GLOBAL_UNSIGNED_TX])
120120
assert len(tx.vin) == len(self.i)
121121
assert len(tx.vout) == len(self.o)
122122

@@ -130,7 +130,7 @@ def make_blank(self):
130130
for m in self.i + self.o:
131131
m.map.clear()
132132

133-
self.g = PSBTMap(map={0: self.g.map[0]})
133+
self.g = PSBTMap(map={PSBT_GLOBAL_UNSIGNED_TX: self.g.map[PSBT_GLOBAL_UNSIGNED_TX]})
134134

135135
def to_base64(self):
136136
return base64.b64encode(self.serialize()).decode("utf8")

0 commit comments

Comments
 (0)