mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-04-20 03:39:48 +02:00
Merge bitcoin/bitcoin#17211: Allow fundrawtransaction and walletcreatefundedpsbt to take external inputs
928af61cdballow send rpc take external inputs and solving data (Andrew Chow)e39b5a5e7aTests for funding with external inputs (Andrew Chow)38f5642cccallow fundtx rpcs to work with external inputs (Andrew Chow)d5cfb864aeAllow Coin Selection be able to take external inputs (Andrew Chow)a00eb388e8Allow CInputCoin to also be constructed with COutPoint and CTxOut (Andrew Chow) Pull request description: Currently `fundrawtransaction` and `walletcreatefundedpsbt` both do not allow external inputs as the wallet does not have the information necessary to estimate their fees. This PR adds an additional argument to both those RPCs which allows the user to specify solving data. This way, the wallet can use that solving data to estimate the size of those inputs. The solving data can be public keys, scripts, or descriptors. ACKs for top commit: prayank23: reACK928af61cdbmeshcollider: Re-utACK928af61cdbinstagibbs: crACK928af61cdbyanmaani: utACK928af61. Tree-SHA512: bc7a6ef8961a7f4971ea5985d75e2d6dc50c2a90b44c664a1c4b0f1be5c1c97823516358fdaab35771a4701dbefc0862127b1d0d4bfd02b4f20d2befa4434700
This commit is contained in:
@@ -8,6 +8,7 @@ from decimal import Decimal
|
||||
from itertools import product
|
||||
|
||||
from test_framework.descriptors import descsum_create
|
||||
from test_framework.key import ECKey
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_approx,
|
||||
@@ -19,6 +20,7 @@ from test_framework.util import (
|
||||
count_bytes,
|
||||
find_vout_for_address,
|
||||
)
|
||||
from test_framework.wallet_util import bytes_to_wif
|
||||
|
||||
|
||||
def get_unspent(listunspent, amount):
|
||||
@@ -132,6 +134,7 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||
self.test_subtract_fee_with_presets()
|
||||
self.test_transaction_too_large()
|
||||
self.test_include_unsafe()
|
||||
self.test_external_inputs()
|
||||
self.test_22670()
|
||||
|
||||
def test_change_position(self):
|
||||
@@ -983,6 +986,56 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||
wallet.sendmany("", outputs)
|
||||
self.generate(self.nodes[0], 10)
|
||||
assert_raises_rpc_error(-4, "Transaction too large", recipient.fundrawtransaction, rawtx)
|
||||
self.nodes[0].unloadwallet("large")
|
||||
|
||||
def test_external_inputs(self):
|
||||
self.log.info("Test funding with external inputs")
|
||||
|
||||
eckey = ECKey()
|
||||
eckey.generate()
|
||||
privkey = bytes_to_wif(eckey.get_bytes())
|
||||
|
||||
self.nodes[2].createwallet("extfund")
|
||||
wallet = self.nodes[2].get_wallet_rpc("extfund")
|
||||
|
||||
# Make a weird but signable script. sh(pkh()) descriptor accomplishes this
|
||||
desc = descsum_create("sh(pkh({}))".format(privkey))
|
||||
if self.options.descriptors:
|
||||
res = self.nodes[0].importdescriptors([{"desc": desc, "timestamp": "now"}])
|
||||
else:
|
||||
res = self.nodes[0].importmulti([{"desc": desc, "timestamp": "now"}])
|
||||
assert res[0]["success"]
|
||||
addr = self.nodes[0].deriveaddresses(desc)[0]
|
||||
addr_info = self.nodes[0].getaddressinfo(addr)
|
||||
|
||||
self.nodes[0].sendtoaddress(addr, 10)
|
||||
self.nodes[0].sendtoaddress(wallet.getnewaddress(), 10)
|
||||
self.nodes[0].generate(6)
|
||||
ext_utxo = self.nodes[0].listunspent(addresses=[addr])[0]
|
||||
|
||||
# An external input without solving data should result in an error
|
||||
raw_tx = wallet.createrawtransaction([ext_utxo], {self.nodes[0].getnewaddress(): 15})
|
||||
assert_raises_rpc_error(-4, "Insufficient funds", wallet.fundrawtransaction, raw_tx)
|
||||
|
||||
# Error conditions
|
||||
assert_raises_rpc_error(-5, "'not a pubkey' is not hex", wallet.fundrawtransaction, raw_tx, {"solving_data": {"pubkeys":["not a pubkey"]}})
|
||||
assert_raises_rpc_error(-5, "'01234567890a0b0c0d0e0f' is not a valid public key", wallet.fundrawtransaction, raw_tx, {"solving_data": {"pubkeys":["01234567890a0b0c0d0e0f"]}})
|
||||
assert_raises_rpc_error(-5, "'not a script' is not hex", wallet.fundrawtransaction, raw_tx, {"solving_data": {"scripts":["not a script"]}})
|
||||
assert_raises_rpc_error(-8, "Unable to parse descriptor 'not a descriptor'", wallet.fundrawtransaction, raw_tx, {"solving_data": {"descriptors":["not a descriptor"]}})
|
||||
|
||||
# But funding should work when the solving data is provided
|
||||
funded_tx = wallet.fundrawtransaction(raw_tx, {"solving_data": {"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"]]}})
|
||||
signed_tx = wallet.signrawtransactionwithwallet(funded_tx['hex'])
|
||||
assert not signed_tx['complete']
|
||||
signed_tx = self.nodes[0].signrawtransactionwithwallet(signed_tx['hex'])
|
||||
assert signed_tx['complete']
|
||||
|
||||
funded_tx = wallet.fundrawtransaction(raw_tx, {"solving_data": {"descriptors": [desc]}})
|
||||
signed_tx = wallet.signrawtransactionwithwallet(funded_tx['hex'])
|
||||
assert not signed_tx['complete']
|
||||
signed_tx = self.nodes[0].signrawtransactionwithwallet(signed_tx['hex'])
|
||||
assert signed_tx['complete']
|
||||
self.nodes[2].unloadwallet("extfund")
|
||||
|
||||
def test_include_unsafe(self):
|
||||
self.log.info("Test fundrawtxn with unsafe inputs")
|
||||
@@ -1017,6 +1070,7 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||
assert all((txin["txid"], txin["vout"]) in inputs for txin in tx_dec["vin"])
|
||||
signedtx = wallet.signrawtransactionwithwallet(fundedtx['hex'])
|
||||
assert wallet.testmempoolaccept([signedtx['hex']])[0]["allowed"]
|
||||
self.nodes[0].unloadwallet("unsafe")
|
||||
|
||||
def test_22670(self):
|
||||
# In issue #22670, it was observed that ApproximateBestSubset may
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
from decimal import Decimal
|
||||
from itertools import product
|
||||
|
||||
from test_framework.descriptors import descsum_create
|
||||
from test_framework.key import ECKey
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_approx,
|
||||
@@ -16,6 +18,7 @@ from test_framework.util import (
|
||||
assert_raises_rpc_error,
|
||||
find_output,
|
||||
)
|
||||
from test_framework.wallet_util import bytes_to_wif
|
||||
|
||||
import json
|
||||
import os
|
||||
@@ -608,5 +611,42 @@ class PSBTTest(BitcoinTestFramework):
|
||||
|
||||
assert_raises_rpc_error(-25, 'Inputs missing or spent', self.nodes[0].walletprocesspsbt, 'cHNidP8BAJoCAAAAAkvEW8NnDtdNtDpsmze+Ht2LH35IJcKv00jKAlUs21RrAwAAAAD/////S8Rbw2cO1020OmybN74e3Ysffkglwq/TSMoCVSzbVGsBAAAAAP7///8CwLYClQAAAAAWABSNJKzjaUb3uOxixsvh1GGE3fW7zQD5ApUAAAAAFgAUKNw0x8HRctAgmvoevm4u1SbN7XIAAAAAAAEAnQIAAAACczMa321tVHuN4GKWKRncycI22aX3uXgwSFUKM2orjRsBAAAAAP7///9zMxrfbW1Ue43gYpYpGdzJwjbZpfe5eDBIVQozaiuNGwAAAAAA/v///wIA+QKVAAAAABl2qRT9zXUVA8Ls5iVqynLHe5/vSe1XyYisQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAAAAAQEfQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAA==')
|
||||
|
||||
# Test that we can fund psbts with external inputs specified
|
||||
eckey = ECKey()
|
||||
eckey.generate()
|
||||
privkey = bytes_to_wif(eckey.get_bytes())
|
||||
|
||||
# Make a weird but signable script. sh(pkh()) descriptor accomplishes this
|
||||
desc = descsum_create("sh(pkh({}))".format(privkey))
|
||||
if self.options.descriptors:
|
||||
res = self.nodes[0].importdescriptors([{"desc": desc, "timestamp": "now"}])
|
||||
else:
|
||||
res = self.nodes[0].importmulti([{"desc": desc, "timestamp": "now"}])
|
||||
assert res[0]["success"]
|
||||
addr = self.nodes[0].deriveaddresses(desc)[0]
|
||||
addr_info = self.nodes[0].getaddressinfo(addr)
|
||||
|
||||
self.nodes[0].sendtoaddress(addr, 10)
|
||||
self.nodes[0].generate(6)
|
||||
ext_utxo = self.nodes[0].listunspent(addresses=[addr])[0]
|
||||
|
||||
# An external input without solving data should result in an error
|
||||
assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[1].walletcreatefundedpsbt, [ext_utxo], {self.nodes[0].getnewaddress(): 10 + ext_utxo['amount']}, 0, {'add_inputs': True})
|
||||
|
||||
# But funding should work when the solving data is provided
|
||||
psbt = self.nodes[1].walletcreatefundedpsbt([ext_utxo], {self.nodes[0].getnewaddress(): 15}, 0, {'add_inputs': True, "solving_data": {"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"]]}})
|
||||
signed = self.nodes[1].walletprocesspsbt(psbt['psbt'])
|
||||
assert not signed['complete']
|
||||
signed = self.nodes[0].walletprocesspsbt(signed['psbt'])
|
||||
assert signed['complete']
|
||||
self.nodes[0].finalizepsbt(signed['psbt'])
|
||||
|
||||
psbt = self.nodes[1].walletcreatefundedpsbt([ext_utxo], {self.nodes[0].getnewaddress(): 15}, 0, {'add_inputs': True, "solving_data":{"descriptors": [desc]}})
|
||||
signed = self.nodes[1].walletprocesspsbt(psbt['psbt'])
|
||||
assert not signed['complete']
|
||||
signed = self.nodes[0].walletprocesspsbt(signed['psbt'])
|
||||
assert signed['complete']
|
||||
self.nodes[0].finalizepsbt(signed['psbt'])
|
||||
|
||||
if __name__ == '__main__':
|
||||
PSBTTest().main()
|
||||
|
||||
@@ -9,6 +9,7 @@ from itertools import product
|
||||
|
||||
from test_framework.authproxy import JSONRPCException
|
||||
from test_framework.descriptors import descsum_create
|
||||
from test_framework.key import ECKey
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
@@ -16,6 +17,7 @@ from test_framework.util import (
|
||||
assert_greater_than,
|
||||
assert_raises_rpc_error,
|
||||
)
|
||||
from test_framework.wallet_util import bytes_to_wif
|
||||
|
||||
class WalletSendTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
@@ -35,7 +37,7 @@ class WalletSendTest(BitcoinTestFramework):
|
||||
conf_target=None, estimate_mode=None, fee_rate=None, add_to_wallet=None, psbt=None,
|
||||
inputs=None, add_inputs=None, include_unsafe=None, change_address=None, change_position=None, change_type=None,
|
||||
include_watching=None, locktime=None, lock_unspents=None, replaceable=None, subtract_fee_from_outputs=None,
|
||||
expect_error=None):
|
||||
expect_error=None, solving_data=None):
|
||||
assert (amount is None) != (data is None)
|
||||
|
||||
from_balance_before = from_wallet.getbalances()["mine"]["trusted"]
|
||||
@@ -94,6 +96,8 @@ class WalletSendTest(BitcoinTestFramework):
|
||||
options["replaceable"] = replaceable
|
||||
if subtract_fee_from_outputs is not None:
|
||||
options["subtract_fee_from_outputs"] = subtract_fee_from_outputs
|
||||
if solving_data is not None:
|
||||
options["solving_data"] = solving_data
|
||||
|
||||
if len(options.keys()) == 0:
|
||||
options = None
|
||||
@@ -476,6 +480,46 @@ class WalletSendTest(BitcoinTestFramework):
|
||||
res = self.test_send(from_wallet=w5, to_wallet=w0, amount=1, include_unsafe=True)
|
||||
assert res["complete"]
|
||||
|
||||
self.log.info("External outputs")
|
||||
eckey = ECKey()
|
||||
eckey.generate()
|
||||
privkey = bytes_to_wif(eckey.get_bytes())
|
||||
|
||||
self.nodes[1].createwallet("extsend")
|
||||
ext_wallet = self.nodes[1].get_wallet_rpc("extsend")
|
||||
self.nodes[1].createwallet("extfund")
|
||||
ext_fund = self.nodes[1].get_wallet_rpc("extfund")
|
||||
|
||||
# Make a weird but signable script. sh(pkh()) descriptor accomplishes this
|
||||
desc = descsum_create("sh(pkh({}))".format(privkey))
|
||||
if self.options.descriptors:
|
||||
res = ext_fund.importdescriptors([{"desc": desc, "timestamp": "now"}])
|
||||
else:
|
||||
res = ext_fund.importmulti([{"desc": desc, "timestamp": "now"}])
|
||||
assert res[0]["success"]
|
||||
addr = self.nodes[0].deriveaddresses(desc)[0]
|
||||
addr_info = ext_fund.getaddressinfo(addr)
|
||||
|
||||
self.nodes[0].sendtoaddress(addr, 10)
|
||||
self.nodes[0].sendtoaddress(ext_wallet.getnewaddress(), 10)
|
||||
self.nodes[0].generate(6)
|
||||
ext_utxo = ext_fund.listunspent(addresses=[addr])[0]
|
||||
|
||||
# An external input without solving data should result in an error
|
||||
self.test_send(from_wallet=ext_wallet, to_wallet=self.nodes[0], amount=15, inputs=[ext_utxo], add_inputs=True, psbt=True, include_watching=True, expect_error=(-4, "Insufficient funds"))
|
||||
|
||||
# But funding should work when the solving data is provided
|
||||
res = self.test_send(from_wallet=ext_wallet, to_wallet=self.nodes[0], amount=15, inputs=[ext_utxo], add_inputs=True, psbt=True, include_watching=True, solving_data={"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"]]})
|
||||
signed = ext_wallet.walletprocesspsbt(res["psbt"])
|
||||
signed = ext_fund.walletprocesspsbt(res["psbt"])
|
||||
assert signed["complete"]
|
||||
self.nodes[0].finalizepsbt(signed["psbt"])
|
||||
|
||||
res = self.test_send(from_wallet=ext_wallet, to_wallet=self.nodes[0], amount=15, inputs=[ext_utxo], add_inputs=True, psbt=True, include_watching=True, solving_data={"descriptors": [desc]})
|
||||
signed = ext_wallet.walletprocesspsbt(res["psbt"])
|
||||
signed = ext_fund.walletprocesspsbt(res["psbt"])
|
||||
assert signed["complete"]
|
||||
self.nodes[0].finalizepsbt(signed["psbt"])
|
||||
|
||||
if __name__ == '__main__':
|
||||
WalletSendTest().main()
|
||||
|
||||
Reference in New Issue
Block a user