Wallet/RPC: Allow specifying min & max chain depth for inputs used by fund calls

Enables users to craft BIP-125 replacements with changes to the output
list, ensuring that if additional funds are needed they will be added.
This commit is contained in:
Juan Pablo Civile
2021-05-24 10:22:10 -03:00
committed by ishaanam
parent 329d7e379d
commit a07a413466
5 changed files with 170 additions and 1 deletions

View File

@@ -36,6 +36,7 @@ from test_framework.util import (
assert_approx,
assert_equal,
assert_greater_than,
assert_greater_than_or_equal,
assert_raises_rpc_error,
find_output,
find_vout_for_address,
@@ -106,6 +107,65 @@ class PSBTTest(BitcoinTestFramework):
self.connect_nodes(0, 1)
self.connect_nodes(0, 2)
def test_input_confs_control(self):
self.nodes[0].createwallet("minconf")
wallet = self.nodes[0].get_wallet_rpc("minconf")
# Fund the wallet with different chain heights
for _ in range(2):
self.nodes[1].sendmany("", {wallet.getnewaddress():1, wallet.getnewaddress():1})
self.generate(self.nodes[1], 1)
unconfirmed_txid = wallet.sendtoaddress(wallet.getnewaddress(), 0.5)
self.log.info("Crafting PSBT using an unconfirmed input")
target_address = self.nodes[1].getnewaddress()
psbtx1 = wallet.walletcreatefundedpsbt([], {target_address: 0.1}, 0, {'fee_rate': 1, 'maxconf': 0})['psbt']
# Make sure we only had the one input
tx1_inputs = self.nodes[0].decodepsbt(psbtx1)['tx']['vin']
assert_equal(len(tx1_inputs), 1)
utxo1 = tx1_inputs[0]
assert_equal(unconfirmed_txid, utxo1['txid'])
signed_tx1 = wallet.walletprocesspsbt(psbtx1)['psbt']
final_tx1 = wallet.finalizepsbt(signed_tx1)['hex']
txid1 = self.nodes[0].sendrawtransaction(final_tx1)
mempool = self.nodes[0].getrawmempool()
assert txid1 in mempool
self.log.info("Fail to craft a new PSBT that sends more funds with add_inputs = False")
assert_raises_rpc_error(-4, "The preselected coins total amount does not cover the transaction target. Please allow other inputs to be automatically selected or include more coins manually", wallet.walletcreatefundedpsbt, [{'txid': utxo1['txid'], 'vout': utxo1['vout']}], {target_address: 1}, 0, {'add_inputs': False})
self.log.info("Fail to craft a new PSBT with minconf above highest one")
assert_raises_rpc_error(-4, "Insufficient funds", wallet.walletcreatefundedpsbt, [{'txid': utxo1['txid'], 'vout': utxo1['vout']}], {target_address: 1}, 0, {'add_inputs': True, 'minconf': 3, 'fee_rate': 10})
self.log.info("Fail to broadcast a new PSBT with maxconf 0 due to BIP125 rules to verify it actually chose unconfirmed outputs")
psbt_invalid = wallet.walletcreatefundedpsbt([{'txid': utxo1['txid'], 'vout': utxo1['vout']}], {target_address: 1}, 0, {'add_inputs': True, 'maxconf': 0, 'fee_rate': 10})['psbt']
signed_invalid = wallet.walletprocesspsbt(psbt_invalid)['psbt']
final_invalid = wallet.finalizepsbt(signed_invalid)['hex']
assert_raises_rpc_error(-26, "bad-txns-spends-conflicting-tx", self.nodes[0].sendrawtransaction, final_invalid)
self.log.info("Craft a replacement adding inputs with highest confs possible")
psbtx2 = wallet.walletcreatefundedpsbt([{'txid': utxo1['txid'], 'vout': utxo1['vout']}], {target_address: 1}, 0, {'add_inputs': True, 'minconf': 2, 'fee_rate': 10})['psbt']
tx2_inputs = self.nodes[0].decodepsbt(psbtx2)['tx']['vin']
assert_greater_than_or_equal(len(tx2_inputs), 2)
for vin in tx2_inputs:
if vin['txid'] != unconfirmed_txid:
assert_greater_than_or_equal(self.nodes[0].gettxout(vin['txid'], vin['vout'])['confirmations'], 2)
signed_tx2 = wallet.walletprocesspsbt(psbtx2)['psbt']
final_tx2 = wallet.finalizepsbt(signed_tx2)['hex']
txid2 = self.nodes[0].sendrawtransaction(final_tx2)
mempool = self.nodes[0].getrawmempool()
assert txid1 not in mempool
assert txid2 in mempool
wallet.unloadwallet()
def assert_change_type(self, psbtx, expected_type):
"""Assert that the given PSBT has a change output with the given type."""
@@ -514,6 +574,8 @@ class PSBTTest(BitcoinTestFramework):
# TODO: Re-enable this for segwit v1
# self.test_utxo_conversion()
self.test_input_confs_control()
# Test that psbts with p2pkh outputs are created properly
p2pkh = self.nodes[0].getnewaddress(address_type='legacy')
psbt = self.nodes[1].walletcreatefundedpsbt([], [{p2pkh : 1}], 0, {"includeWatching" : True}, True)