@@ -136,5 +136,291 @@ def test_block_conflicts(self):
136
136
assert_equal (former_conflicted ["confirmations" ], 1 )
137
137
assert_equal (former_conflicted ["blockheight" ], 217 )
138
138
139
+ def test_mempool_conflict (self ):
140
+ self .nodes [0 ].createwallet ("alice" )
141
+ alice = self .nodes [0 ].get_wallet_rpc ("alice" )
142
+
143
+ bob = self .nodes [1 ]
144
+
145
+ self .nodes [2 ].send (outputs = [{alice .getnewaddress () : 25 } for _ in range (3 )])
146
+ self .generate (self .nodes [2 ], 1 )
147
+
148
+ self .log .info ("Test a scenario where a transaction has a mempool conflict" )
149
+
150
+ unspents = alice .listunspent ()
151
+ assert_equal (len (unspents ), 3 )
152
+ assert all ([tx ["amount" ] == 25 for tx in unspents ])
153
+
154
+ # tx1 spends unspent[0] and unspent[1]
155
+ raw_tx = alice .createrawtransaction (inputs = [unspents [0 ], unspents [1 ]], outputs = [{bob .getnewaddress () : 49.9999 }])
156
+ tx1 = alice .signrawtransactionwithwallet (raw_tx )['hex' ]
157
+
158
+ # tx2 spends unspent[1] and unspent[2], conflicts with tx1
159
+ raw_tx = alice .createrawtransaction (inputs = [unspents [1 ], unspents [2 ]], outputs = [{bob .getnewaddress () : 49.99 }])
160
+ tx2 = alice .signrawtransactionwithwallet (raw_tx )['hex' ]
161
+
162
+ # tx3 spends unspent[2], conflicts with tx2
163
+ raw_tx = alice .createrawtransaction (inputs = [unspents [2 ]], outputs = [{bob .getnewaddress () : 24.9899 }])
164
+ tx3 = alice .signrawtransactionwithwallet (raw_tx )['hex' ]
165
+
166
+ # broadcast tx1
167
+ tx1_txid = alice .sendrawtransaction (tx1 )
168
+
169
+ assert_equal (alice .listunspent (), [unspents [2 ]])
170
+ assert_equal (alice .getbalance (), 25 )
171
+
172
+ # broadcast tx2, replaces tx1 in mempool
173
+ tx2_txid = alice .sendrawtransaction (tx2 )
174
+
175
+ # Check that unspent[0] is now available because the transaction spending it has been replaced in the mempool
176
+ assert_equal (alice .listunspent (), [unspents [0 ]])
177
+ assert_equal (alice .getbalance (), 25 )
178
+
179
+ assert_equal (alice .gettransaction (tx1_txid )["mempoolconflicts" ], [tx2_txid ])
180
+
181
+ self .log .info ("Test scenario where a mempool conflict is removed" )
182
+
183
+ # broadcast tx3, replaces tx2 in mempool
184
+ # Now that tx1's conflict has been removed, tx1 is now
185
+ # not conflicted, and instead is inactive until it is
186
+ # rebroadcasted. Now unspent[0] is not available, because
187
+ # tx1 is no longer conflicted.
188
+ alice .sendrawtransaction (tx3 )
189
+
190
+ assert_equal (alice .gettransaction (tx1_txid )["mempoolconflicts" ], [])
191
+ assert tx1_txid not in self .nodes [0 ].getrawmempool ()
192
+
193
+ # now all of alice's outputs should be considered spent
194
+ # unspent[0]: spent by inactive tx1
195
+ # unspent[1]: spent by inactive tx1
196
+ # unspent[2]: spent by active tx3
197
+ assert_equal (alice .listunspent (), [])
198
+ assert_equal (alice .getbalance (), 0 )
199
+
200
+ # Clean up for next test
201
+ bob .sendall ([self .nodes [2 ].getnewaddress ()])
202
+ self .generate (self .nodes [2 ], 1 )
203
+
204
+ alice .unloadwallet ()
205
+
206
+ def test_mempool_and_block_conflicts (self ):
207
+ self .nodes [0 ].createwallet ("alice_2" )
208
+ alice = self .nodes [0 ].get_wallet_rpc ("alice_2" )
209
+ bob = self .nodes [1 ]
210
+
211
+ self .nodes [2 ].send (outputs = [{alice .getnewaddress () : 25 } for _ in range (3 )])
212
+ self .generate (self .nodes [2 ], 1 )
213
+
214
+ self .log .info ("Test a scenario where a transaction has both a block conflict and a mempool conflict" )
215
+ unspents = [{"txid" : element ["txid" ], "vout" : element ["vout" ]} for element in alice .listunspent ()]
216
+
217
+ assert_equal (bob .getbalances ()["mine" ]["untrusted_pending" ], 0 )
218
+
219
+ # alice and bob nodes are disconnected so that transactions can be
220
+ # created by alice, but broadcasted from bob so that alice's wallet
221
+ # doesn't know about them
222
+ self .disconnect_nodes (0 , 1 )
223
+
224
+ # Sends funds to bob
225
+ raw_tx = alice .createrawtransaction (inputs = [unspents [0 ]], outputs = [{bob .getnewaddress () : 24.99999 }])
226
+ raw_tx1 = alice .signrawtransactionwithwallet (raw_tx )['hex' ]
227
+ tx1_txid = bob .sendrawtransaction (raw_tx1 ) # broadcast original tx spending unspents[0] only to bob
228
+
229
+ # create a conflict to previous tx (also spends unspents[0]), but don't broadcast, sends funds back to alice
230
+ raw_tx = alice .createrawtransaction (inputs = [unspents [0 ], unspents [2 ]], outputs = [{alice .getnewaddress () : 49.999 }])
231
+ tx1_conflict = alice .signrawtransactionwithwallet (raw_tx )['hex' ]
232
+
233
+ # Sends funds to bob
234
+ raw_tx = alice .createrawtransaction (inputs = [unspents [1 ]], outputs = [{bob .getnewaddress () : 24.9999 }])
235
+ raw_tx2 = alice .signrawtransactionwithwallet (raw_tx )['hex' ]
236
+ tx2_txid = bob .sendrawtransaction (raw_tx2 ) # broadcast another original tx spending unspents[1] only to bob
237
+
238
+ # create a conflict to previous tx (also spends unspents[1]), but don't broadcast, sends funds to alice
239
+ raw_tx = alice .createrawtransaction (inputs = [unspents [1 ]], outputs = [{alice .getnewaddress () : 24.9999 }])
240
+ tx2_conflict = alice .signrawtransactionwithwallet (raw_tx )['hex' ]
241
+
242
+ bob_unspents = [{"txid" : element , "vout" : 0 } for element in [tx1_txid , tx2_txid ]]
243
+
244
+ # tx1 and tx2 are now in bob's mempool, and they are unconflicted, so bob has these funds
245
+ assert_equal (bob .getbalances ()["mine" ]["untrusted_pending" ], Decimal ("49.99989000" ))
246
+
247
+ # spend both of bob's unspents, child tx of tx1 and tx2
248
+ raw_tx = bob .createrawtransaction (inputs = [bob_unspents [0 ], bob_unspents [1 ]], outputs = [{bob .getnewaddress () : 49.999 }])
249
+ raw_tx3 = bob .signrawtransactionwithwallet (raw_tx )['hex' ]
250
+ tx3_txid = bob .sendrawtransaction (raw_tx3 ) # broadcast tx only to bob
251
+
252
+ # alice knows about 0 txs, bob knows about 3
253
+ assert_equal (len (alice .getrawmempool ()), 0 )
254
+ assert_equal (len (bob .getrawmempool ()), 3 )
255
+
256
+ assert_equal (bob .getbalances ()["mine" ]["untrusted_pending" ], Decimal ("49.99900000" ))
257
+
258
+ # bob broadcasts tx_1 conflict
259
+ tx1_conflict_txid = bob .sendrawtransaction (tx1_conflict )
260
+ assert_equal (len (alice .getrawmempool ()), 0 )
261
+ assert_equal (len (bob .getrawmempool ()), 2 ) # tx1_conflict kicks out both tx1, and its child tx3
262
+
263
+ assert tx2_txid in bob .getrawmempool ()
264
+ assert tx1_conflict_txid in bob .getrawmempool ()
265
+
266
+ assert_equal (bob .gettransaction (tx1_txid )["mempoolconflicts" ], [tx1_conflict_txid ])
267
+ assert_equal (bob .gettransaction (tx2_txid )["mempoolconflicts" ], [])
268
+ assert_equal (bob .gettransaction (tx3_txid )["mempoolconflicts" ], [tx1_conflict_txid ])
269
+
270
+ # check that tx3 is now conflicted, so the output from tx2 can now be spent
271
+ assert_equal (bob .getbalances ()["mine" ]["untrusted_pending" ], Decimal ("24.99990000" ))
272
+
273
+ # we will be disconnecting this block in the future
274
+ alice .sendrawtransaction (tx2_conflict )
275
+ assert_equal (len (alice .getrawmempool ()), 1 ) # currently alice's mempool is only aware of tx2_conflict
276
+ # 11 blocks are mined so that when they are invalidated, tx_2
277
+ # does not get put back into the mempool
278
+ blk = self .generate (self .nodes [0 ], 11 , sync_fun = self .no_op )[0 ]
279
+ assert_equal (len (alice .getrawmempool ()), 0 ) # tx2_conflict is now mined
280
+
281
+ self .connect_nodes (0 , 1 )
282
+ self .sync_blocks ()
283
+ assert_equal (alice .getbestblockhash (), bob .getbestblockhash ())
284
+
285
+ # now that tx2 has a block conflict, tx1_conflict should be the only tx in bob's mempool
286
+ assert tx1_conflict_txid in bob .getrawmempool ()
287
+ assert_equal (len (bob .getrawmempool ()), 1 )
288
+
289
+ # tx3 should now also be block-conflicted by tx2_conflict
290
+ assert_equal (bob .gettransaction (tx3_txid )["confirmations" ], - 11 )
291
+ # bob has no pending funds, since tx1, tx2, and tx3 are all conflicted
292
+ assert_equal (bob .getbalances ()["mine" ]["untrusted_pending" ], 0 )
293
+ bob .invalidateblock (blk ) # remove tx2_conflict
294
+ # bob should still have no pending funds because tx1 and tx3 are still conflicted, and tx2 has not been re-broadcast
295
+ assert_equal (bob .getbalances ()["mine" ]["untrusted_pending" ], 0 )
296
+ assert_equal (len (bob .getrawmempool ()), 1 )
297
+ # check that tx3 is no longer block-conflicted
298
+ assert_equal (bob .gettransaction (tx3_txid )["confirmations" ], 0 )
299
+
300
+ bob .sendrawtransaction (raw_tx2 )
301
+ assert_equal (bob .getbalances ()["mine" ]["untrusted_pending" ], Decimal ("24.99990000" ))
302
+
303
+ # create a conflict to previous tx (also spends unspents[2]), but don't broadcast, sends funds back to alice
304
+ raw_tx = alice .createrawtransaction (inputs = [unspents [2 ]], outputs = [{alice .getnewaddress () : 24.99 }])
305
+ tx1_conflict_conflict = alice .signrawtransactionwithwallet (raw_tx )['hex' ]
306
+
307
+ bob .sendrawtransaction (tx1_conflict_conflict ) # kick tx1_conflict out of the mempool
308
+ bob .sendrawtransaction (raw_tx1 ) #re-broadcast tx1 because it is no longer conflicted
309
+
310
+ # Now bob has no pending funds because tx1 and tx2 are spent by tx3, which hasn't been re-broadcast yet
311
+ assert_equal (bob .getbalances ()["mine" ]["untrusted_pending" ], 0 )
312
+
313
+ bob .sendrawtransaction (raw_tx3 )
314
+ assert_equal (len (bob .getrawmempool ()), 4 ) # The mempool contains: tx1, tx2, tx1_conflict_conflict, tx3
315
+ assert_equal (bob .getbalances ()["mine" ]["untrusted_pending" ], Decimal ("49.99900000" ))
316
+
317
+ # Clean up for next test
318
+ bob .reconsiderblock (blk )
319
+ assert_equal (alice .getbestblockhash (), bob .getbestblockhash ())
320
+ self .sync_mempools ()
321
+ self .generate (self .nodes [2 ], 1 )
322
+
323
+ alice .unloadwallet ()
324
+
325
+ def test_descendants_with_mempool_conflicts (self ):
326
+ self .nodes [0 ].createwallet ("alice_3" )
327
+ alice = self .nodes [0 ].get_wallet_rpc ("alice_3" )
328
+
329
+ self .nodes [2 ].send (outputs = [{alice .getnewaddress () : 25 } for _ in range (2 )])
330
+ self .generate (self .nodes [2 ], 1 )
331
+
332
+ self .nodes [1 ].createwallet ("bob_1" )
333
+ bob = self .nodes [1 ].get_wallet_rpc ("bob_1" )
334
+
335
+ self .nodes [2 ].createwallet ("carol" )
336
+ carol = self .nodes [2 ].get_wallet_rpc ("carol" )
337
+
338
+ self .log .info ("Test a scenario where a transaction's parent has a mempool conflict" )
339
+
340
+ unspents = alice .listunspent ()
341
+ assert_equal (len (unspents ), 2 )
342
+ assert all ([tx ["amount" ] == 25 for tx in unspents ])
343
+
344
+ assert_equal (alice .getrawmempool (), [])
345
+
346
+ # Alice spends first utxo to bob in tx1
347
+ raw_tx = alice .createrawtransaction (inputs = [unspents [0 ]], outputs = [{bob .getnewaddress () : 24.9999 }])
348
+ tx1 = alice .signrawtransactionwithwallet (raw_tx )['hex' ]
349
+ tx1_txid = alice .sendrawtransaction (tx1 )
350
+
351
+ self .sync_mempools ()
352
+
353
+ assert_equal (alice .getbalance (), 25 )
354
+ assert_equal (bob .getbalances ()["mine" ]["untrusted_pending" ], Decimal ("24.99990000" ))
355
+
356
+ assert_equal (bob .gettransaction (tx1_txid )["mempoolconflicts" ], [])
357
+
358
+ raw_tx = bob .createrawtransaction (inputs = [bob .listunspent (minconf = 0 )[0 ]], outputs = [{carol .getnewaddress () : 24.999 }])
359
+ # Bob creates a child to tx1
360
+ tx1_child = bob .signrawtransactionwithwallet (raw_tx )['hex' ]
361
+ tx1_child_txid = bob .sendrawtransaction (tx1_child )
362
+
363
+ self .sync_mempools ()
364
+
365
+ # Currently neither tx1 nor tx1_child should have any conflicts
366
+ assert_equal (bob .gettransaction (tx1_txid )["mempoolconflicts" ], [])
367
+ assert_equal (bob .gettransaction (tx1_child_txid )["mempoolconflicts" ], [])
368
+ assert tx1_txid in bob .getrawmempool ()
369
+ assert tx1_child_txid in bob .getrawmempool ()
370
+ assert_equal (len (bob .getrawmempool ()), 2 )
371
+
372
+ assert_equal (bob .getbalances ()["mine" ]["untrusted_pending" ], 0 )
373
+ assert_equal (carol .getbalances ()["mine" ]["untrusted_pending" ], Decimal ("24.99900000" ))
374
+
375
+ # Alice spends first unspent again, conflicting with tx1
376
+ raw_tx = alice .createrawtransaction (inputs = [unspents [0 ], unspents [1 ]], outputs = [{carol .getnewaddress () : 49.99 }])
377
+ tx1_conflict = alice .signrawtransactionwithwallet (raw_tx )['hex' ]
378
+ tx1_conflict_txid = alice .sendrawtransaction (tx1_conflict )
379
+
380
+ self .sync_mempools ()
381
+
382
+ assert_equal (bob .getbalances ()["mine" ]["untrusted_pending" ], 0 )
383
+ assert_equal (carol .getbalances ()["mine" ]["untrusted_pending" ], Decimal ("49.99000000" ))
384
+
385
+ assert tx1_txid not in bob .getrawmempool ()
386
+ assert tx1_child_txid not in bob .getrawmempool ()
387
+ assert tx1_conflict_txid in bob .getrawmempool ()
388
+ assert_equal (len (bob .getrawmempool ()), 1 )
389
+
390
+ # Now both tx1 and tx1_child are conflicted by tx1_conflict
391
+ assert_equal (bob .gettransaction (tx1_txid )["mempoolconflicts" ], [tx1_conflict_txid ])
392
+ assert_equal (bob .gettransaction (tx1_child_txid )["mempoolconflicts" ], [tx1_conflict_txid ])
393
+
394
+ # Now create a conflict to tx1_conflict, so that it gets kicked out of the mempool
395
+ raw_tx = alice .createrawtransaction (inputs = [unspents [1 ]], outputs = [{carol .getnewaddress () : 24.9895 }])
396
+ tx1_conflict_conflict = alice .signrawtransactionwithwallet (raw_tx )['hex' ]
397
+ tx1_conflict_conflict_txid = alice .sendrawtransaction (tx1_conflict_conflict )
398
+
399
+ self .sync_mempools ()
400
+
401
+ # Now that tx1_conflict has been removed, both tx1 and tx1_child
402
+ assert_equal (bob .gettransaction (tx1_txid )["mempoolconflicts" ], [])
403
+ assert_equal (bob .gettransaction (tx1_child_txid )["mempoolconflicts" ], [])
404
+
405
+ # Both tx1 and tx1_child are still not in the mempool because they have not be re-broadcasted
406
+ assert tx1_txid not in bob .getrawmempool ()
407
+ assert tx1_child_txid not in bob .getrawmempool ()
408
+ assert tx1_conflict_txid not in bob .getrawmempool ()
409
+ assert tx1_conflict_conflict_txid in bob .getrawmempool ()
410
+ assert_equal (len (bob .getrawmempool ()), 1 )
411
+
412
+ assert_equal (alice .getbalance (), 0 )
413
+ assert_equal (bob .getbalances ()["mine" ]["untrusted_pending" ], 0 )
414
+ assert_equal (carol .getbalances ()["mine" ]["untrusted_pending" ], Decimal ("24.98950000" ))
415
+
416
+ # Both tx1 and tx1_child can now be re-broadcasted
417
+ bob .sendrawtransaction (tx1 )
418
+ bob .sendrawtransaction (tx1_child )
419
+ assert_equal (len (bob .getrawmempool ()), 3 )
420
+
421
+ alice .unloadwallet ()
422
+ bob .unloadwallet ()
423
+ carol .unloadwallet ()
424
+
139
425
if __name__ == '__main__' :
140
426
TxConflicts (__file__ ).main ()
0 commit comments