Skip to content

Commit 38f5642

Browse files
committed
allow fundtx rpcs to work with external inputs
1 parent d5cfb86 commit 38f5642

File tree

1 file changed

+109
-3
lines changed

1 file changed

+109
-3
lines changed

src/wallet/rpcwallet.cpp

Lines changed: 109 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343

4444
#include <univalue.h>
4545

46+
#include <map>
4647

4748
using interfaces::FoundBlock;
4849

@@ -3213,6 +3214,7 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out,
32133214
{"fee_rate", UniValueType()}, // will be checked by AmountFromValue() in SetFeeEstimateMode()
32143215
{"feeRate", UniValueType()}, // will be checked by AmountFromValue() below
32153216
{"psbt", UniValueType(UniValue::VBOOL)},
3217+
{"solving_data", UniValueType(UniValue::VOBJ)},
32163218
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
32173219
{"subtract_fee_from_outputs", UniValueType(UniValue::VARR)},
32183220
{"replaceable", UniValueType(UniValue::VBOOL)},
@@ -3289,6 +3291,54 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out,
32893291
coinControl.fAllowWatchOnly = ParseIncludeWatchonly(NullUniValue, wallet);
32903292
}
32913293

3294+
if (options.exists("solving_data")) {
3295+
UniValue solving_data = options["solving_data"].get_obj();
3296+
if (solving_data.exists("pubkeys")) {
3297+
for (const UniValue& pk_univ : solving_data["pubkeys"].get_array().getValues()) {
3298+
const std::string& pk_str = pk_univ.get_str();
3299+
if (!IsHex(pk_str)) {
3300+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("'%s' is not hex", pk_str));
3301+
}
3302+
const std::vector<unsigned char> data(ParseHex(pk_str));
3303+
CPubKey pubkey(data.begin(), data.end());
3304+
if (!pubkey.IsFullyValid()) {
3305+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("'%s' is not a valid public key", pk_str));
3306+
}
3307+
coinControl.m_external_provider.pubkeys.emplace(pubkey.GetID(), pubkey);
3308+
// Add witness script for pubkeys
3309+
const CScript wit_script = GetScriptForDestination(WitnessV0KeyHash(pubkey));
3310+
coinControl.m_external_provider.scripts.emplace(CScriptID(wit_script), wit_script);
3311+
}
3312+
}
3313+
3314+
if (solving_data.exists("scripts")) {
3315+
for (const UniValue& script_univ : solving_data["scripts"].get_array().getValues()) {
3316+
const std::string& script_str = script_univ.get_str();
3317+
if (!IsHex(script_str)) {
3318+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("'%s' is not hex", script_str));
3319+
}
3320+
std::vector<unsigned char> script_data(ParseHex(script_str));
3321+
const CScript script(script_data.begin(), script_data.end());
3322+
coinControl.m_external_provider.scripts.emplace(CScriptID(script), script);
3323+
}
3324+
}
3325+
3326+
if (solving_data.exists("descriptors")) {
3327+
for (const UniValue& desc_univ : solving_data["descriptors"].get_array().getValues()) {
3328+
const std::string& desc_str = desc_univ.get_str();
3329+
FlatSigningProvider desc_out;
3330+
std::string error;
3331+
std::vector<CScript> scripts_temp;
3332+
std::unique_ptr<Descriptor> desc = Parse(desc_str, desc_out, error, true);
3333+
if (!desc) {
3334+
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Unable to parse descriptor '%s': %s", desc_str, error));
3335+
}
3336+
desc->Expand(0, desc_out, scripts_temp, desc_out);
3337+
coinControl.m_external_provider = Merge(coinControl.m_external_provider, desc_out);
3338+
}
3339+
}
3340+
}
3341+
32923342
if (tx.vout.size() == 0)
32933343
throw JSONRPCError(RPC_INVALID_PARAMETER, "TX must have at least one output");
32943344

@@ -3306,6 +3356,19 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out,
33063356
setSubtractFeeFromOutputs.insert(pos);
33073357
}
33083358

3359+
// Fetch specified UTXOs from the UTXO set to get the scriptPubKeys and values of the outputs being selected
3360+
// and to match with the given solving_data. Only used for non-wallet outputs.
3361+
std::map<COutPoint, Coin> coins;
3362+
for (const CTxIn& txin : tx.vin) {
3363+
coins[txin.prevout]; // Create empty map entry keyed by prevout.
3364+
}
3365+
wallet.chain().findCoins(coins);
3366+
for (const auto& coin : coins) {
3367+
if (!coin.second.out.IsNull()) {
3368+
coinControl.Select(coin.first, coin.second.out);
3369+
}
3370+
}
3371+
33093372
bilingual_str error;
33103373

