4
4
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5
5
"""Test the wallet balance RPC methods."""
6
6
from decimal import Decimal
7
+ import struct
7
8
9
+ from test_framework .address import ADDRESS_BCRT1_UNSPENDABLE as ADDRESS_WATCHONLY
8
10
from test_framework .test_framework import BitcoinTestFramework
9
11
from test_framework .util import (
10
12
assert_equal ,
11
13
assert_raises_rpc_error ,
14
+ connect_nodes_bi ,
15
+ sync_blocks ,
12
16
)
13
17
14
- RANDOM_COINBASE_ADDRESS = 'mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ'
15
18
16
19
def create_transactions (node , address , amt , fees ):
17
20
# Create and sign raw transactions from node to address for amt.
18
21
# Creates a transaction for each fee and returns an array
19
22
# of the raw transactions.
20
- utxos = node .listunspent (0 )
23
+ utxos = [ u for u in node .listunspent (0 ) if u [ 'spendable' ]]
21
24
22
25
# Create transactions
23
26
inputs = []
24
27
ins_total = 0
25
28
for utxo in utxos :
26
29
inputs .append ({"txid" : utxo ["txid" ], "vout" : utxo ["vout" ]})
27
30
ins_total += utxo ['amount' ]
28
- if ins_total > amt :
31
+ if ins_total + max ( fees ) > amt :
29
32
break
30
33
31
34
txs = []
32
35
for fee in fees :
33
36
outputs = {address : amt , node .getrawchangeaddress (): ins_total - amt - fee }
34
37
raw_tx = node .createrawtransaction (inputs , outputs , 0 , True )
35
38
raw_tx = node .signrawtransactionwithwallet (raw_tx )
39
+ assert_equal (raw_tx ['complete' ], True )
36
40
txs .append (raw_tx )
37
41
38
42
return txs
@@ -41,21 +45,26 @@ class WalletTest(BitcoinTestFramework):
41
45
def set_test_params (self ):
42
46
self .num_nodes = 2
43
47
self .setup_clean_chain = True
48
+ self .extra_args = [
49
+ ['-limitdescendantcount=3' ], # Limit mempool descendants as a hack to have wallet txs rejected from the mempool
50
+ [],
51
+ ]
44
52
45
53
def skip_test_if_missing_module (self ):
46
54
self .skip_if_no_wallet ()
47
55
48
56
def run_test (self ):
57
+ self .nodes [0 ].importaddress (ADDRESS_WATCHONLY )
49
58
# Check that nodes don't own any UTXOs
50
59
assert_equal (len (self .nodes [0 ].listunspent ()), 0 )
51
60
assert_equal (len (self .nodes [1 ].listunspent ()), 0 )
52
61
53
- self .log .info ("Mining one block for each node " )
62
+ self .log .info ("Mining blocks ... " )
54
63
55
64
self .nodes [0 ].generate (1 )
56
65
self .sync_all ()
57
66
self .nodes [1 ].generate (1 )
58
- self .nodes [1 ].generatetoaddress (100 , RANDOM_COINBASE_ADDRESS )
67
+ self .nodes [1 ].generatetoaddress (101 , ADDRESS_WATCHONLY )
59
68
self .sync_all ()
60
69
61
70
assert_equal (self .nodes [0 ].getbalance (), 50 )
@@ -64,8 +73,10 @@ def run_test(self):
64
73
self .log .info ("Test getbalance with different arguments" )
65
74
assert_equal (self .nodes [0 ].getbalance ("*" ), 50 )
66
75
assert_equal (self .nodes [0 ].getbalance ("*" , 1 ), 50 )
67
- assert_equal (self .nodes [0 ].getbalance ("*" , 1 , True ), 50 )
76
+ assert_equal (self .nodes [0 ].getbalance ("*" , 1 , True ), 100 )
68
77
assert_equal (self .nodes [0 ].getbalance (minconf = 1 ), 50 )
78
+ assert_equal (self .nodes [0 ].getbalance (minconf = 0 , include_watchonly = True ), 100 )
79
+ assert_equal (self .nodes [1 ].getbalance (minconf = 0 , include_watchonly = True ), 50 )
69
80
70
81
# Send 40 BTC from 0 to 1 and 60 BTC from 1 to 0.
71
82
txs = create_transactions (self .nodes [0 ], self .nodes [1 ].getnewaddress (), 40 , [Decimal ('0.01' )])
@@ -83,32 +94,34 @@ def run_test(self):
83
94
84
95
self .log .info ("Test getbalance and getunconfirmedbalance with unconfirmed inputs" )
85
96
86
- # getbalance without any arguments includes unconfirmed transactions, but not untrusted transactions
87
- assert_equal (self .nodes [0 ].getbalance (), Decimal ('9.99' )) # change from node 0's send
88
- assert_equal (self .nodes [1 ].getbalance (), Decimal ('29.99' )) # change from node 1's send
89
- # Same with minconf=0
90
- assert_equal (self .nodes [0 ].getbalance (minconf = 0 ), Decimal ('9.99' ))
91
- assert_equal (self .nodes [1 ].getbalance (minconf = 0 ), Decimal ('29.99' ))
92
- # getbalance with a minconf incorrectly excludes coins that have been spent more recently than the minconf blocks ago
93
- # TODO: fix getbalance tracking of coin spentness depth
94
- assert_equal (self .nodes [0 ].getbalance (minconf = 1 ), Decimal ('0' ))
95
- assert_equal (self .nodes [1 ].getbalance (minconf = 1 ), Decimal ('0' ))
96
- # getunconfirmedbalance
97
- assert_equal (self .nodes [0 ].getunconfirmedbalance (), Decimal ('60' )) # output of node 1's spend
98
- assert_equal (self .nodes [1 ].getunconfirmedbalance (), Decimal ('0' )) # Doesn't include output of node 0's send since it was spent
97
+ def test_balances (* , fee_node_1 = 0 ):
98
+ # getbalance without any arguments includes unconfirmed transactions, but not untrusted transactions
99
+ assert_equal (self .nodes [0 ].getbalance (), Decimal ('9.99' )) # change from node 0's send
100
+ assert_equal (self .nodes [1 ].getbalance (), Decimal ('30' ) - fee_node_1 ) # change from node 1's send
101
+ # Same with minconf=0
102
+ assert_equal (self .nodes [0 ].getbalance (minconf = 0 ), Decimal ('9.99' ))
103
+ assert_equal (self .nodes [1 ].getbalance (minconf = 0 ), Decimal ('30' ) - fee_node_1 )
104
+ # getbalance with a minconf incorrectly excludes coins that have been spent more recently than the minconf blocks ago
105
+ # TODO: fix getbalance tracking of coin spentness depth
106
+ assert_equal (self .nodes [0 ].getbalance (minconf = 1 ), Decimal ('0' ))
107
+ assert_equal (self .nodes [1 ].getbalance (minconf = 1 ), Decimal ('0' ))
108
+ # getunconfirmedbalance
109
+ assert_equal (self .nodes [0 ].getunconfirmedbalance (), Decimal ('60' )) # output of node 1's spend
110
+ assert_equal (self .nodes [0 ].getwalletinfo ()["unconfirmed_balance" ], Decimal ('60' ))
111
+ assert_equal (self .nodes [1 ].getunconfirmedbalance (), Decimal ('0' )) # Doesn't include output of node 0's send since it was spent
112
+ assert_equal (self .nodes [1 ].getwalletinfo ()["unconfirmed_balance" ], Decimal ('0' ))
113
+
114
+ test_balances (fee_node_1 = Decimal ('0.01' ))
99
115
100
116
# Node 1 bumps the transaction fee and resends
101
117
self .nodes [1 ].sendrawtransaction (txs [1 ]['hex' ])
118
+ self .nodes [0 ].sendrawtransaction (txs [1 ]['hex' ]) # sending on both nodes is faster than waiting for propagation
102
119
self .sync_all ()
103
120
104
121
self .log .info ("Test getbalance and getunconfirmedbalance with conflicted unconfirmed inputs" )
122
+ test_balances (fee_node_1 = Decimal ('0.02' ))
105
123
106
- assert_equal (self .nodes [0 ].getwalletinfo ()["unconfirmed_balance" ], Decimal ('60' )) # output of node 1's send
107
- assert_equal (self .nodes [0 ].getunconfirmedbalance (), Decimal ('60' ))
108
- assert_equal (self .nodes [1 ].getwalletinfo ()["unconfirmed_balance" ], Decimal ('0' )) # Doesn't include output of node 0's send since it was spent
109
- assert_equal (self .nodes [1 ].getunconfirmedbalance (), Decimal ('0' ))
110
-
111
- self .nodes [1 ].generatetoaddress (1 , RANDOM_COINBASE_ADDRESS )
124
+ self .nodes [1 ].generatetoaddress (1 , ADDRESS_WATCHONLY )
112
125
self .sync_all ()
113
126
114
127
# balances are correct after the transactions are confirmed
@@ -118,7 +131,7 @@ def run_test(self):
118
131
# Send total balance away from node 1
119
132
txs = create_transactions (self .nodes [1 ], self .nodes [0 ].getnewaddress (), Decimal ('29.97' ), [Decimal ('0.01' )])
120
133
self .nodes [1 ].sendrawtransaction (txs [0 ]['hex' ])
121
- self .nodes [1 ].generatetoaddress (2 , RANDOM_COINBASE_ADDRESS )
134
+ self .nodes [1 ].generatetoaddress (2 , ADDRESS_WATCHONLY )
122
135
self .sync_all ()
123
136
124
137
# getbalance with a minconf incorrectly excludes coins that have been spent more recently than the minconf blocks ago
@@ -140,6 +153,51 @@ def run_test(self):
140
153
after = self .nodes [1 ].getunconfirmedbalance ()
141
154
assert_equal (before + Decimal ('0.1' ), after )
142
155
156
+ # Create 3 more wallet txs, where the last is not accepted to the
157
+ # mempool because it is the third descendant of the tx above
158
+ for _ in range (3 ):
159
+ # Set amount high enough such that all coins are spent by each tx
160
+ txid = self .nodes [0 ].sendtoaddress (self .nodes [0 ].getnewaddress (), 99 )
161
+
162
+ self .log .info ('Check that wallet txs not in the mempool are untrusted' )
163
+ assert txid not in self .nodes [0 ].getrawmempool ()
164
+ assert_equal (self .nodes [0 ].gettransaction (txid )['trusted' ], False )
165
+ assert_equal (self .nodes [0 ].getbalance (minconf = 0 ), 0 )
166
+
167
+ self .log .info ("Test replacement and reorg of non-mempool tx" )
168
+ tx_orig = self .nodes [0 ].gettransaction (txid )['hex' ]
169
+ # Increase fee by 1 coin
170
+ tx_replace = tx_orig .replace (
171
+ struct .pack ("<q" , 99 * 10 ** 8 ).hex (),
172
+ struct .pack ("<q" , 98 * 10 ** 8 ).hex (),
173
+ )
174
+ tx_replace = self .nodes [0 ].signrawtransactionwithwallet (tx_replace )['hex' ]
175
+ # Total balance is given by the sum of outputs of the tx
176
+ total_amount = sum ([o ['value' ] for o in self .nodes [0 ].decoderawtransaction (tx_replace )['vout' ]])
177
+ self .sync_all ()
178
+ self .nodes [1 ].sendrawtransaction (hexstring = tx_replace , maxfeerate = 0 )
179
+
180
+ # Now confirm tx_replace
181
+ block_reorg = self .nodes [1 ].generatetoaddress (1 , ADDRESS_WATCHONLY )[0 ]
182
+ self .sync_all ()
183
+ assert_equal (self .nodes [0 ].getbalance (minconf = 0 ), total_amount )
184
+
185
+ self .log .info ('Put txs back into mempool of node 1 (not node 0)' )
186
+ self .nodes [0 ].invalidateblock (block_reorg )
187
+ self .nodes [1 ].invalidateblock (block_reorg )
188
+ assert_equal (self .nodes [0 ].getbalance (minconf = 0 ), 0 ) # wallet txs not in the mempool are untrusted
189
+ self .nodes [0 ].generatetoaddress (1 , ADDRESS_WATCHONLY )
190
+ assert_equal (self .nodes [0 ].getbalance (minconf = 0 ), 0 ) # wallet txs not in the mempool are untrusted
191
+
192
+ # Now confirm tx_orig
193
+ self .restart_node (1 , ['-persistmempool=0' ])
194
+ connect_nodes_bi (self .nodes , 0 , 1 )
195
+ sync_blocks (self .nodes )
196
+ self .nodes [1 ].sendrawtransaction (tx_orig )
197
+ self .nodes [1 ].generatetoaddress (1 , ADDRESS_WATCHONLY )
198
+ self .sync_all ()
199
+ assert_equal (self .nodes [0 ].getbalance (minconf = 0 ), total_amount + 1 ) # The reorg recovered our fee of 1 coin
200
+
143
201
144
202
if __name__ == '__main__' :
145
203
WalletTest ().main ()
0 commit comments