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.
1#!/usr/bin/env python32# Copyright (c) The Bitcoin Core developers3# Distributed under the MIT software license, see the accompanying4# file COPYING or http://www.opensource.org/licenses/mit-license.php.5"""Test gettxspendingprevout RPC."""7from test_framework.test_framework import BitcoinTestFramework8from test_framework.util import (9assert_equal,10assert_raises_rpc_error,11)12from test_framework.wallet import MiniWallet14#### Query Helpers ####15def prevout(txid, vout):16"""Build a prevout query dict for use with gettxspendingprevout"""17return {'txid': txid, 'vout': vout}19#### Result Helpers ####21def unspent_out(txid, vout):22"""Expected result for an available output (not spent)"""23return {'txid': txid, 'vout': vout}25def spent_out(txid, vout, spending_tx_id):26"""Expected result for an output with a known spender"""27return {'txid': txid, 'vout': vout, 'spendingtxid': spending_tx_id}29def spent_out_in_block(txid, vout, spending_tx_id, blockhash, spending_tx):30"""Expected result for an output spent in a confirmed block, with full tx data"""31return {'txid': txid, 'vout': vout, 'spendingtxid': spending_tx_id, 'blockhash': blockhash, 'spendingtx': spending_tx}33class GetTxSpendingPrevoutTest(BitcoinTestFramework):34def set_test_params(self):35self.num_nodes = 336self.noban_tx_relay = True37self.extra_args = [38["-txospenderindex"],39["-txospenderindex"],40[],41]43def run_test(self):44node0, node1, node2 = self.nodes45self.wallet = MiniWallet(node0)46root_utxo = self.wallet.get_utxo()47txid_root_utxo = root_utxo['txid']49def create_tx(**kwargs):50return self.wallet.send_self_transfer_multi(from_node=node0, **kwargs)52# Create a tree of unconfirmed transactions in the mempool:53# txA54# / \55# / \56# / \57# / \58# / \59# txB txC60# / \ / \61# / \ / \62# txD txE txF txG63# \ /64# \ /65# txH67txA = create_tx(utxos_to_spend=[root_utxo], num_outputs=2)68txB = create_tx(utxos_to_spend=[txA["new_utxos"][0]], num_outputs=2)69txC = create_tx(utxos_to_spend=[txA["new_utxos"][1]], num_outputs=2)70txD = create_tx(utxos_to_spend=[txB["new_utxos"][0]], num_outputs=1)71txE = create_tx(utxos_to_spend=[txB["new_utxos"][1]], num_outputs=1)72txF = create_tx(utxos_to_spend=[txC["new_utxos"][0]], num_outputs=2)73txG = create_tx(utxos_to_spend=[txC["new_utxos"][1]], num_outputs=1)74txH = create_tx(utxos_to_spend=[txE["new_utxos"][0], txF["new_utxos"][0]], num_outputs=1)76txs = [txA, txB, txC, txD, txE, txF, txG, txH]77txidA, txidB, txidC, txidD, txidE, txidF, txidG, txidH = [tx["txid"] for tx in txs]79self.sync_mempools()80self.wait_until(lambda: node0.getindexinfo()["txospenderindex"]["synced"])81self.wait_until(lambda: node1.getindexinfo()["txospenderindex"]["synced"])82mempool = node0.getrawmempool()83assert_equal(len(mempool), 8)84for tx in txs:85assert tx["txid"] in mempool87self.log.info("Find transactions spending outputs")88# wait until spending transactions are found in the mempool of node 0, 1 and 289result = node0.gettxspendingprevout([prevout(txid_root_utxo, vout=0), prevout(txidA, vout=1)])90assert_equal(result, [spent_out(txid_root_utxo, vout=0, spending_tx_id=txidA),91spent_out(txidA, vout=1, spending_tx_id=txidC)])92self.wait_until(lambda: node1.gettxspendingprevout([prevout(txid_root_utxo, vout=0), prevout(txidA, vout=1)]) == result)93self.wait_until(lambda: node2.gettxspendingprevout([prevout(txid_root_utxo, vout=0), prevout(txidA, vout=1)], mempool_only=True) == result)95self.log.info("Find transaction spending multiple outputs")96result = node0.gettxspendingprevout([prevout(txidE, vout=0), prevout(txidF, vout=0)])97assert_equal(result, [spent_out(txidE, vout=0, spending_tx_id=txidH),98spent_out(txidF, vout=0, spending_tx_id=txidH)])100self.log.info("Find no transaction when output is unspent")101assert_equal(node0.gettxspendingprevout([prevout(txidH, vout=0)]), [unspent_out(txidH, vout=0)])102for node in self.nodes:103assert_equal(node.gettxspendingprevout([prevout(txidA, vout=5)]), [unspent_out(txidA, vout=5)])105self.log.info("Mixed spent and unspent outputs")106result = node0.gettxspendingprevout([prevout(txidB, vout=0), prevout(txidG, vout=3)])107assert_equal(result, [spent_out(txidB, vout=0, spending_tx_id=txidD),108unspent_out(txidG, vout=3)])110self.log.info("Unknown input fields")111assert_raises_rpc_error(-3, "Unexpected key unknown", node0.gettxspendingprevout, [{'txid' : txidC, 'vout' : 1, 'unknown' : 42}])113self.log.info("Invalid vout provided")114assert_raises_rpc_error(-8, "Invalid parameter, vout cannot be negative", node0.gettxspendingprevout, [prevout(txidA, vout=-1)])116self.log.info("Invalid txid provided")117assert_raises_rpc_error(-3, "JSON value of type number for field txid is not of expected type string", node0.gettxspendingprevout, [prevout(42, vout=0)])119self.log.info("Missing outputs")120assert_raises_rpc_error(-8, "Invalid parameter, outputs are missing", node0.gettxspendingprevout, [])122self.log.info("Missing vout")123assert_raises_rpc_error(-3, "Missing vout", node0.gettxspendingprevout, [{'txid' : txidA}])125self.log.info("Missing txid")126assert_raises_rpc_error(-3, "Missing txid", node0.gettxspendingprevout, [{'vout' : 3}])128blockhash = self.generate(self.wallet, 1)[0]129# spending transactions are found in the index of nodes 0 and 1 but not node 2130for node in [node0, node1]:131result = node.gettxspendingprevout([prevout(txid_root_utxo, vout=0), prevout(txidA, vout=1)], return_spending_tx=True)132assert_equal(result, [spent_out_in_block(txid_root_utxo, vout=0, spending_tx_id=txidA, blockhash=blockhash, spending_tx=txA['hex']),133spent_out_in_block(txidA, vout=1, spending_tx_id=txidC, blockhash=blockhash, spending_tx=txC['hex'])])134# Spending transactions not in node 2 (no spending index)135result = node2.gettxspendingprevout([prevout(txid_root_utxo, vout=0), prevout(txidA, vout=1)], return_spending_tx=True)136assert_equal(result, [unspent_out(txid_root_utxo, vout=0), unspent_out(txidA, vout=1)])138# spending transaction is not found if we only search the mempool139result = node0.gettxspendingprevout([prevout(txid_root_utxo, vout=0), prevout(txidA, vout=1)], return_spending_tx=True, mempool_only=True)140assert_equal(result, [unspent_out(txid_root_utxo, vout=0), unspent_out(txidA, vout=1)])142self.log.info("Check that our txospenderindex is updated when a reorg replaces a spending transaction")143reorg_replace_utxo = self.wallet.get_utxo(mark_as_spent=False)144txid_reorg_replace_utxo = reorg_replace_utxo['txid']146tx1 = create_tx(utxos_to_spend=[reorg_replace_utxo], num_outputs=1)147blockhash = self.generate(self.wallet, 1)[0]149# tx1 is confirmed, and indexed in txospenderindex as spending our utxo150assert tx1["txid"] not in node0.getrawmempool()151result = node0.gettxspendingprevout([prevout(txid_reorg_replace_utxo, vout=0)], return_spending_tx=True)152assert_equal(result, [spent_out_in_block(txid_reorg_replace_utxo, vout=0, spending_tx_id=tx1["txid"], blockhash=blockhash, spending_tx=tx1['hex'])])154# replace tx1 with tx2 triggering a "reorg"155best_block_hash = node0.getbestblockhash()156for node in self.nodes:157node.invalidateblock(best_block_hash)158assert tx1["txid"] in node.getrawmempool()160# create and submit replacement161tx2 = create_tx(utxos_to_spend=[reorg_replace_utxo], num_outputs=2)162assert tx2["txid"] in node0.getrawmempool()164# check that when we find tx2 when we look in the mempool for a tx spending our output165result = node0.gettxspendingprevout([prevout(txid_reorg_replace_utxo, vout=0)], return_spending_tx=True)166assert_equal(result, [ {'txid' : txid_reorg_replace_utxo, 'vout' : 0, 'spendingtxid' : tx2["txid"], 'spendingtx' : tx2['hex']} ])168# check that our txospenderindex has been updated169blockhash = self.generate(self.wallet, 1)[0]170result = node0.gettxspendingprevout([prevout(txid_reorg_replace_utxo, vout=0)], return_spending_tx=True)171assert_equal(result, [spent_out_in_block(txid_reorg_replace_utxo, vout=0, spending_tx_id=tx2["txid"], blockhash=blockhash, spending_tx=tx2['hex'])])173self.log.info("Check that our txospenderindex is updated when a reorg cancels a spending transaction")174reorg_cancel_utxo = self.wallet.get_utxo(mark_as_spent=False)175txid_reorg_cancel_utxo = reorg_cancel_utxo['txid']177tx1 = create_tx(utxos_to_spend=[reorg_cancel_utxo], num_outputs=1)178tx2 = create_tx(utxos_to_spend=[tx1["new_utxos"][0]], num_outputs=1)179# tx1 spends our utxo, tx2 spends tx1180blockhash = self.generate(self.wallet, 1)[0]181# tx1 and tx2 are confirmed, and indexed in txospenderindex182result = node0.gettxspendingprevout([prevout(txid_reorg_cancel_utxo, vout=0)], return_spending_tx=True)183assert_equal(result, [spent_out_in_block(txid_reorg_cancel_utxo, vout=0, spending_tx_id=tx1["txid"], blockhash=blockhash, spending_tx=tx1['hex'])])184result = node0.gettxspendingprevout([prevout(tx1['txid'], vout=0)], return_spending_tx=True)185assert_equal(result, [spent_out_in_block(tx1['txid'], vout=0, spending_tx_id=tx2["txid"], blockhash=blockhash, spending_tx=tx2['hex'])])187# replace tx1 with tx3188blockhash = node0.getbestblockhash()189for node in self.nodes:190node.invalidateblock(blockhash)192tx3 = create_tx(utxos_to_spend=[reorg_cancel_utxo], num_outputs=2, fee_per_output=2000)193node0_mempool = node0.getrawmempool()194assert tx3["txid"] in node0_mempool195assert tx1["txid"] not in node0_mempool196assert tx2["txid"] not in node0_mempool198# tx2 is not in the mempool anymore, but still in txospender index which has not been rewound yet199result = node0.gettxspendingprevout([prevout(tx1['txid'], vout=0)], return_spending_tx=True)200assert_equal(result, [spent_out_in_block(tx1['txid'], vout=0, spending_tx_id=tx2["txid"], blockhash=blockhash, spending_tx=tx2['hex'])])202txinfo = node0.getrawtransaction(tx2["txid"], verbose = True, blockhash = blockhash)203assert_equal(txinfo["confirmations"], 0)204assert_equal(txinfo["in_active_chain"], False)206blockhash = self.generate(self.wallet, 1)[0]207# we check that the spending tx for tx1 is now tx3208result = node0.gettxspendingprevout([prevout(txid_reorg_cancel_utxo, vout=0)], return_spending_tx=True)209assert_equal(result, [spent_out_in_block(txid_reorg_cancel_utxo, vout=0, spending_tx_id=tx3["txid"], blockhash=blockhash, spending_tx=tx3['hex'])])211# we check that there is no more spending tx for tx1212result = node0.gettxspendingprevout([prevout(tx1['txid'], vout=0)], return_spending_tx=True)213assert_equal(result, [unspent_out(tx1['txid'], vout=0)])215self.log.info("Check mixed mempool and index result order")216tx4 = create_tx(utxos_to_spend=[txH["new_utxos"][0]], num_outputs=1)217result = node0.gettxspendingprevout([prevout(txid_reorg_cancel_utxo, vout=0), prevout(txidH, vout=0)])218assert_equal(result, [219spent_out(txid_reorg_cancel_utxo, vout=0, spending_tx_id=tx3["txid"]) | {"blockhash": blockhash},220spent_out(txidH, vout=0, spending_tx_id=tx4["txid"]),221])224if __name__ == '__main__':225GetTxSpendingPrevoutTest(__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.