Skip to content

Commit

Permalink
allow send rpc take external inputs and solving data
Browse files Browse the repository at this point in the history
  • Loading branch information
achow101 committed Oct 3, 2021
1 parent e39b5a5 commit 928af61
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 1 deletion.
20 changes: 20 additions & 0 deletions src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4286,6 +4286,26 @@ static RPCHelpMan send()
},
{"replaceable", RPCArg::Type::BOOL, RPCArg::DefaultHint{"wallet default"}, "Marks this transaction as BIP125 replaceable.\n"
"Allows this transaction to be replaced by a transaction with higher fees"},
{"solving_data", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "Keys and scripts needed for producing a final transaction with a dummy signature.\n"
"Used for fee estimation during coin selection.",
{
{"pubkeys", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Public keys involved in this transaction.",
{
{"pubkey", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A public key"},
},
},
{"scripts", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Scripts involved in this transaction.",
{
{"script", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A script"},
},
},
{"descriptors", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Descriptors that provide solving data for this transaction.",
{
{"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "A descriptor"},
},
}
}
},
},
"options"},
},
Expand Down
46 changes: 45 additions & 1 deletion test/functional/wallet_send.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@

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,
assert_fee_amount,
assert_greater_than,
assert_raises_rpc_error,
)
from test_framework.wallet_util import bytes_to_wif

class WalletSendTest(BitcoinTestFramework):
def set_test_params(self):
Expand All @@ -35,7 +37,7 @@ def test_send(self, from_wallet, to_wallet=None, amount=None, data=None,
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"]
Expand Down Expand Up @@ -94,6 +96,8 @@ def test_send(self, from_wallet, to_wallet=None, amount=None, data=None,
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
Expand Down Expand Up @@ -476,6 +480,46 @@ def run_test(self):
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()

0 comments on commit 928af61

Please sign in to comment.