Skip to content

Commit e39b5a5

Browse files
committed
Tests for funding with external inputs
1 parent 38f5642 commit e39b5a5

File tree

2 files changed

+94
-0
lines changed

2 files changed

+94
-0
lines changed

test/functional/rpc_fundrawtransaction.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from itertools import product
99

1010
from test_framework.descriptors import descsum_create
11+
from test_framework.key import ECKey
1112
from test_framework.test_framework import BitcoinTestFramework
1213
from test_framework.util import (
1314
assert_approx,
@@ -19,6 +20,7 @@
1920
count_bytes,
2021
find_vout_for_address,
2122
)
23+
from test_framework.wallet_util import bytes_to_wif
2224

2325

2426
def get_unspent(listunspent, amount):
@@ -132,6 +134,7 @@ def run_test(self):
132134
self.test_subtract_fee_with_presets()
133135
self.test_transaction_too_large()
134136
self.test_include_unsafe()
137+
self.test_external_inputs()
135138
self.test_22670()
136139

137140
def test_change_position(self):
@@ -983,6 +986,56 @@ def test_transaction_too_large(self):
983986
wallet.sendmany("", outputs)
984987
self.generate(self.nodes[0], 10)
985988
assert_raises_rpc_error(-4, "Transaction too large", recipient.fundrawtransaction, rawtx)
989+
self.nodes[0].unloadwallet("large")
990+
991+
def test_external_inputs(self):
992+
self.log.info("Test funding with external inputs")
993+
994+
eckey = ECKey()
995+
eckey.generate()
996+
privkey = bytes_to_wif(eckey.get_bytes())
997+
998+
self.nodes[2].createwallet("extfund")
999+
wallet = self.nodes[2].get_wallet_rpc("extfund")
1000+
1001+
# Make a weird but signable script. sh(pkh()) descriptor accomplishes this
1002+
desc = descsum_create("sh(pkh({}))".format(privkey))
1003+
if self.options.descriptors:
1004+
res = self.nodes[0].importdescriptors([{"desc": desc, "timestamp": "now"}])
1005+
else:
1006+
res = self.nodes[0].importmulti([{"desc": desc, "timestamp": "now"}])
1007+
assert res[0]["success"]
1008+
addr = self.nodes[0].deriveaddresses(desc)[0]
1009+
addr_info = self.nodes[0].getaddressinfo(addr)
1010+
1011+
self.nodes[0].sendtoaddress(addr, 10)
1012+
self.nodes[0].sendtoaddress(wallet.getnewaddress(), 10)
1013+
self.nodes[0].generate(6)
1014+
ext_utxo = self.nodes[0].listunspent(addresses=[addr])[0]
1015+
1016+
# An external input without solving data should result in an error
1017+
raw_tx = wallet.createrawtransaction([ext_utxo], {self.nodes[0].getnewaddress(): 15})
1018+
assert_raises_rpc_error(-4, "Insufficient funds", wallet.fundrawtransaction, raw_tx)
1019+
1020+
# Error conditions
1021+
assert_raises_rpc_error(-5, "'not a pubkey' is not hex", wallet.fundrawtransaction, raw_tx, {"solving_data": {"pubkeys":["not a pubkey"]}})
1022+
assert_raises_rpc_error(-5, "'01234567890a0b0c0d0e0f' is not a valid public key", wallet.fundrawtransaction, raw_tx, {"solving_data": {"pubkeys":["01234567890a0b0c0d0e0f"]}})
1023+
assert_raises_rpc_error(-5, "'not a script' is not hex", wallet.fundrawtransaction, raw_tx, {"solving_data": {"scripts":["not a script"]}})
1024+
assert_raises_rpc_error(-8, "Unable to parse descriptor 'not a descriptor'", wallet.fundrawtransaction, raw_tx, {"solving_data": {"descriptors":["not a descriptor"]}})
1025+
1026+
# But funding should work when the solving data is provided
1027+
funded_tx = wallet.fundrawtransaction(raw_tx, {"solving_data": {"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"]]}})
1028+
signed_tx = wallet.signrawtransactionwithwallet(funded_tx['hex'])
1029+
assert not signed_tx['complete']
1030+
signed_tx = self.nodes[0].signrawtransactionwithwallet(signed_tx['hex'])
1031+
assert signed_tx['complete']
1032+
1033+
funded_tx = wallet.fundrawtransaction(raw_tx, {"solving_data": {"descriptors": [desc]}})
1034+
signed_tx = wallet.signrawtransactionwithwallet(funded_tx['hex'])
1035+
assert not signed_tx['complete']
1036+
signed_tx = self.nodes[0].signrawtransactionwithwallet(signed_tx['hex'])
1037+
assert signed_tx['complete']
1038+
self.nodes[2].unloadwallet("extfund")
9861039

