@@ -124,6 +124,7 @@ def run_test(self):
124
124
self .test_invalid_change_address ()
125
125
self .test_valid_change_address ()
126
126
self .test_change_type ()
127
+ self .test_double_change ()
127
128
self .test_coin_selection ()
128
129
self .test_two_vin ()
129
130
self .test_two_vin_two_vout ()
@@ -315,6 +316,83 @@ def test_change_type(self):
315
316
dec_tx = self .nodes [2 ].decoderawtransaction (rawtx ['hex' ])
316
317
assert_equal ('witness_v0_keyhash' , dec_tx ['vout' ][rawtx ['changepos' ]]['scriptPubKey' ]['type' ])
317
318
319
+ def test_double_change (self ):
320
+ self .log .info ("Test fundrawtxn funding a tx with an existent change output and the same custom change addr" )
321
+ wallet = self .nodes [0 ]
322
+ wallet .syncwithvalidationinterfacequeue () # up-to-date wallet
323
+
324
+ # 1) fundrawtxn shouldn't create a second change output if there is one already going to that address
325
+ change_addr = wallet .getrawchangeaddress ()
326
+ external_addr = wallet .getnewaddress ()
327
+
328
+ outputs = {external_addr : Decimal (40.0 ), change_addr : Decimal (3 )}
329
+ rawtx = wallet .createrawtransaction ([], outputs )
330
+ funded_tx = wallet .fundrawtransaction (hexstring = rawtx , options = {'changeAddress' : change_addr })
331
+
332
+ # Verify change output existence
333
+ change_pos = funded_tx ['changepos' ]
334
+ assert change_pos != - 1
335
+
336
+ # Verify outputs now; the wallet must have increased the existent change out value instead of creating a new one
337
+ desc_tx = wallet .decoderawtransaction (funded_tx ['hex' ])
338
+ assert_equal (wallet .gettransaction (desc_tx ['vin' ][0 ]['txid' ])['amount' ], Decimal (50 )) # assert input value
339
+ assert_equal (len (desc_tx ['vout' ]), 2 )
340
+ for index in range (2 ):
341
+ out = desc_tx ['vout' ][index ]
342
+ if index == change_pos :
343
+ # Change must have increased its value, change = input - outputs - fee.
344
+ assert_equal (out ['value' ], Decimal (50 ) - outputs [external_addr ] - funded_tx ['fee' ])
345
+ assert_equal (out ['scriptPubKey' ]['address' ], change_addr )
346
+ else :
347
+ # Non-change output must remain the same
348
+ assert_equal (out ['value' ], outputs [external_addr ])
349
+ assert_equal (out ['scriptPubKey' ]['address' ], external_addr )
350
+
351
+ # Now verify fee and change output amount correctness.
352
+ # Create another tx looking exactly the same as the previous one (same inputs, same outputs) but without
353
+ # manually setting the change output (the wallet will automatically set it).
354
+ rawtx2 = wallet .createrawtransaction (inputs = [desc_tx ['vin' ][0 ]], outputs = {external_addr : Decimal (40.0 )})
355
+ funded_tx2 = wallet .fundrawtransaction (hexstring = rawtx2 , options = {'changeAddress' : change_addr , 'add_inputs' : False })
356
+ assert_equal (len (desc_tx ['vout' ]), 2 )
357
+ # Fee must be the same
358
+ assert_equal (funded_tx ['fee' ], funded_tx2 ['fee' ])
359
+ # And change amount must also be the same
360
+ def get_change_value (desc , change_pos ): return desc ['vout' ][change_pos ]['value' ]
361
+ desc_tx2 = wallet .decoderawtransaction (funded_tx2 ['hex' ])
362
+ assert_equal (get_change_value (desc_tx , funded_tx ['changepos' ]), get_change_value (desc_tx2 , funded_tx2 ['changepos' ]))
363
+
364
+ #############################################################################################################
365
+ # 2) Fund tx and verify that the second output going to the change address is not discarded by the wallet
366
+ # changeless txs predilection. Even when the output is "change" under the wallet perspective, the user has
367
+ # set it manually, so it must not be discarded.
368
+ outputs = {external_addr : Decimal ("49.998" ), change_addr : Decimal (3 )}
369
+ rawtx = wallet .createrawtransaction ([], outputs )
370
+ funded_tx = wallet .fundrawtransaction (hexstring = rawtx , options = {'changeAddress' : change_addr })
371
+
372
+ # Verify change output existence
373
+ change_pos = funded_tx ['changepos' ]
374
+ assert change_pos != - 1
375
+
376
+ # Verify outputs once more; the wallet must have increased the existent change out value instead of removing it
377
+ # in favor of a changeless solution
378
+ desc_tx = wallet .decoderawtransaction (funded_tx ['hex' ])
379
+ # assert inputs value
380
+ assert_equal (len (desc_tx ['vin' ]), 2 )
381
+ for input in desc_tx ['vin' ]:
382
+ assert_equal (wallet .gettransaction (input ['txid' ])['amount' ], Decimal (50 ))
383
+
384
+ assert_equal (len (desc_tx ['vout' ]), 2 )
385
+ for index in range (2 ):
386
+ out = desc_tx ['vout' ][index ]
387
+ if index == change_pos :
388
+ # Change must have increased its value, change = input - outputs - fee.
389
+ assert_equal (out ['value' ], Decimal (100 ) - outputs [external_addr ] - funded_tx ['fee' ])
390
+ assert_equal (out ['scriptPubKey' ]['address' ], change_addr )
391
+ else :
392
+ # Non-change output must remain the same
393
+ assert_equal (out ['value' ], outputs [external_addr ])
394
+ assert_equal (out ['scriptPubKey' ]['address' ], external_addr )
395
+
318
396
def test_coin_selection (self ):
319
397
self .log .info ("Test fundrawtxn with a vin < required amount" )
320
398
utx = get_unspent (self .nodes [2 ].listunspent (), 1 )
0 commit comments