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