-
Notifications
You must be signed in to change notification settings - Fork 38.7k
Allow fundrawtransaction and walletcreatefundedpsbt to take external inputs #17211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a00eb38
d5cfb86
38f5642
e39b5a5
928af61
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,9 +9,14 @@ | |
| #include <policy/feerate.h> | ||
| #include <policy/fees.h> | ||
| #include <primitives/transaction.h> | ||
| #include <script/keyorigin.h> | ||
| #include <script/signingprovider.h> | ||
| #include <script/standard.h> | ||
|
|
||
| #include <optional> | ||
| #include <algorithm> | ||
| #include <map> | ||
| #include <set> | ||
|
|
||
| const int DEFAULT_MIN_DEPTH = 0; | ||
| const int DEFAULT_MAX_DEPTH = 9999999; | ||
|
|
@@ -53,6 +58,8 @@ class CCoinControl | |
| int m_min_depth = DEFAULT_MIN_DEPTH; | ||
| //! Maximum chain depth value for coin availability | ||
| int m_max_depth = DEFAULT_MAX_DEPTH; | ||
| //! SigningProvider that has pubkeys and scripts to do spend size estimation for external inputs | ||
| FlatSigningProvider m_external_provider; | ||
|
|
||
| CCoinControl(); | ||
|
|
||
|
|
@@ -66,11 +73,32 @@ class CCoinControl | |
| return (setSelected.count(output) > 0); | ||
| } | ||
|
|
||
| bool IsExternalSelected(const COutPoint& output) const | ||
| { | ||
| return (m_external_txouts.count(output) > 0); | ||
| } | ||
|
|
||
| bool GetExternalOutput(const COutPoint& outpoint, CTxOut& txout) const | ||
| { | ||
| const auto ext_it = m_external_txouts.find(outpoint); | ||
| if (ext_it == m_external_txouts.end()) { | ||
| return false; | ||
| } | ||
| txout = ext_it->second; | ||
| return true; | ||
| } | ||
|
|
||
| void Select(const COutPoint& output) | ||
| { | ||
| setSelected.insert(output); | ||
| } | ||
|
|
||
| void Select(const COutPoint& outpoint, const CTxOut& txout) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would rather it be named the more obvious |
||
| { | ||
| setSelected.insert(outpoint); | ||
| m_external_txouts.emplace(outpoint, txout); | ||
| } | ||
|
|
||
| void UnSelect(const COutPoint& output) | ||
| { | ||
| setSelected.erase(output); | ||
|
|
@@ -88,6 +116,7 @@ class CCoinControl | |
|
|
||
| private: | ||
| std::set<COutPoint> setSelected; | ||
| std::map<COutPoint, CTxOut> m_external_txouts; | ||
| }; | ||
|
|
||
| #endif // BITCOIN_WALLET_COINCONTROL_H | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,6 +43,7 @@ | |
|
|
||
| #include <univalue.h> | ||
|
|
||
| #include <map> | ||
|
|
||
| using interfaces::FoundBlock; | ||
|
|
||
|
|
@@ -3213,6 +3214,7 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out, | |
| {"fee_rate", UniValueType()}, // will be checked by AmountFromValue() in SetFeeEstimateMode() | ||
| {"feeRate", UniValueType()}, // will be checked by AmountFromValue() below | ||
| {"psbt", UniValueType(UniValue::VBOOL)}, | ||
| {"solving_data", UniValueType(UniValue::VOBJ)}, | ||
| {"subtractFeeFromOutputs", UniValueType(UniValue::VARR)}, | ||
| {"subtract_fee_from_outputs", UniValueType(UniValue::VARR)}, | ||
| {"replaceable", UniValueType(UniValue::VBOOL)}, | ||
|
|
@@ -3289,6 +3291,54 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out, | |
| coinControl.fAllowWatchOnly = ParseIncludeWatchonly(NullUniValue, wallet); | ||
| } | ||
|
|
||
| if (options.exists("solving_data")) { | ||
| UniValue solving_data = options["solving_data"].get_obj(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any reason for this not to be |
||
| if (solving_data.exists("pubkeys")) { | ||
| for (const UniValue& pk_univ : solving_data["pubkeys"].get_array().getValues()) { | ||
| const std::string& pk_str = pk_univ.get_str(); | ||
| if (!IsHex(pk_str)) { | ||
| throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("'%s' is not hex", pk_str)); | ||
| } | ||
| const std::vector<unsigned char> data(ParseHex(pk_str)); | ||
| CPubKey pubkey(data.begin(), data.end()); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto. |
||
| if (!pubkey.IsFullyValid()) { | ||
| throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("'%s' is not a valid public key", pk_str)); | ||
| } | ||
| coinControl.m_external_provider.pubkeys.emplace(pubkey.GetID(), pubkey); | ||
| // Add witness script for pubkeys | ||
| const CScript wit_script = GetScriptForDestination(WitnessV0KeyHash(pubkey)); | ||
| coinControl.m_external_provider.scripts.emplace(CScriptID(wit_script), wit_script); | ||
| } | ||
| } | ||
|
|
||
| if (solving_data.exists("scripts")) { | ||
| for (const UniValue& script_univ : solving_data["scripts"].get_array().getValues()) { | ||
| const std::string& script_str = script_univ.get_str(); | ||
| if (!IsHex(script_str)) { | ||
| throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("'%s' is not hex", script_str)); | ||
| } | ||
| std::vector<unsigned char> script_data(ParseHex(script_str)); | ||
| const CScript script(script_data.begin(), script_data.end()); | ||
| coinControl.m_external_provider.scripts.emplace(CScriptID(script), script); | ||
| } | ||
| } | ||
|
|
||
| if (solving_data.exists("descriptors")) { | ||
| for (const UniValue& desc_univ : solving_data["descriptors"].get_array().getValues()) { | ||
| const std::string& desc_str = desc_univ.get_str(); | ||
| FlatSigningProvider desc_out; | ||
| std::string error; | ||
| std::vector<CScript> scripts_temp; | ||
| std::unique_ptr<Descriptor> desc = Parse(desc_str, desc_out, error, true); | ||
| if (!desc) { | ||
| throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Unable to parse descriptor '%s': %s", desc_str, error)); | ||
| } | ||
| desc->Expand(0, desc_out, scripts_temp, desc_out); | ||
| coinControl.m_external_provider = Merge(coinControl.m_external_provider, desc_out); | ||
| } | ||
| } | ||
| } | ||
achow101 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| if (tx.vout.size() == 0) | ||
| throw JSONRPCError(RPC_INVALID_PARAMETER, "TX must have at least one output"); | ||
|
|
||
|
|
@@ -3306,6 +3356,19 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out, | |
| setSubtractFeeFromOutputs.insert(pos); | ||
| } | ||
|
|
||
| // Fetch specified UTXOs from the UTXO set to get the scriptPubKeys and values of the outputs being selected | ||
| // and to match with the given solving_data. Only used for non-wallet outputs. | ||
| std::map<COutPoint, Coin> coins; | ||
achow101 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| for (const CTxIn& txin : tx.vin) { | ||
| coins[txin.prevout]; // Create empty map entry keyed by prevout. | ||
| } | ||
| wallet.chain().findCoins(coins); | ||
| for (const auto& coin : coins) { | ||
| if (!coin.second.out.IsNull()) { | ||
| coinControl.Select(coin.first, coin.second.out); | ||
| } | ||
| } | ||
|
|
||
| bilingual_str error; | ||
|
|
||
| if (!FundTransaction(wallet, tx, fee_out, change_position, error, lockUnspents, setSubtractFeeFromOutputs, coinControl)) { | ||
|
|
@@ -3321,8 +3384,9 @@ static RPCHelpMan fundrawtransaction() | |
| "No existing outputs will be modified unless \"subtractFeeFromOutputs\" is specified.\n" | ||
| "Note that inputs which were signed may need to be resigned after completion since in/outputs have been added.\n" | ||
| "The inputs added will not be signed, use signrawtransactionwithkey\n" | ||
| " or signrawtransactionwithwallet for that.\n" | ||
| "Note that all existing inputs must have their previous output transaction be in the wallet.\n" | ||
| "or signrawtransactionwithwallet for that.\n" | ||
| "All existing inputs must either have their previous output transaction be in the wallet\n" | ||
| "or be in the UTXO set. Solving data must be provided for non-wallet inputs.\n" | ||
| "Note that all inputs selected must be of standard form and P2SH scripts must be\n" | ||
| "in the wallet using importaddress or addmultisigaddress (to calculate fees).\n" | ||
| "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() | |
| {"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"}, | ||
| {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, std::string() + "The fee estimate mode, must be one of (case insensitive):\n" | ||
| " \"" + FeeModes("\"\n\"") + "\""}, | ||
| {"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"}, | ||
| {"iswitness", RPCArg::Type::BOOL, RPCArg::DefaultHint{"depends on heuristic tests"}, "Whether the transaction hex is a serialized witness transaction.\n" | ||
|
|
@@ -4202,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"}, | ||
| }, | ||
|
|
@@ -4489,7 +4593,9 @@ static RPCHelpMan walletcreatefundedpsbt() | |
| { | ||
| return RPCHelpMan{"walletcreatefundedpsbt", | ||
| "\nCreates and funds a transaction in the Partially Signed Transaction format.\n" | ||
| "Implements the Creator and Updater roles.\n", | ||
| "Implements the Creator and Updater roles.\n" | ||
| "All existing inputs must either have their previous output transaction be in the wallet\n" | ||
| "or be in the UTXO set. Solving data must be provided for non-wallet inputs.\n", | ||
| { | ||
| {"inputs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "Leave empty to add inputs automatically. See add_inputs option.", | ||
| { | ||
|
|
@@ -4546,6 +4652,26 @@ static RPCHelpMan walletcreatefundedpsbt() | |
| {"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"}, | ||
| {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, std::string() + "The fee estimate mode, must be one of (case insensitive):\n" | ||
| " \"" + FeeModes("\"\n\"") + "\""}, | ||
| {"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"}, | ||
| {"bip32derivs", RPCArg::Type::BOOL, RPCArg::Default{true}, "Include BIP 32 derivation paths for public keys if we know them"}, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.