In 21631e94b9e647f6b286195efe03137d886abd36:
It would be good to check a few other things here:
diff --git a/test/functional/wallet_listtransactions.py b/test/functional/wallet_listtransactions.py
--- a/test/functional/wallet_listtransactions.py
+++ b/test/functional/wallet_listtransactions.py
@@ -16,6 +16,7 @@
assert_not_equal,
assert_array_result,
assert_equal,
+ assert_greater_than,
assert_raises_rpc_error,
find_vout_for_address,
)
@@ -243,6 +244,31 @@
assert "fee" in tx_info
assert_equal(any(detail["category"] == "send" for detail in tx_info["details"]), True)
+ def check_tx_variants(self, wallet, txid, canonical_tx_hex, canonical_wtxid, alternate_wtxids):
+ """Assert gettransaction and listtransactions report tx variants properly"""
+ tx_info = wallet.gettransaction(txid)
+ assert_equal(tx_info["hex"], canonical_tx_hex)
+ assert_equal(tx_info["wtxid"], canonical_wtxid)
+ # alternate_wtxids lists the other variants, never the canonical one.
+ assert canonical_wtxid not in tx_info["alternate_wtxids"]
+ assert_equal(set(tx_info["alternate_wtxids"]), set(alternate_wtxids))
+
+ # listtransactions exposes the same alternate_wtxids field as gettransaction
+ list_entry = next(entry for entry in wallet.listtransactions() if entry["txid"] == txid)
+ assert_equal(list_entry["alternate_wtxids"], tx_info["alternate_wtxids"])
+
+ # Returns the finalized psbt transaction and its wtxid
+ def finalize_tx_variant(self, wallet, psbt, spend_path):
+ # First check the expected spend path is being used
+ sig_field = {"script": "taproot_script_path_sigs", "key": "taproot_key_path_sig"}
+ present, absent = sig_field[spend_path], sig_field["key" if spend_path == "script" else "script"]
+ psbt_input = self.nodes[0].decodepsbt(psbt)["inputs"][0]
+ assert present in psbt_input and absent not in psbt_input
+
+ # Then finalize and decode
+ tx = wallet.finalizepsbt(psbt)["hex"]
+ return tx, wallet.decoderawtransaction(tx)["hash"]
+
def test_alternate_witness_tx(self):
self.log.info("Test gettransaction when a transaction with an alternate wtxid is added")
self.nodes[0].createwallet("altwit")
@@ -259,42 +285,44 @@
self.disconnect_nodes(0, 1)
self.disconnect_nodes(0, 2)
+ # Create output psbt
+ psbt = wallet.walletcreatefundedpsbt(outputs=[{default_wallet.getnewaddress(): 0.5}])["psbt"]
+
# Create a script path spend
- psbt = wallet.walletcreatefundedpsbt(outputs=[{default_wallet.getnewaddress(): 0.5}])["psbt"]
script_path_psbt = wallet.walletprocesspsbt(psbt=psbt, finalize=False)["psbt"]
- dec_psbt = self.nodes[0].decodepsbt(script_path_psbt)
- assert "taproot_script_path_sigs" in dec_psbt["inputs"][0]
- assert "taproot_key_path_sig" not in dec_psbt["inputs"][0]
- script_path_tx = wallet.finalizepsbt(script_path_psbt)["hex"]
- script_path_wtxid = self.nodes[0].decoderawtransaction(script_path_tx)["hash"]
+ script_path_tx, script_path_wtxid = self.finalize_tx_variant(wallet, script_path_psbt, spend_path="script")
txid = self.nodes[0].sendrawtransaction(script_path_tx)
- wallet.gettransaction(txid)
+ self.check_tx_variants(wallet, txid, script_path_tx, script_path_wtxid, alternate_wtxids=[])
# Make a key path spend separate from the wallet
key_path_desc = descsum_create("tr(tprv8ZgxMBicQKsPerQj6m35no46amfKQdjY7AhLnmatHYXs8S4MTgeZYkWAn4edSGwwL3vkSiiGqSZQrmy5D3P5gBoqgvYP2fCUpBwbKTMTAkL/*,pk(tprv8ZgxMBicQKsPd3cbrKjE5GKKJLDEidhtzSSmPVtSPyoHQGL2LZw49yt9foZsN9BeiC5VqRaESUSDV2PS9w7zAVBSK6EQH3CZW9sMKxSKDwD/*))")
key_path_psbt = self.nodes[0].descriptorprocesspsbt(psbt=psbt, descriptors=[{"desc": key_path_desc}], finalize=False)["psbt"]
- dec_psbt = self.nodes[0].decodepsbt(key_path_psbt)
- assert "taproot_script_path_sigs" not in dec_psbt["inputs"][0]
- assert "taproot_key_path_sig" in dec_psbt["inputs"][0]
- key_path_tx = wallet.finalizepsbt(key_path_psbt)["hex"]
- key_path_wtxid = self.nodes[0].decoderawtransaction(key_path_tx)["hash"]
- assert_not_equal(script_path_wtxid, key_path_wtxid)
+ key_path_tx, key_path_wtxid = self.finalize_tx_variant(wallet, key_path_psbt, spend_path="key")
txid2 = self.nodes[0].sendrawtransaction(key_path_tx)
+
+ # Ensure txs wtxids are not equal but their txid are
+ assert_not_equal(script_path_wtxid, key_path_wtxid)
assert_equal(txid, txid2)
+
+ # The key path spend has a lower weight than the script path spend, so
+ # once both are known the key path is chosen as the canonical tx.
+ assert_greater_than(
+ self.nodes[0].decoderawtransaction(script_path_tx)["weight"],
+ self.nodes[0].decoderawtransaction(key_path_tx)["weight"],
+ )
+
+ # Mine and check the wallet has been properly updated
self.generateblock(self.nodes[0], default_wallet.getnewaddress(), [key_path_tx], sync_fun=self.no_op)
- tx_info = wallet.gettransaction(txid)
+ self.check_tx_variants(wallet, txid2, key_path_tx, key_path_wtxid, alternate_wtxids=[script_path_wtxid])
- # The transaction returned by gettransaction should be the key path as it has a lower weight
- # And the script path wtxid should be in alternate_txids
- assert_equal(tx_info["hex"], key_path_tx)
- assert script_path_wtxid in tx_info["alternate_wtxids"]
-
- # Check persistence
+ # Check persistence: both witness variants and the canonical choice survive a wallet reload
wallet.unloadwallet()
self.nodes[0].loadwallet("altwit")
tx_info = wallet.gettransaction(txid)
assert_equal(tx_info["hex"], key_path_tx)
+ assert_equal(tx_info["wtxid"], key_path_wtxid)
assert script_path_wtxid in tx_info["alternate_wtxids"]
+ assert key_path_wtxid not in tx_info["alternate_wtxids"]
# Reorging the script path spend to be confirmed will change the canonical tx
self.generate(self.nodes[1], 3, sync_fun=self.no_op)
@@ -303,9 +331,12 @@
self.connect_nodes(0, 2)
self.sync_all()
- tx_info = wallet.gettransaction(txid)
- assert_equal(tx_info["hex"], script_path_tx)
- assert key_path_wtxid in tx_info["alternate_wtxids"]
+ self.check_tx_variants(wallet, txid, script_path_tx, script_path_wtxid, alternate_wtxids=[key_path_wtxid])
+
+ # The canonical flip must also persist across a reload
+ wallet.unloadwallet()
+ self.nodes[0].loadwallet("altwit")
+ self.check_tx_variants(wallet, txid, script_path_tx, script_path_wtxid, alternate_wtxids=[key_path_wtxid])
if __name__ == '__main__':