@@ -29,70 +29,76 @@ def skip_test_if_missing_module(self):
29
29
self .skip_if_no_wallet ()
30
30
31
31
def run_test (self ):
32
+ # create two wallets to tests conflicts from both sender's and receiver's sides
33
+ alice = self .nodes [0 ].get_wallet_rpc (self .default_wallet_name )
34
+ self .nodes [0 ].createwallet (wallet_name = "bob" )
35
+ bob = self .nodes [0 ].get_wallet_rpc ("bob" )
36
+
32
37
self .generate (self .nodes [1 ], COINBASE_MATURITY )
33
- balance = self . nodes [ 0 ] .getbalance ()
34
- txA = self . nodes [ 0 ]. sendtoaddress (self . nodes [ 0 ] .getnewaddress (), Decimal ("10" ))
35
- txB = self . nodes [ 0 ]. sendtoaddress (self . nodes [ 0 ] .getnewaddress (), Decimal ("10" ))
36
- txC = self . nodes [ 0 ]. sendtoaddress (self . nodes [ 0 ] .getnewaddress (), Decimal ("10" ))
38
+ balance = alice .getbalance ()
39
+ txA = alice . sendtoaddress (alice .getnewaddress (), Decimal ("10" ))
40
+ txB = alice . sendtoaddress (alice .getnewaddress (), Decimal ("10" ))
41
+ txC = alice . sendtoaddress (alice .getnewaddress (), Decimal ("10" ))
37
42
self .sync_mempools ()
38
43
self .generate (self .nodes [1 ], 1 )
39
44
40
45
# Can not abandon non-wallet transaction
41
- assert_raises_rpc_error (- 5 , 'Invalid or non-wallet transaction id' , lambda : self . nodes [ 0 ] .abandontransaction (txid = 'ff' * 32 ))
46
+ assert_raises_rpc_error (- 5 , 'Invalid or non-wallet transaction id' , lambda : alice .abandontransaction (txid = 'ff' * 32 ))
42
47
# Can not abandon confirmed transaction
43
- assert_raises_rpc_error (- 5 , 'Transaction not eligible for abandonment' , lambda : self . nodes [ 0 ] .abandontransaction (txid = txA ))
48
+ assert_raises_rpc_error (- 5 , 'Transaction not eligible for abandonment' , lambda : alice .abandontransaction (txid = txA ))
44
49
45
- newbalance = self . nodes [ 0 ] .getbalance ()
50
+ newbalance = alice .getbalance ()
46
51
assert balance - newbalance < Decimal ("0.001" ) #no more than fees lost
47
52
balance = newbalance
48
53
49
54
# Disconnect nodes so node0's transactions don't get into node1's mempool
50
55
self .disconnect_nodes (0 , 1 )
51
56
52
57
# Identify the 10btc outputs
53
- nA = next (tx_out ["vout" ] for tx_out in self . nodes [ 0 ] .gettransaction (txA )["details" ] if tx_out ["amount" ] == Decimal ("10" ))
54
- nB = next (tx_out ["vout" ] for tx_out in self . nodes [ 0 ] .gettransaction (txB )["details" ] if tx_out ["amount" ] == Decimal ("10" ))
55
- nC = next (tx_out ["vout" ] for tx_out in self . nodes [ 0 ] .gettransaction (txC )["details" ] if tx_out ["amount" ] == Decimal ("10" ))
58
+ nA = next (tx_out ["vout" ] for tx_out in alice .gettransaction (txA )["details" ] if tx_out ["amount" ] == Decimal ("10" ))
59
+ nB = next (tx_out ["vout" ] for tx_out in alice .gettransaction (txB )["details" ] if tx_out ["amount" ] == Decimal ("10" ))
60
+ nC = next (tx_out ["vout" ] for tx_out in alice .gettransaction (txC )["details" ] if tx_out ["amount" ] == Decimal ("10" ))
56
61
57
62
inputs = []
58
63
# spend 10btc outputs from txA and txB
59
64
inputs .append ({"txid" : txA , "vout" : nA })
60
65
inputs .append ({"txid" : txB , "vout" : nB })
61
66
outputs = {}
62
67
63
- outputs [self . nodes [ 0 ] .getnewaddress ()] = Decimal ("14.99998" )
64
- outputs [self . nodes [ 1 ] .getnewaddress ()] = Decimal ("5" )
65
- signed = self . nodes [ 0 ]. signrawtransactionwithwallet (self . nodes [ 0 ] .createrawtransaction (inputs , outputs ))
68
+ outputs [alice .getnewaddress ()] = Decimal ("14.99998" )
69
+ outputs [bob .getnewaddress ()] = Decimal ("5" )
70
+ signed = alice . signrawtransactionwithwallet (alice .createrawtransaction (inputs , outputs ))
66
71
txAB1 = self .nodes [0 ].sendrawtransaction (signed ["hex" ])
67
72
68
73
# Identify the 14.99998btc output
69
- nAB = next (tx_out ["vout" ] for tx_out in self . nodes [ 0 ] .gettransaction (txAB1 )["details" ] if tx_out ["amount" ] == Decimal ("14.99998" ))
74
+ nAB = next (tx_out ["vout" ] for tx_out in alice .gettransaction (txAB1 )["details" ] if tx_out ["amount" ] == Decimal ("14.99998" ))
70
75
71
76
#Create a child tx spending AB1 and C
72
77
inputs = []
73
78
inputs .append ({"txid" : txAB1 , "vout" : nAB })
74
79
inputs .append ({"txid" : txC , "vout" : nC })
75
80
outputs = {}
76
- outputs [self . nodes [ 0 ] .getnewaddress ()] = Decimal ("24.9996" )
77
- signed2 = self . nodes [ 0 ]. signrawtransactionwithwallet (self . nodes [ 0 ] .createrawtransaction (inputs , outputs ))
81
+ outputs [alice .getnewaddress ()] = Decimal ("24.9996" )
82
+ signed2 = alice . signrawtransactionwithwallet (alice .createrawtransaction (inputs , outputs ))
78
83
txABC2 = self .nodes [0 ].sendrawtransaction (signed2 ["hex" ])
79
84
80
85
# Create a child tx spending ABC2
81
86
signed3_change = Decimal ("24.999" )
82
87
inputs = [{"txid" : txABC2 , "vout" : 0 }]
83
- outputs = {self . nodes [ 0 ] .getnewaddress (): signed3_change }
84
- signed3 = self . nodes [ 0 ]. signrawtransactionwithwallet (self . nodes [ 0 ] .createrawtransaction (inputs , outputs ))
88
+ outputs = {alice .getnewaddress (): signed3_change }
89
+ signed3 = alice . signrawtransactionwithwallet (alice .createrawtransaction (inputs , outputs ))
85
90
# note tx is never directly referenced, only abandoned as a child of the above
86
91
self .nodes [0 ].sendrawtransaction (signed3 ["hex" ])
87
92
88
93
# In mempool txs from self should increase balance from change
89
- newbalance = self . nodes [ 0 ] .getbalance ()
94
+ newbalance = alice .getbalance ()
90
95
assert_equal (newbalance , balance - Decimal ("30" ) + signed3_change )
91
96
balance = newbalance
92
97
93
98
# Restart the node with a higher min relay fee so the parent tx is no longer in mempool
94
99
# TODO: redo with eviction
95
100
self .restart_node (0 , extra_args = ["-minrelaytxfee=0.0001" ])
101
+ alice = self .nodes [0 ].get_wallet_rpc (self .default_wallet_name )
96
102
assert self .nodes [0 ].getmempoolinfo ()['loaded' ]
97
103
98
104
# Verify txs no longer in either node's mempool
@@ -101,25 +107,25 @@ def run_test(self):
101
107
102
108
# Not in mempool txs from self should only reduce balance
103
109
# inputs are still spent, but change not received
104
- newbalance = self . nodes [ 0 ] .getbalance ()
110
+ newbalance = alice .getbalance ()
105
111
assert_equal (newbalance , balance - signed3_change )
106
112
# Unconfirmed received funds that are not in mempool, also shouldn't show
107
113
# up in unconfirmed balance
108
- balances = self . nodes [ 0 ] .getbalances ()['mine' ]
114
+ balances = alice .getbalances ()['mine' ]
109
115
assert_equal (balances ['untrusted_pending' ] + balances ['trusted' ], newbalance )
110
116
# Also shouldn't show up in listunspent
111
- assert not txABC2 in [utxo ["txid" ] for utxo in self . nodes [ 0 ] .listunspent (0 )]
117
+ assert not txABC2 in [utxo ["txid" ] for utxo in alice .listunspent (0 )]
112
118
balance = newbalance
113
119
114
120
# Abandon original transaction and verify inputs are available again
115
121
# including that the child tx was also abandoned
116
- self . nodes [ 0 ] .abandontransaction (txAB1 )
117
- newbalance = self . nodes [ 0 ] .getbalance ()
122
+ alice .abandontransaction (txAB1 )
123
+ newbalance = alice .getbalance ()
118
124
assert_equal (newbalance , balance + Decimal ("30" ))
119
125
balance = newbalance
120
126
121
127
self .log .info ("Check abandoned transactions in listsinceblock" )
122
- listsinceblock = self . nodes [ 0 ] .listsinceblock ()
128
+ listsinceblock = alice .listsinceblock ()
123
129
txAB1_listsinceblock = [d for d in listsinceblock ['transactions' ] if d ['txid' ] == txAB1 and d ['category' ] == 'send' ]
124
130
for tx in txAB1_listsinceblock :
125
131
assert_equal (tx ['abandoned' ], True )
@@ -128,49 +134,53 @@ def run_test(self):
128
134
129
135
# Verify that even with a low min relay fee, the tx is not reaccepted from wallet on startup once abandoned
130
136
self .restart_node (0 , extra_args = ["-minrelaytxfee=0.00001" ])
137
+ alice = self .nodes [0 ].get_wallet_rpc (self .default_wallet_name )
131
138
assert self .nodes [0 ].getmempoolinfo ()['loaded' ]
132
139
133
140
assert_equal (len (self .nodes [0 ].getrawmempool ()), 0 )
134
- assert_equal (self . nodes [ 0 ] .getbalance (), balance )
141
+ assert_equal (alice .getbalance (), balance )
135
142
136
143
# But if it is received again then it is unabandoned
137
144
# And since now in mempool, the change is available
138
145
# But its child tx remains abandoned
139
146
self .nodes [0 ].sendrawtransaction (signed ["hex" ])
140
- newbalance = self . nodes [ 0 ] .getbalance ()
147
+ newbalance = alice .getbalance ()
141
148
assert_equal (newbalance , balance - Decimal ("20" ) + Decimal ("14.99998" ))
142
149
balance = newbalance
143
150
144
151
# Send child tx again so it is unabandoned
145
152
self .nodes [0 ].sendrawtransaction (signed2 ["hex" ])
146
- newbalance = self . nodes [ 0 ] .getbalance ()
153
+ newbalance = alice .getbalance ()
147
154
assert_equal (newbalance , balance - Decimal ("10" ) - Decimal ("14.99998" ) + Decimal ("24.9996" ))
148
155
balance = newbalance
149
156
150
157
# Remove using high relay fee again
151
158
self .restart_node (0 , extra_args = ["-minrelaytxfee=0.0001" ])
159
+ alice = self .nodes [0 ].get_wallet_rpc (self .default_wallet_name )
152
160
assert self .nodes [0 ].getmempoolinfo ()['loaded' ]
153
161
assert_equal (len (self .nodes [0 ].getrawmempool ()), 0 )
154
- newbalance = self . nodes [ 0 ] .getbalance ()
162
+ newbalance = alice .getbalance ()
155
163
assert_equal (newbalance , balance - Decimal ("24.9996" ))
156
164
balance = newbalance
157
165
158
166
self .log .info ("Test transactions conflicted by a double spend" )
167
+ self .nodes [0 ].loadwallet ("bob" )
168
+ bob = self .nodes [0 ].get_wallet_rpc ("bob" )
169
+
159
170
# Create a double spend of AB1 by spending again from only A's 10 output
160
171
# Mine double spend from node 1
161
172
inputs = []
162
173
inputs .append ({"txid" : txA , "vout" : nA })
163
174
outputs = {}
164
- outputs [self .nodes [1 ].getnewaddress ()] = Decimal ("9.9999" )
165
- tx = self .nodes [0 ].createrawtransaction (inputs , outputs )
166
- signed = self .nodes [0 ].signrawtransactionwithwallet (tx )
167
- self .nodes [1 ].sendrawtransaction (signed ["hex" ])
168
- self .generate (self .nodes [1 ], 1 , sync_fun = self .no_op )
169
-
175
+ outputs [self .nodes [1 ].getnewaddress ()] = Decimal ("3.9999" )
176
+ outputs [bob .getnewaddress ()] = Decimal ("5.9999" )
177
+ tx = alice .createrawtransaction (inputs , outputs )
178
+ signed = alice .signrawtransactionwithwallet (tx )
179
+ double_spend_txid = self .nodes [1 ].sendrawtransaction (signed ["hex" ])
170
180
self .connect_nodes (0 , 1 )
171
- self .sync_blocks ( )
181
+ self .generate ( self . nodes [ 1 ], 1 )
172
182
173
- tx_list = self . nodes [ 0 ] .listtransactions ()
183
+ tx_list = alice .listtransactions ()
174
184
175
185
conflicted = [tx for tx in tx_list if tx ["confirmations" ] < 0 ]
176
186
assert_equal (4 , len (conflicted ))
@@ -179,7 +189,7 @@ def run_test(self):
179
189
assert_equal (2 , len (wallet_conflicts ))
180
190
181
191
double_spends = [tx for tx in tx_list if tx ["walletconflicts" ] and tx ["confirmations" ] > 0 ]
182
- assert_equal (1 , len (double_spends ))
192
+ assert_equal (2 , len (double_spends )) # one for each output
183
193
double_spend = double_spends [0 ]
184
194
185
195
# Test the properties of the conflicted transactions, i.e. with confirmations < 0.
@@ -198,16 +208,27 @@ def run_test(self):
198
208
assert_equal (double_spend ["walletconflicts" ], [tx ["txid" ]])
199
209
assert_equal (tx ["walletconflicts" ], [double_spend ["txid" ]])
200
210
211
+ # Test walletconflicts on the receiver's side
212
+ txinfo = bob .gettransaction (txAB1 )
213
+ assert_equal (txinfo ['confirmations' ], - 1 )
214
+ assert_equal (txinfo ['walletconflicts' ], [double_spend ['txid' ]])
215
+
216
+ double_spends = [tx for tx in bob .listtransactions () if tx ["walletconflicts" ] and tx ["confirmations" ] > 0 ]
217
+ assert_equal (1 , len (double_spends ))
218
+ double_spend = double_spends [0 ]
219
+ assert_equal (double_spend_txid , double_spend ['txid' ])
220
+ assert_equal (double_spend ["walletconflicts" ], [txAB1 ])
221
+
201
222
# Verify that B and C's 10 BTC outputs are available for spending again because AB1 is now conflicted
202
- newbalance = self . nodes [ 0 ] .getbalance ()
223
+ newbalance = alice .getbalance ()
203
224
assert_equal (newbalance , balance + Decimal ("20" ))
204
225
balance = newbalance
205
226
206
227
# There is currently a minor bug around this and so this test doesn't work. See Issue #7315
207
228
# Invalidate the block with the double spend and B's 10 BTC output should no longer be available
208
229
# Don't think C's should either
209
230
self .nodes [0 ].invalidateblock (self .nodes [0 ].getbestblockhash ())
210
- newbalance = self . nodes [ 0 ] .getbalance ()
231
+ newbalance = alice .getbalance ()
211
232
#assert_equal(newbalance, balance - Decimal("10"))
212
233
self .log .info ("If balance has not declined after invalidateblock then out of mempool wallet tx which is no longer" )
213
234
self .log .info ("conflicted has not resumed causing its inputs to be seen as spent. See Issue #7315" )
0 commit comments