33113374
if (!FundTransaction(wallet, tx, fee_out, change_position, error, lockUnspents, setSubtractFeeFromOutputs, coinControl)) {
@@ -3321,8 +3384,9 @@ static RPCHelpMan fundrawtransaction()
33213384
"No existing outputs will be modified unless \"subtractFeeFromOutputs\" is specified.\n"
33223385
"Note that inputs which were signed may need to be resigned after completion since in/outputs have been added.\n"
33233386
"The inputs added will not be signed, use signrawtransactionwithkey\n"
3324-
" or signrawtransactionwithwallet for that.\n"
3325-
"Note that all existing inputs must have their previous output transaction be in the wallet.\n"
3387+
"or signrawtransactionwithwallet for that.\n"
3388+
"All existing inputs must either have their previous output transaction be in the wallet\n"
3389+
"or be in the UTXO set. Solving data must be provided for non-wallet inputs.\n"
33263390
"Note that all inputs selected must be of standard form and P2SH scripts must be\n"
33273391
"in the wallet using importaddress or addmultisigaddress (to calculate fees).\n"
33283392
"You can see whether this is the case by checking the \"solvable\" field in the listunspent output.\n"
@@ -3357,6 +3421,26 @@ static RPCHelpMan fundrawtransaction()
33573421
{"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"},
33583422
{"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
33593423
" \"" + FeeModes("\"\n\"") + "\""},
3424+
{"solving_data", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "Keys and scripts needed for producing a final transaction with a dummy signature.\n"
3425+
"Used for fee estimation during coin selection.",
3426+
{
3427+
{"pubkeys", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Public keys involved in this transaction.",
3428+
{
3429+
{"pubkey", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A public key"},
3430+
},
3431+
},
3432+
{"scripts", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Scripts involved in this transaction.",
3433+
{
3434+
{"script", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A script"},
3435+
},
3436+
},
3437+
{"descriptors", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Descriptors that provide solving data for this transaction.",
3438+
{
3439+
{"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "A descriptor"},
3440+
},
3441+
}
3442+
}
3443+
},
33603444
},
33613445
"options"},
33623446
{"iswitness", RPCArg::Type::BOOL, RPCArg::DefaultHint{"depends on heuristic tests"}, "Whether the transaction hex is a serialized witness transaction.\n"
@@ -4489,7 +4573,9 @@ static RPCHelpMan walletcreatefundedpsbt()
44894573
{
44904574
return RPCHelpMan{"walletcreatefundedpsbt",
44914575
"\nCreates and funds a transaction in the Partially Signed Transaction format.\n"
4492-
"Implements the Creator and Updater roles.\n",
4576+
"Implements the Creator and Updater roles.\n"
4577+
"All existing inputs must either have their previous output transaction be in the wallet\n"
4578+
"or be in the UTXO set. Solving data must be provided for non-wallet inputs.\n",
44934579
{
44944580
{"inputs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "Leave empty to add inputs automatically. See add_inputs option.",
44954581
{
@@ -4546,6 +4632,26 @@ static RPCHelpMan walletcreatefundedpsbt()
45464632
{"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"},
45474633
{"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
45484634
" \"" + FeeModes("\"\n\"") + "\""},
4635+
{"solving_data", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "Keys and scripts needed for producing a final transaction with a dummy signature.\n"
4636+
"Used for fee estimation during coin selection.",
4637+
{
4638+
{"pubkeys", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Public keys involved in this transaction.",
4639+
{
4640+
{"pubkey", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A public key"},
4641+
},
4642+
},
4643+
{"scripts", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Scripts involved in this transaction.",
4644+
{
4645+
{"script", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A script"},
4646+
},
4647+
},
4648+
{"descriptors", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Descriptors that provide solving data for this transaction.",
4649+
{
4650+
{"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "A descriptor"},
4651+
},
4652+
}
4653+
}
4654+
},
45494655
},
45504656
"options"},
45514657
{"bip32derivs", RPCArg::Type::BOOL, RPCArg::Default{true}, "Include BIP 32 derivation paths for public keys if we know them"},

0 commit comments

Comments
 (0)