1 #!/usr/bin/env python3
2 # Copyright (c) 2018-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 the wallet balance RPC methods."""
6 from decimal import Decimal
7 8 from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE as ADDRESS_WATCHONLY
9 from test_framework.blocktools import COINBASE_MATURITY
10 from test_framework.test_framework import LimenkaTestFramework
11 from test_framework.util import (
12 assert_equal,
13 assert_is_hash_string,
14 assert_raises_rpc_error,
15 )
16 17 18 def create_transactions(node, address, amt, fees):
19 # Create and sign raw transactions from node to address for amt.
20 # Creates a transaction for each fee and returns an array
21 # of the raw transactions.
22 utxos = [u for u in node.listunspent(0) if u['spendable']]
23 24 # Create transactions
25 inputs = []
26 ins_total = 0
27 for utxo in utxos:
28 inputs.append({"txid": utxo["txid"], "vout": utxo["vout"]})
29 ins_total += utxo['amount']
30 if ins_total >= amt + max(fees):
31 break
32 # make sure there was enough utxos
33 assert ins_total >= amt + max(fees)
34 35 txs = []
36 for fee in fees:
37 outputs = {address: amt}
38 # prevent 0 change output
39 if ins_total > amt + fee:
40 outputs[node.getrawchangeaddress()] = ins_total - amt - fee
41 raw_tx = node.createrawtransaction(inputs, outputs, 0, True)
42 raw_tx = node.signrawtransactionwithwallet(raw_tx)
43 assert_equal(raw_tx['complete'], True)
44 txs.append(raw_tx)
45 46 return txs
47 48 class WalletTest(LimenkaTestFramework):
49 def add_options(self, parser):
50 self.add_wallet_options(parser)
51 52 def set_test_params(self):
53 self.num_nodes = 2
54 self.setup_clean_chain = True
55 # whitelist peers to speed up tx relay / mempool sync
56 self.noban_tx_relay = True
57 self.extra_args = [
58 # Limit mempool descendants as a hack to have wallet txs rejected from the mempool.
59 # Set walletrejectlongchains=0 so the wallet still creates the transactions.
60 ['-limitdescendantcount=3', '-walletrejectlongchains=0'],
61 [],
62 ]
63 64 def skip_test_if_missing_module(self):
65 self.skip_if_no_wallet()
66 67 def run_test(self):
68 if not self.options.descriptors:
69 # Tests legacy watchonly behavior which is not present (and does not need to be tested) in descriptor wallets
70 self.nodes[0].importaddress(ADDRESS_WATCHONLY)
71 # Check that nodes don't own any UTXOs
72 assert_equal(len(self.nodes[0].listunspent()), 0)
73 assert_equal(len(self.nodes[1].listunspent()), 0)
74 75 self.log.info("Check that only node 0 is watching an address")
76 assert 'watchonly' in self.nodes[0].getbalances()
77 assert 'watchonly' not in self.nodes[1].getbalances()
78 79 self.log.info("Mining blocks ...")
80 self.generate(self.nodes[0], 1)
81 self.generate(self.nodes[1], 1)
82 83 # Verify listunspent returns immature coinbase if 'include_immature_coinbase' is set
84 assert_equal(len(self.nodes[0].listunspent(query_options={'include_immature_coinbase': True})), 1)
85 assert_equal(len(self.nodes[0].listunspent(query_options={'include_immature_coinbase': False})), 0)
86 87 self.generatetoaddress(self.nodes[1], COINBASE_MATURITY + 1, ADDRESS_WATCHONLY)
88 89 # Verify listunspent returns all immature coinbases if 'include_immature_coinbase' is set
90 # For now, only the legacy wallet will see the coinbases going to the imported 'ADDRESS_WATCHONLY'
91 assert_equal(len(self.nodes[0].listunspent(query_options={'include_immature_coinbase': False})), 1 if self.options.descriptors else 2)
92 assert_equal(len(self.nodes[0].listunspent(query_options={'include_immature_coinbase': True})), 1 if self.options.descriptors else COINBASE_MATURITY + 2)
93 94 if not self.options.descriptors:
95 # Tests legacy watchonly behavior which is not present (and does not need to be tested) in descriptor wallets
96 assert_equal(self.nodes[0].getbalances()['mine']['trusted'], 50)
97 assert_equal(self.nodes[0].getwalletinfo()['balance'], 50)
98 assert_equal(self.nodes[1].getbalances()['mine']['trusted'], 50)
99 100 assert_equal(self.nodes[0].getbalances()['watchonly']['immature'], 5000)
101 assert 'watchonly' not in self.nodes[1].getbalances()
102 103 assert_equal(self.nodes[0].getbalance(), 50)
104 assert_equal(self.nodes[1].getbalance(), 50)
105 106 self.log.info("Test getbalance with different arguments")
107 assert_equal(self.nodes[0].getbalance("*"), 50)
108 assert_equal(self.nodes[0].getbalance("*", 1), 50)
109 assert_raises_rpc_error(-8, "getbalance minconf option is only currently supported if dummy is set to \"*\"", self.nodes[0].getbalance, minconf=1)
110 assert_raises_rpc_error(-8, "getbalance minconf option is only currently supported if dummy is set to \"*\"", self.nodes[0].getbalance, minconf=0, include_watchonly=True)
111 if not self.options.descriptors:
112 assert_equal(self.nodes[0].getbalance("*", 1, True), 100)
113 else:
114 assert_equal(self.nodes[0].getbalance("*", 1, True), 50)
115 assert_raises_rpc_error(-8, "getbalance minconf option is only currently supported if dummy is set to \"*\"", self.nodes[1].getbalance, minconf=0, include_watchonly=True)
116 117 # Send 40 BTC from 0 to 1 and 60 BTC from 1 to 0.
118 txs = create_transactions(self.nodes[0], self.nodes[1].getnewaddress(), 40, [Decimal('0.01')])
119 self.nodes[0].sendrawtransaction(txs[0]['hex'])
120 self.nodes[1].sendrawtransaction(txs[0]['hex']) # sending on both nodes is faster than waiting for propagation
121 122 self.sync_all()
123 txs = create_transactions(self.nodes[1], self.nodes[0].getnewaddress(), 60, [Decimal('0.01'), Decimal('0.02')])
124 self.nodes[1].sendrawtransaction(txs[0]['hex'])
125 self.nodes[0].sendrawtransaction(txs[0]['hex']) # sending on both nodes is faster than waiting for propagation
126 self.sync_all()
127 128 # First argument of getbalance must be set to "*"
129 assert_raises_rpc_error(-32, "dummy first argument must be excluded or set to \"*\"", self.nodes[1].getbalance, "")
130 131 self.log.info("Test balances with unconfirmed inputs")
132 133 # Before `test_balance()`, we have had two nodes with a balance of 50
134 # each and then we:
135 #
136 # 1) Sent 40 from node A to node B with fee 0.01
137 # 2) Sent 60 from node B to node A with fee 0.01
138 #
139 # Then we check the balances:
140 #
141 # 1) As is
142 # 2) With transaction 2 from above with 2x the fee
143 #
144 # Prior to #16766, in this situation, the node would immediately report
145 # a balance of 30 on node B as unconfirmed and trusted.
146 #
147 # After #16766, we show that balance as unconfirmed.
148 #
149 # The balance is indeed "trusted" and "confirmed" insofar as removing
150 # the mempool transactions would return at least that much money. But
151 # the algorithm after #16766 marks it as unconfirmed because the 'taint'
152 # tracking of transaction trust for summing balances doesn't consider
153 # which inputs belong to a user. In this case, the change output in
154 # question could be "destroyed" by replace the 1st transaction above.
155 #
156 # The post #16766 behavior is correct; we shouldn't be treating those
157 # funds as confirmed. If you want to rely on that specific UTXO existing
158 # which has given you that balance, you cannot, as a third party
159 # spending the other input would destroy that unconfirmed.
160 #
161 # For example, if the test transactions were:
162 #
163 # 1) Sent 40 from node A to node B with fee 0.01
164 # 2) Sent 10 from node B to node A with fee 0.01
165 #
166 # Then our node would report a confirmed balance of 40 + 50 - 10 = 80
167 # BTC, which is more than would be available if transaction 1 were
168 # replaced.
169 170 171 def test_balances(*, fee_node_1=0):
172 # getbalances
173 expected_balances_0 = {'mine': {'immature': Decimal('0E-8'),
174 'trusted': Decimal('9.99'), # change from node 0's send
175 'untrusted_pending': Decimal('60.0')},
176 'watchonly': {'immature': Decimal('5000'),
177 'trusted': Decimal('50.0'),
178 'untrusted_pending': Decimal('0E-8')}}
179 expected_balances_1 = {'mine': {'immature': Decimal('0E-8'),
180 'trusted': Decimal('0E-8'), # node 1's send had an unsafe input
181 'untrusted_pending': Decimal('30.0') - fee_node_1}} # Doesn't include output of node 0's send since it was spent
182 if self.options.descriptors:
183 del expected_balances_0["watchonly"]
184 balances_0 = self.nodes[0].getbalances()
185 balances_1 = self.nodes[1].getbalances()
186 # remove lastprocessedblock keys (they will be tested later)
187 del balances_0['lastprocessedblock']
188 del balances_1['lastprocessedblock']
189 assert_equal(balances_0, expected_balances_0)
190 assert_equal(balances_1, expected_balances_1)
191 # getbalance without any arguments includes unconfirmed transactions, but not untrusted transactions
192 assert_equal(self.nodes[0].getbalance(), Decimal('9.99')) # change from node 0's send
193 assert_equal(self.nodes[1].getbalance(), Decimal('0')) # node 1's send had an unsafe input
194 # getbalance with '*' and minconf=0 includes unconfirmed transactions, AND untrusted transactions
195 assert_equal(self.nodes[0].getbalance('*', 0), Decimal('69.99'))
196 assert_equal(self.nodes[1].getbalance('*', 0), Decimal('30') - fee_node_1)
197 # getbalance with '*' and minconf=1 includes only confirmed and sent transactions
198 assert_equal(self.nodes[0].getbalance('*', 1), Decimal('9.99'))
199 assert_equal(self.nodes[1].getbalance('*', 1), Decimal('-10') - fee_node_1)
200 # getunconfirmedbalance
201 assert_equal(self.nodes[0].getunconfirmedbalance(), Decimal('60')) # output of node 1's spend
202 assert_equal(self.nodes[1].getunconfirmedbalance(), Decimal('30') - fee_node_1) # Doesn't include output of node 0's send since it was spent
203 # getwalletinfo.unconfirmed_balance
204 assert_equal(self.nodes[0].getwalletinfo()["unconfirmed_balance"], Decimal('60'))
205 assert_equal(self.nodes[1].getwalletinfo()["unconfirmed_balance"], Decimal('30') - fee_node_1)
206 207 test_balances(fee_node_1=Decimal('0.01'))
208 209 # Node 1 bumps the transaction fee and resends
210 self.nodes[1].sendrawtransaction(txs[1]['hex'])
211 self.nodes[0].sendrawtransaction(txs[1]['hex']) # sending on both nodes is faster than waiting for propagation
212 self.sync_all()
213 214 self.log.info("Test getbalance and getbalances.mine.untrusted_pending with conflicted unconfirmed inputs")
215 test_balances(fee_node_1=Decimal('0.02'))
216 217 self.generatetoaddress(self.nodes[1], 1, ADDRESS_WATCHONLY)
218 219 # balances are correct after the transactions are confirmed
220 balance_node0 = Decimal('69.99') # node 1's send plus change from node 0's send
221 balance_node1 = Decimal('29.98') # change from node 0's send
222 assert_equal(self.nodes[0].getbalances()['mine']['trusted'], balance_node0)
223 assert_equal(self.nodes[1].getbalances()['mine']['trusted'], balance_node1)
224 assert_equal(self.nodes[0].getbalance(), balance_node0)
225 assert_equal(self.nodes[1].getbalance(), balance_node1)
226 227 # Send total balance away from node 1
228 txs = create_transactions(self.nodes[1], self.nodes[0].getnewaddress(), Decimal('29.97'), [Decimal('0.01')])
229 self.nodes[1].sendrawtransaction(txs[0]['hex'])
230 self.generatetoaddress(self.nodes[1], 2, ADDRESS_WATCHONLY)
231 232 # check mempool transactions count for wallet unconfirmed balance after
233 # dynamically loading the wallet.
234 before = self.nodes[1].getbalances()['mine']['untrusted_pending']
235 dst = self.nodes[1].getnewaddress()
236 self.nodes[1].unloadwallet(self.default_wallet_name)
237 self.nodes[0].sendtoaddress(dst, 0.1)
238 self.sync_all()
239 self.nodes[1].loadwallet(self.default_wallet_name)
240 after = self.nodes[1].getbalances()['mine']['untrusted_pending']
241 assert_equal(before + Decimal('0.1'), after)
242 243 # Create 3 more wallet txs, where the last is not accepted to the
244 # mempool because it is the third descendant of the tx above
245 for _ in range(3):
246 # Set amount high enough such that all coins are spent by each tx
247 txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 99)
248 249 self.log.info('Check that wallet txs not in the mempool are untrusted')
250 assert txid not in self.nodes[0].getrawmempool()
251 assert_equal(self.nodes[0].gettransaction(txid)['trusted'], False)
252 assert_equal(self.nodes[0].getbalances()['mine']['trusted'], 0)
253 254 self.log.info("Test replacement and reorg of non-mempool tx")
255 tx_orig = self.nodes[0].gettransaction(txid)['hex']
256 # Increase fee by 1 coin
257 tx_replace = tx_orig.replace(
258 (99 * 10**8).to_bytes(8, "little", signed=True).hex(),
259 (98 * 10**8).to_bytes(8, "little", signed=True).hex(),
260 )
261 tx_replace = self.nodes[0].signrawtransactionwithwallet(tx_replace)['hex']
262 # Total balance is given by the sum of outputs of the tx
263 total_amount = sum([o['value'] for o in self.nodes[0].decoderawtransaction(tx_replace)['vout']])
264 self.sync_all()
265 self.nodes[1].sendrawtransaction(hexstring=tx_replace, maxfeerate=0)
266 267 # Now confirm tx_replace
268 block_reorg = self.generatetoaddress(self.nodes[1], 1, ADDRESS_WATCHONLY)[0]
269 assert_equal(self.nodes[0].getbalances()['mine']['trusted'], total_amount)
270 271 self.log.info('Put txs back into mempool of node 1 (not node 0)')
272 self.nodes[0].invalidateblock(block_reorg)
273 self.nodes[1].invalidateblock(block_reorg)
274 assert_equal(self.nodes[0].getbalances()['mine']['trusted'], 0) # wallet txs not in the mempool are untrusted
275 self.generatetoaddress(self.nodes[0], 1, ADDRESS_WATCHONLY, sync_fun=self.no_op)
276 277 # Now confirm tx_orig
278 self.restart_node(1, ['-persistmempool=0'])
279 self.connect_nodes(0, 1)
280 self.sync_blocks()
281 self.nodes[1].sendrawtransaction(tx_orig)
282 self.generatetoaddress(self.nodes[1], 1, ADDRESS_WATCHONLY)
283 assert_equal(self.nodes[0].getbalances()['mine']['trusted'], total_amount + 1) # The reorg recovered our fee of 1 coin
284 285 if not self.options.descriptors:
286 self.log.info('Check if mempool is taken into account after import*')
287 address = self.nodes[0].getnewaddress()
288 privkey = self.nodes[0].dumpprivkey(address)
289 self.nodes[0].sendtoaddress(address, 0.1)
290 self.nodes[0].unloadwallet('')
291 # check importaddress on fresh wallet
292 self.nodes[0].createwallet('w1', False, True)
293 self.nodes[0].importaddress(address)
294 assert_equal(self.nodes[0].getbalances()['mine']['untrusted_pending'], 0)
295 assert_equal(self.nodes[0].getbalances()['watchonly']['untrusted_pending'], Decimal('0.1'))
296 self.nodes[0].importprivkey(privkey)
297 assert_equal(self.nodes[0].getbalances()['mine']['untrusted_pending'], Decimal('0.1'))
298 assert_equal(self.nodes[0].getbalances()['watchonly']['untrusted_pending'], 0)
299 self.nodes[0].unloadwallet('w1')
300 # check importprivkey on fresh wallet
301 self.nodes[0].createwallet('w2', False, True)
302 self.nodes[0].importprivkey(privkey)
303 assert_equal(self.nodes[0].getbalances()['mine']['untrusted_pending'], Decimal('0.1'))
304 305 306 # Tests the lastprocessedblock JSON object in getbalances, getwalletinfo
307 # and gettransaction by checking for valid hex strings and by comparing
308 # the hashes & heights between generated blocks.
309 self.log.info("Test getbalances returns expected lastprocessedblock json object")
310 prev_hash = self.nodes[0].getbestblockhash()
311 prev_height = self.nodes[0].getblock(prev_hash)['height']
312 self.generatetoaddress(self.nodes[0], 5, self.nodes[0].get_deterministic_priv_key().address)
313 lastblock = self.nodes[0].getbalances()['lastprocessedblock']
314 assert_is_hash_string(lastblock['hash'])
315 assert_equal((prev_hash == lastblock['hash']), False)
316 assert_equal(lastblock['height'], prev_height + 5)
317 318 prev_hash = self.nodes[0].getbestblockhash()
319 prev_height = self.nodes[0].getblock(prev_hash)['height']
320 self.log.info("Test getwalletinfo returns expected lastprocessedblock json object")
321 walletinfo = self.nodes[0].getwalletinfo()
322 assert_equal(walletinfo['lastprocessedblock']['height'], prev_height)
323 assert_equal(walletinfo['lastprocessedblock']['hash'], prev_hash)
324 325 self.log.info("Test gettransaction returns expected lastprocessedblock json object")
326 txid = self.nodes[1].sendtoaddress(self.nodes[1].getnewaddress(), 0.01)
327 tx_info = self.nodes[1].gettransaction(txid)
328 assert_equal(tx_info['lastprocessedblock']['height'], prev_height)
329 assert_equal(tx_info['lastprocessedblock']['hash'], prev_hash)
330 331 if __name__ == '__main__':
332 WalletTest(__file__).main()
333