/ test / functional / wallet_simulaterawtx.py
wallet_simulaterawtx.py
  1  #!/usr/bin/env python3
  2  # Copyright (c) 2021-present 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 simulaterawtransaction.
  6  """
  7  
  8  from decimal import Decimal
  9  from test_framework.blocktools import COINBASE_MATURITY
 10  from test_framework.test_framework import BitcoinTestFramework
 11  from test_framework.util import (
 12      assert_approx,
 13      assert_equal,
 14      assert_raises_rpc_error,
 15  )
 16  
 17  class SimulateTxTest(BitcoinTestFramework):
 18      def set_test_params(self):
 19          self.setup_clean_chain = True
 20          self.num_nodes = 1
 21  
 22      def skip_test_if_missing_module(self):
 23          self.skip_if_no_wallet()
 24  
 25      def setup_network(self, split=False):
 26          self.setup_nodes()
 27  
 28      def run_test(self):
 29          node = self.nodes[0]
 30  
 31          self.generate(node, 1, sync_fun=self.no_op) # Leave IBD
 32  
 33          node.createwallet(wallet_name='w0')
 34          node.createwallet(wallet_name='w1')
 35          node.createwallet(wallet_name='w2', disable_private_keys=True)
 36          w0 = node.get_wallet_rpc('w0')
 37          w1 = node.get_wallet_rpc('w1')
 38          w2 = node.get_wallet_rpc('w2')
 39  
 40          self.generatetoaddress(node, COINBASE_MATURITY + 1, w0.getnewaddress())
 41          assert_equal(w0.getbalance(), 50.0)
 42          assert_equal(w1.getbalance(), 0.0)
 43  
 44          address1 = w1.getnewaddress()
 45          address2 = w1.getnewaddress()
 46  
 47          # Add address1 as watch-only to w2
 48          import_res = w2.importdescriptors([{"desc": w1.getaddressinfo(address1)["desc"], "timestamp": "now"}])
 49          assert_equal(import_res[0]["success"], True)
 50  
 51          tx1 = node.createrawtransaction([], [{address1: 5.0}])
 52          tx2 = node.createrawtransaction([], [{address2: 10.0}])
 53  
 54          # w0 should be unaffected, w2 should see +5 for tx1
 55          assert_equal(w0.simulaterawtransaction([tx1])["balance_change"], 0.0)
 56          assert_equal(w2.simulaterawtransaction([tx1])["balance_change"], 5.0)
 57  
 58          # w1 should see +5 balance for tx1
 59          assert_equal(w1.simulaterawtransaction([tx1])["balance_change"], 5.0)
 60  
 61          # w0 should be unaffected, w2 should see +5 for both transactions
 62          assert_equal(w0.simulaterawtransaction([tx1, tx2])["balance_change"], 0.0)
 63          assert_equal(w2.simulaterawtransaction([tx1, tx2])["balance_change"], 5.0)
 64  
 65          # w1 should see +15 balance for both transactions
 66          assert_equal(w1.simulaterawtransaction([tx1, tx2])["balance_change"], 15.0)
 67  
 68          # w0 funds transaction; it should now see a decrease in (tx fee and payment), and w1 should see the same as above
 69          funding = w0.fundrawtransaction(tx1)
 70          tx1 = funding["hex"]
 71          tx1changepos = funding["changepos"]
 72          bitcoin_fee = Decimal(funding["fee"])
 73  
 74          # w0 sees fee + 5 btc decrease, w2 sees + 5 btc
 75          assert_approx(w0.simulaterawtransaction([tx1])["balance_change"], -(Decimal("5") + bitcoin_fee))
 76          assert_approx(w2.simulaterawtransaction([tx1])["balance_change"], Decimal("5"))
 77  
 78          # w1 sees same as before
 79          assert_equal(w1.simulaterawtransaction([tx1])["balance_change"], 5.0)
 80  
 81          # same inputs (tx) more than once should error
 82          assert_raises_rpc_error(-8, "Transaction(s) are spending the same output more than once", w0.simulaterawtransaction, [tx1,tx1])
 83  
 84          tx1ob = node.decoderawtransaction(tx1)
 85          tx1hex = tx1ob["txid"]
 86          tx1vout = 1 - tx1changepos
 87          # tx3 spends new w1 UTXO paying to w0
 88          tx3 = node.createrawtransaction([{"txid": tx1hex, "vout": tx1vout}], {w0.getnewaddress(): 4.9999})
 89          # tx4 spends new w1 UTXO paying to w1
 90          tx4 = node.createrawtransaction([{"txid": tx1hex, "vout": tx1vout}], {w1.getnewaddress(): 4.9999})
 91  
 92          # on their own, both should fail due to missing input(s)
 93          assert_raises_rpc_error(-8, "One or more transaction inputs are missing or have been spent already", w0.simulaterawtransaction, [tx3])
 94          assert_raises_rpc_error(-8, "One or more transaction inputs are missing or have been spent already", w1.simulaterawtransaction, [tx3])
 95          assert_raises_rpc_error(-8, "One or more transaction inputs are missing or have been spent already", w0.simulaterawtransaction, [tx4])
 96          assert_raises_rpc_error(-8, "One or more transaction inputs are missing or have been spent already", w1.simulaterawtransaction, [tx4])
 97  
 98          # they should succeed when including tx1:
 99          #       wallet                  tx3                             tx4
100          #       w0                      -5 - bitcoin_fee + 4.9999       -5 - bitcoin_fee
101          #       w1                      0                               +4.9999
102          assert_approx(w0.simulaterawtransaction([tx1, tx3])["balance_change"], -Decimal("5") - bitcoin_fee + Decimal("4.9999"))
103          assert_approx(w1.simulaterawtransaction([tx1, tx3])["balance_change"], 0)
104          assert_approx(w0.simulaterawtransaction([tx1, tx4])["balance_change"], -Decimal("5") - bitcoin_fee)
105          assert_approx(w1.simulaterawtransaction([tx1, tx4])["balance_change"], Decimal("4.9999"))
106  
107          # they should fail if attempting to include both tx3 and tx4
108          assert_raises_rpc_error(-8, "Transaction(s) are spending the same output more than once", w0.simulaterawtransaction, [tx1, tx3, tx4])
109          assert_raises_rpc_error(-8, "Transaction(s) are spending the same output more than once", w1.simulaterawtransaction, [tx1, tx3, tx4])
110  
111          # send tx1 to avoid reusing same UTXO below
112          node.sendrawtransaction(w0.signrawtransactionwithwallet(tx1)["hex"])
113          self.generate(node, 1, sync_fun=self.no_op) # Confirm tx to trigger error below
114          self.sync_all()
115  
116          # w0 funds transaction 2; it should now see a decrease in (tx fee and payment), and w1 should see the same as above
117          funding = w0.fundrawtransaction(tx2)
118          tx2 = funding["hex"]
119          bitcoin_fee2 = Decimal(funding["fee"])
120          assert_approx(w0.simulaterawtransaction([tx2])["balance_change"], -(Decimal("10") + bitcoin_fee2))
121          assert_approx(w1.simulaterawtransaction([tx2])["balance_change"], +(Decimal("10")))
122          assert_approx(w2.simulaterawtransaction([tx2])["balance_change"], 0)
123  
124          # w0-w2 error due to tx1 already being mined
125          assert_raises_rpc_error(-8, "One or more transaction inputs are missing or have been spent already", w0.simulaterawtransaction, [tx1, tx2])
126          assert_raises_rpc_error(-8, "One or more transaction inputs are missing or have been spent already", w1.simulaterawtransaction, [tx1, tx2])
127          assert_raises_rpc_error(-8, "One or more transaction inputs are missing or have been spent already", w2.simulaterawtransaction, [tx1, tx2])
128  
129  if __name__ == '__main__':
130      SimulateTxTest(__file__).main()