Skip to content

Commit a8ac2ad

Browse files
committed
test: fundrawtxn, add coverage for double change output creation
Coverage for fundrawtxn: 1) Should not create a second change output if there is one already going to that address. 2) The wallet changeless predilection should never discard a manually set output by the user. e.g. the user manually crafted a tx that already contain a change output with certain value. This output shouldn't get lost as it was predefined by the user. Only should be used as recipient of the "inputs - outputs" surplus during 'fundrawtransaction'.
1 parent 9509238 commit a8ac2ad

File tree

1 file changed

+78
-0
lines changed

1 file changed

+78
-0
lines changed

test/functional/wallet_fundrawtransaction.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ def run_test(self):
124124
self.test_invalid_change_address()
125125
self.test_valid_change_address()
126126
self.test_change_type()
127+
self.test_double_change()
127128
self.test_coin_selection()
128129
self.test_two_vin()
129130
self.test_two_vin_two_vout()
@@ -315,6 +316,83 @@ def test_change_type(self):
315316
dec_tx = self.nodes[2].decoderawtransaction(rawtx['hex'])
316317
assert_equal('witness_v0_keyhash', dec_tx['vout'][rawtx['changepos']]['scriptPubKey']['type'])
317318

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+
318396
def test_coin_selection(self):
319397
self.log.info("Test fundrawtxn with a vin < required amount")
320398
utx = get_unspent(self.nodes[2].listunspent(), 1)

0 commit comments

Comments
 (0)