9871040
def test_include_unsafe(self):
9881041
self.log.info("Test fundrawtxn with unsafe inputs")
@@ -1017,6 +1070,7 @@ def test_include_unsafe(self):
10171070
assert all((txin["txid"], txin["vout"]) in inputs for txin in tx_dec["vin"])
10181071
signedtx = wallet.signrawtransactionwithwallet(fundedtx['hex'])
10191072
assert wallet.testmempoolaccept([signedtx['hex']])[0]["allowed"]
1073+
self.nodes[0].unloadwallet("unsafe")
10201074

10211075
def test_22670(self):
10221076
# In issue #22670, it was observed that ApproximateBestSubset may

test/functional/rpc_psbt.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from decimal import Decimal
99
from itertools import product
1010

11+
from test_framework.descriptors import descsum_create
12+
from test_framework.key import ECKey
1113
from test_framework.test_framework import BitcoinTestFramework
1214
from test_framework.util import (
1315
assert_approx,
@@ -16,6 +18,7 @@
1618
assert_raises_rpc_error,
1719
find_output,
1820
)
21+
from test_framework.wallet_util import bytes_to_wif
1922

2023
import json
2124
import os
@@ -608,5 +611,42 @@ def test_psbt_input_keys(psbt_input, keys):
608611

609612
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==')
610613

614+
# Test that we can fund psbts with external inputs specified
615+
eckey = ECKey()
616+
eckey.generate()
617+
privkey = bytes_to_wif(eckey.get_bytes())
618+
619+
# Make a weird but signable script. sh(pkh()) descriptor accomplishes this
620+
desc = descsum_create("sh(pkh({}))".format(privkey))
621+
if self.options.descriptors:
622+
res = self.nodes[0].importdescriptors([{"desc": desc, "timestamp": "now"}])
623+
else:
624+
res = self.nodes[0].importmulti([{"desc": desc, "timestamp": "now"}])
625+
assert res[0]["success"]
626+
addr = self.nodes[0].deriveaddresses(desc)[0]
627+
addr_info = self.nodes[0].getaddressinfo(addr)
628+
629+
self.nodes[0].sendtoaddress(addr, 10)
630+
self.nodes[0].generate(6)
631+
ext_utxo = self.nodes[0].listunspent(addresses=[addr])[0]
632+
633+
# An external input without solving data should result in an error
634+
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})
635+
636+
# But funding should work when the solving data is provided
637+
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"]]}})
638+
signed = self.nodes[1].walletprocesspsbt(psbt['psbt'])
639+
assert not signed['complete']
640+
signed = self.nodes[0].walletprocesspsbt(signed['psbt'])
641+
assert signed['complete']
642+
self.nodes[0].finalizepsbt(signed['psbt'])
643+
644+
psbt = self.nodes[1].walletcreatefundedpsbt([ext_utxo], {self.nodes[0].getnewaddress(): 15}, 0, {'add_inputs': True, "solving_data":{"descriptors": [desc]}})
645+
signed = self.nodes[1].walletprocesspsbt(psbt['psbt'])
646+
assert not signed['complete']
647+
signed = self.nodes[0].walletprocesspsbt(signed['psbt'])
648+
assert signed['complete']
649+
self.nodes[0].finalizepsbt(signed['psbt'])
650+
611651
if __name__ == '__main__':
612652
PSBTTest().main()

0 commit comments

Comments
 (0)