Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
251assert_equal(tx_info["hex"], canonical_tx_hex)252assert_equal(tx_info["wtxid"], canonical_wtxid)253# alternate_wtxids lists the other variants, never the canonical one254assert canonical_wtxid not in tx_info["alternate_wtxids"]255assert_equal(set(tx_info["alternate_wtxids"]), set(alternate_wtxids))257# listtransactions exposes the same alternate_wtxids field as gettransaction258list_entry = next(entry for entry in wallet.listtransactions() if entry["txid"] == txid)259assert_equal(list_entry["alternate_wtxids"], tx_info["alternate_wtxids"])261# Returns the finalized psbt transaction and its wtxid262def finalize_tx_variant(self, wallet, psbt, spend_path):263# First check the expected spend path is being used264sig_field = {"script": "taproot_script_path_sigs", "key": "taproot_key_path_sig"}265present, absent = sig_field[spend_path], sig_field["key" if spend_path == "script" else "script"]266psbt_input = self.nodes[0].decodepsbt(psbt)["inputs"][0]267assert present in psbt_input and absent not in psbt_input269# Then finalize and decode270tx = wallet.finalizepsbt(psbt)["hex"]271return tx, wallet.decoderawtransaction(tx)["hash"]273def test_alternate_witness_tx(self):274self.log.info("Test gettransaction and listtransactions report alternate witnesses and canonical variant")275self.nodes[0].createwallet("altwit")276default_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)277wallet = self.nodes[0].get_wallet_rpc("altwit")279xprvs = [ExtendedPrivateKey.generate() for _ in range(0, 2)]280xpubs = [xprv.pubkey() for xprv in xprvs]282# Import a taproot descriptor with script paths283desc = descsum_create(f"tr({xpubs[0].to_string()}/*,pk({xprvs[1].to_string()}/*))")284assert_equal(wallet.importdescriptors([{"desc": desc, "active": True, "timestamp": "now"}])[0]["success"], True)285default_wallet.sendtoaddress(wallet.getnewaddress(address_type="bech32m"), 1)286self.generate(self.nodes[0], 1)287# Isolate node0 for later reorg coverage288self.disconnect_nodes(0, 1)289self.disconnect_nodes(0, 2)291# Create output psbt292psbt = wallet.walletcreatefundedpsbt(outputs=[{default_wallet.getnewaddress(): 0.5}])["psbt"]294# Create a script path spend and relay it. With only one variant known it295# is trivially canonical and has no alternates296self.log.info("Test the only known variant is canonical with no alternates")297script_path_psbt = wallet.walletprocesspsbt(psbt=psbt, finalize=False)["psbt"]298script_path_tx, script_path_wtxid = self.finalize_tx_variant(wallet, script_path_psbt, spend_path="script")299txid = self.nodes[0].sendrawtransaction(script_path_tx)300self.check_tx_variants(wallet, txid, script_path_tx, script_path_wtxid, alternate_wtxids=[])302# Make a key path spend separate from the wallet303key_path_desc = descsum_create(f"tr({xprvs[0].to_string()}/*,pk({xpubs[1].to_string()}/*))")304key_path_psbt = self.nodes[0].descriptorprocesspsbt(psbt=psbt, descriptors=[{"desc": key_path_desc}], finalize=False)["psbt"]305key_path_tx, key_path_wtxid = self.finalize_tx_variant(wallet, key_path_psbt, spend_path="key")307# Ensure variants share the same txid but differ in wtxid, and the key path is the lighter of the two308assert_equal(txid, self.nodes[0].decoderawtransaction(key_path_tx)["txid"])309assert_not_equal(script_path_wtxid, key_path_wtxid)310assert_greater_than(311self.nodes[0].decoderawtransaction(script_path_tx)["weight"],312self.nodes[0].decoderawtransaction(key_path_tx)["weight"],313)315# The wallet only learns the key path witness from a block (the mempool316# holds one transaction per txid). Mine the key path: a confirmed variant317# is canonical, with the script path now listed as its alternate.318block = self.generateblock(self.nodes[0], default_wallet.getnewaddress(), [key_path_tx], sync_fun=self.no_op)["hash"]319self.check_tx_variants(wallet, txid, key_path_tx, key_path_wtxid, alternate_wtxids=[script_path_wtxid])321# Reorg that block out so both variants are known and unconfirmed. With no322# confirmation to force the choice, the lighter key path is canonical.323self.log.info("Test the lighter variant is canonical when both are known and unconfirmed")324self.nodes[0].invalidateblock(block)325self.nodes[0].syncwithvalidationinterfacequeue()326assert_equal(wallet.gettransaction(txid)["confirmations"], 0)327self.check_tx_variants(wallet, txid, key_path_tx, key_path_wtxid, alternate_wtxids=[script_path_wtxid])329# The canonical choice and alternates survive a wallet reload330wallet.unloadwallet()331self.nodes[0].loadwallet("altwit")332self.check_tx_variants(wallet, txid, key_path_tx, key_path_wtxid, alternate_wtxids=[script_path_wtxid])334# Now confirm the heavier script path instead, on a longer competing chain335# from node1, and reconnect so node0 reorgs onto it. The confirmed variant336# is canonical even though it is the heavier one.337self.log.info("Test a confirmed variant is canonical even when it is the heavier one")338self.generate(self.nodes[1], 3, sync_fun=self.no_op)339block = self.generateblock(self.nodes[1], default_wallet.getnewaddress(), [script_path_tx], sync_fun=self.no_op)["hash"]340self.connect_nodes(0, 1)341self.connect_nodes(0, 2)342self.sync_all()343self.check_tx_variants(wallet, txid, script_path_tx, script_path_wtxid, alternate_wtxids=[key_path_wtxid])345# The confirmed-canonical choice survive a reload346wallet.unloadwallet()347self.nodes[0].loadwallet("altwit")348self.check_tx_variants(wallet, txid, script_path_tx, script_path_wtxid, alternate_wtxids=[key_path_wtxid])350self.log.info("Test canonical reverts to the lighter variant when the confirmed one is reorged out")351# Both variants are unconfirmed again, so the lighter key path is canonical once more352self.disconnect_nodes(0, 1)353self.disconnect_nodes(0, 2)354self.nodes[0].invalidateblock(block)355self.nodes[0].syncwithvalidationinterfacequeue()356assert_equal(wallet.gettransaction(txid)["confirmations"], 0)357self.check_tx_variants(wallet, txid, key_path_tx, key_path_wtxid, alternate_wtxids=[script_path_wtxid])359# listsinceblock "removed" entries reflect the wallet's current CWalletTx, not a360# snapshot of the detached block. The detached block contained the heavier script361# path variant, but "wtxid" reports the current canonical (key path) variant and362# the script path variant appears under "alternate_wtxids". A future improvement363# could track which specific variant was in the detached block and report that.364removed = next(e for e in wallet.listsinceblock(block)["removed"] if e["txid"] == txid)365assert_equal(removed["confirmations"], 0)366assert_equal(removed["wtxid"], key_path_wtxid)367assert_equal(removed["alternate_wtxids"], [script_path_wtxid])370if __name__ == '__main__':371ListTransactionsTest(__file__).main()
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.