rpc_gettxspendingprevout.py
1 #!/usr/bin/env python3 2 # Copyright (c) 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 gettxspendingprevout RPC.""" 6 7 from test_framework.test_framework import BitcoinTestFramework 8 from test_framework.util import ( 9 assert_equal, 10 assert_raises_rpc_error, 11 ) 12 from test_framework.wallet import MiniWallet 13 14 #### Query Helpers #### 15 def prevout(txid, vout): 16 """Build a prevout query dict for use with gettxspendingprevout""" 17 return {'txid': txid, 'vout': vout} 18 19 #### Result Helpers #### 20 21 def unspent_out(txid, vout): 22 """Expected result for an available output (not spent)""" 23 return {'txid': txid, 'vout': vout} 24 25 def spent_out(txid, vout, spending_tx_id): 26 """Expected result for an output with a known spender""" 27 return {'txid': txid, 'vout': vout, 'spendingtxid': spending_tx_id} 28 29 def 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""" 31 return {'txid': txid, 'vout': vout, 'spendingtxid': spending_tx_id, 'blockhash': blockhash, 'spendingtx': spending_tx} 32 33 class GetTxSpendingPrevoutTest(BitcoinTestFramework): 34 def set_test_params(self): 35 self.num_nodes = 3 36 self.noban_tx_relay = True 37 self.extra_args = [ 38 ["-txospenderindex"], 39 ["-txospenderindex"], 40 [], 41 ] 42 43 def run_test(self): 44 node0, node1, node2 = self.nodes 45 self.wallet = MiniWallet(node0) 46 root_utxo = self.wallet.get_utxo() 47 txid_root_utxo = root_utxo['txid'] 48 49 def create_tx(**kwargs): 50 return self.wallet.send_self_transfer_multi(from_node=node0, **kwargs) 51 52 # Create a tree of unconfirmed transactions in the mempool: 53 # txA 54 # / \ 55 # / \ 56 # / \ 57 # / \ 58 # / \ 59 # txB txC 60 # / \ / \ 61 # / \ / \ 62 # txD txE txF txG 63 # \ / 64 # \ / 65 # txH 66 67 txA = create_tx(utxos_to_spend=[root_utxo], num_outputs=2) 68 txB = create_tx(utxos_to_spend=[txA["new_utxos"][0]], num_outputs=2) 69 txC = create_tx(utxos_to_spend=[txA["new_utxos"][1]], num_outputs=2) 70 txD = create_tx(utxos_to_spend=[txB["new_utxos"][0]], num_outputs=1) 71 txE = create_tx(utxos_to_spend=[txB["new_utxos"][1]], num_outputs=1) 72 txF = create_tx(utxos_to_spend=[txC["new_utxos"][0]], num_outputs=2) 73 txG = create_tx(utxos_to_spend=[txC["new_utxos"][1]], num_outputs=1) 74 txH = create_tx(utxos_to_spend=[txE["new_utxos"][0], txF["new_utxos"][0]], num_outputs=1) 75 76 txs = [txA, txB, txC, txD, txE, txF, txG, txH] 77 txidA, txidB, txidC, txidD, txidE, txidF, txidG, txidH = [tx["txid"] for tx in txs] 78 79 self.sync_mempools() 80 self.wait_until(lambda: node0.getindexinfo()["txospenderindex"]["synced"]) 81 self.wait_until(lambda: node1.getindexinfo()["txospenderindex"]["synced"]) 82 mempool = node0.getrawmempool() 83 assert_equal(len(mempool), 8) 84 for tx in txs: 85 assert tx["txid"] in mempool 86 87 self.log.info("Find transactions spending outputs") 88 # wait until spending transactions are found in the mempool of node 0, 1 and 2 89 result = node0.gettxspendingprevout([prevout(txid_root_utxo, vout=0), prevout(txidA, vout=1)]) 90 assert_equal(result, [spent_out(txid_root_utxo, vout=0, spending_tx_id=txidA), 91 spent_out(txidA, vout=1, spending_tx_id=txidC)]) 92 self.wait_until(lambda: node1.gettxspendingprevout([prevout(txid_root_utxo, vout=0), prevout(txidA, vout=1)]) == result) 93 self.wait_until(lambda: node2.gettxspendingprevout([prevout(txid_root_utxo, vout=0), prevout(txidA, vout=1)], mempool_only=True) == result) 94 95 self.log.info("Find transaction spending multiple outputs") 96 result = node0.gettxspendingprevout([prevout(txidE, vout=0), prevout(txidF, vout=0)]) 97 assert_equal(result, [spent_out(txidE, vout=0, spending_tx_id=txidH), 98 spent_out(txidF, vout=0, spending_tx_id=txidH)]) 99 100 self.log.info("Find no transaction when output is unspent") 101 assert_equal(node0.gettxspendingprevout([prevout(txidH, vout=0)]), [unspent_out(txidH, vout=0)]) 102 for node in self.nodes: 103 assert_equal(node.gettxspendingprevout([prevout(txidA, vout=5)]), [unspent_out(txidA, vout=5)]) 104 105 self.log.info("Mixed spent and unspent outputs") 106 result = node0.gettxspendingprevout([prevout(txidB, vout=0), prevout(txidG, vout=3)]) 107 assert_equal(result, [spent_out(txidB, vout=0, spending_tx_id=txidD), 108 unspent_out(txidG, vout=3)]) 109 110 self.log.info("Unknown input fields") 111 assert_raises_rpc_error(-3, "Unexpected key unknown", node0.gettxspendingprevout, [{'txid' : txidC, 'vout' : 1, 'unknown' : 42}]) 112 113 self.log.info("Invalid vout provided") 114 assert_raises_rpc_error(-8, "Invalid parameter, vout cannot be negative", node0.gettxspendingprevout, [prevout(txidA, vout=-1)]) 115 116 self.log.info("Invalid txid provided") 117 assert_raises_rpc_error(-3, "JSON value of type number for field txid is not of expected type string", node0.gettxspendingprevout, [prevout(42, vout=0)]) 118 119 self.log.info("Missing outputs") 120 assert_raises_rpc_error(-8, "Invalid parameter, outputs are missing", node0.gettxspendingprevout, []) 121 122 self.log.info("Missing vout") 123 assert_raises_rpc_error(-3, "Missing vout", node0.gettxspendingprevout, [{'txid' : txidA}]) 124 125 self.log.info("Missing txid") 126 assert_raises_rpc_error(-3, "Missing txid", node0.gettxspendingprevout, [{'vout' : 3}]) 127 128 blockhash = self.generate(self.wallet, 1)[0] 129 # spending transactions are found in the index of nodes 0 and 1 but not node 2 130 for node in [node0, node1]: 131 result = node.gettxspendingprevout([prevout(txid_root_utxo, vout=0), prevout(txidA, vout=1)], return_spending_tx=True) 132 assert_equal(result, [spent_out_in_block(txid_root_utxo, vout=0, spending_tx_id=txidA, blockhash=blockhash, spending_tx=txA['hex']), 133 spent_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) 135 result = node2.gettxspendingprevout([prevout(txid_root_utxo, vout=0), prevout(txidA, vout=1)], return_spending_tx=True) 136 assert_equal(result, [unspent_out(txid_root_utxo, vout=0), unspent_out(txidA, vout=1)]) 137 138 # spending transaction is not found if we only search the mempool 139 result = node0.gettxspendingprevout([prevout(txid_root_utxo, vout=0), prevout(txidA, vout=1)], return_spending_tx=True, mempool_only=True) 140 assert_equal(result, [unspent_out(txid_root_utxo, vout=0), unspent_out(txidA, vout=1)]) 141 142 self.log.info("Check that our txospenderindex is updated when a reorg replaces a spending transaction") 143 reorg_replace_utxo = self.wallet.get_utxo(mark_as_spent=False) 144 txid_reorg_replace_utxo = reorg_replace_utxo['txid'] 145 146 tx1 = create_tx(utxos_to_spend=[reorg_replace_utxo], num_outputs=1) 147 blockhash = self.generate(self.wallet, 1)[0] 148 149 # tx1 is confirmed, and indexed in txospenderindex as spending our utxo 150 assert tx1["txid"] not in node0.getrawmempool() 151 result = node0.gettxspendingprevout([prevout(txid_reorg_replace_utxo, vout=0)], return_spending_tx=True) 152 assert_equal(result, [spent_out_in_block(txid_reorg_replace_utxo, vout=0, spending_tx_id=tx1["txid"], blockhash=blockhash, spending_tx=tx1['hex'])]) 153 154 # replace tx1 with tx2 triggering a "reorg" 155 best_block_hash = node0.getbestblockhash() 156 for node in self.nodes: 157 node.invalidateblock(best_block_hash) 158 assert tx1["txid"] in node.getrawmempool() 159 160 # create and submit replacement 161 tx2 = create_tx(utxos_to_spend=[reorg_replace_utxo], num_outputs=2) 162 assert tx2["txid"] in node0.getrawmempool() 163 164 # check that when we find tx2 when we look in the mempool for a tx spending our output 165 result = node0.gettxspendingprevout([prevout(txid_reorg_replace_utxo, vout=0)], return_spending_tx=True) 166 assert_equal(result, [ {'txid' : txid_reorg_replace_utxo, 'vout' : 0, 'spendingtxid' : tx2["txid"], 'spendingtx' : tx2['hex']} ]) 167 168 # check that our txospenderindex has been updated 169 blockhash = self.generate(self.wallet, 1)[0] 170 result = node0.gettxspendingprevout([prevout(txid_reorg_replace_utxo, vout=0)], return_spending_tx=True) 171 assert_equal(result, [spent_out_in_block(txid_reorg_replace_utxo, vout=0, spending_tx_id=tx2["txid"], blockhash=blockhash, spending_tx=tx2['hex'])]) 172 173 self.log.info("Check that our txospenderindex is updated when a reorg cancels a spending transaction") 174 reorg_cancel_utxo = self.wallet.get_utxo(mark_as_spent=False) 175 txid_reorg_cancel_utxo = reorg_cancel_utxo['txid'] 176 177 tx1 = create_tx(utxos_to_spend=[reorg_cancel_utxo], num_outputs=1) 178 tx2 = create_tx(utxos_to_spend=[tx1["new_utxos"][0]], num_outputs=1) 179 # tx1 spends our utxo, tx2 spends tx1 180 blockhash = self.generate(self.wallet, 1)[0] 181 # tx1 and tx2 are confirmed, and indexed in txospenderindex 182 result = node0.gettxspendingprevout([prevout(txid_reorg_cancel_utxo, vout=0)], return_spending_tx=True) 183 assert_equal(result, [spent_out_in_block(txid_reorg_cancel_utxo, vout=0, spending_tx_id=tx1["txid"], blockhash=blockhash, spending_tx=tx1['hex'])]) 184 result = node0.gettxspendingprevout([prevout(tx1['txid'], vout=0)], return_spending_tx=True) 185 assert_equal(result, [spent_out_in_block(tx1['txid'], vout=0, spending_tx_id=tx2["txid"], blockhash=blockhash, spending_tx=tx2['hex'])]) 186 187 # replace tx1 with tx3 188 blockhash = node0.getbestblockhash() 189 for node in self.nodes: 190 node.invalidateblock(blockhash) 191 192 tx3 = create_tx(utxos_to_spend=[reorg_cancel_utxo], num_outputs=2, fee_per_output=2000) 193 node0_mempool = node0.getrawmempool() 194 assert tx3["txid"] in node0_mempool 195 assert tx1["txid"] not in node0_mempool 196 assert tx2["txid"] not in node0_mempool 197 198 # tx2 is not in the mempool anymore, but still in txospender index which has not been rewound yet 199 result = node0.gettxspendingprevout([prevout(tx1['txid'], vout=0)], return_spending_tx=True) 200 assert_equal(result, [spent_out_in_block(tx1['txid'], vout=0, spending_tx_id=tx2["txid"], blockhash=blockhash, spending_tx=tx2['hex'])]) 201 202 txinfo = node0.getrawtransaction(tx2["txid"], verbose = True, blockhash = blockhash) 203 assert_equal(txinfo["confirmations"], 0) 204 assert_equal(txinfo["in_active_chain"], False) 205 206 blockhash = self.generate(self.wallet, 1)[0] 207 # we check that the spending tx for tx1 is now tx3 208 result = node0.gettxspendingprevout([prevout(txid_reorg_cancel_utxo, vout=0)], return_spending_tx=True) 209 assert_equal(result, [spent_out_in_block(txid_reorg_cancel_utxo, vout=0, spending_tx_id=tx3["txid"], blockhash=blockhash, spending_tx=tx3['hex'])]) 210 211 # we check that there is no more spending tx for tx1 212 result = node0.gettxspendingprevout([prevout(tx1['txid'], vout=0)], return_spending_tx=True) 213 assert_equal(result, [unspent_out(tx1['txid'], vout=0)]) 214 215 216 if __name__ == '__main__': 217 GetTxSpendingPrevoutTest(__file__).main()