rpc_gettxspendingprevout.py raw

   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()
 218