aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSamuel Dobson <dobsonsa68@gmail.com>2021-10-04 21:46:51 +1300
committerSamuel Dobson <dobsonsa68@gmail.com>2021-10-04 22:08:46 +1300
commit573b4621cc1f1ea2a46d3c068af4bbb74f9c2a18 (patch)
treee9b606a70ef83637197c4fe5659b512c9cc02012 /src
parent446b706696451ae1a66ac416f347d734c5741d7c (diff)
parent928af61cdb2c4de1c3d10e6fda13bbba5ca0bba9 (diff)
downloadbitcoin-573b4621cc1f1ea2a46d3c068af4bbb74f9c2a18.tar.xz
Merge bitcoin/bitcoin#17211: Allow fundrawtransaction and walletcreatefundedpsbt to take external inputs
928af61cdb2c4de1c3d10e6fda13bbba5ca0bba9 allow send rpc take external inputs and solving data (Andrew Chow) e39b5a5e7aa4d015257565ca79dc7b1f7a65e074 Tests for funding with external inputs (Andrew Chow) 38f5642cccf2b6708e58f5e2af5ecdcf752e61ec allow fundtx rpcs to work with external inputs (Andrew Chow) d5cfb864ae16da62399bc97ab1ed54d32cf0cce9 Allow Coin Selection be able to take external inputs (Andrew Chow) a00eb388e8046fe105666445dff6c91e8f8664cb Allow 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: reACK https://github.com/bitcoin/bitcoin/commit/928af61cdb2c4de1c3d10e6fda13bbba5ca0bba9 meshcollider: Re-utACK 928af61cdb2c4de1c3d10e6fda13bbba5ca0bba9 instagibbs: crACK 928af61cdb2c4de1c3d10e6fda13bbba5ca0bba9 yanmaani: utACK 928af61. Tree-SHA512: bc7a6ef8961a7f4971ea5985d75e2d6dc50c2a90b44c664a1c4b0f1be5c1c97823516358fdaab35771a4701dbefc0862127b1d0d4bfd02b4f20d2befa4434700
Diffstat (limited to 'src')
-rw-r--r--src/wallet/coincontrol.h29
-rw-r--r--src/wallet/coinselection.h12
-rw-r--r--src/wallet/rpcwallet.cpp132
-rw-r--r--src/wallet/spend.cpp81
-rw-r--r--src/wallet/spend.h5
-rw-r--r--src/wallet/wallet.cpp23
-rw-r--r--src/wallet/wallet.h9
7 files changed, 242 insertions, 49 deletions
diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h
index 85cbec76b7..c989512d3e 100644
--- a/src/wallet/coincontrol.h
+++ b/src/wallet/coincontrol.h
@@ -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 @@ public:
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 @@ public:
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)
+ {
+ setSelected.insert(outpoint);
+ m_external_txouts.emplace(outpoint, txout);
+ }
+
void UnSelect(const COutPoint& output)
{
setSelected.erase(output);
@@ -88,6 +116,7 @@ public:
private:
std::set<COutPoint> setSelected;
+ std::map<COutPoint, CTxOut> m_external_txouts;
};
#endif // BITCOIN_WALLET_COINCONTROL_H
diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h
index a28bee622e..78d877a10b 100644
--- a/src/wallet/coinselection.h
+++ b/src/wallet/coinselection.h
@@ -37,6 +37,18 @@ public:
m_input_bytes = input_bytes;
}
+ CInputCoin(const COutPoint& outpoint_in, const CTxOut& txout_in)
+ {
+ outpoint = outpoint_in;
+ txout = txout_in;
+ effective_value = txout.nValue;
+ }
+
+ CInputCoin(const COutPoint& outpoint_in, const CTxOut& txout_in, int input_bytes) : CInputCoin(outpoint_in, txout_in)
+ {
+ m_input_bytes = input_bytes;
+ }
+
COutPoint outpoint;
CTxOut txout;
CAmount effective_value;
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index f6cf8868de..aa97b748b1 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -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();
+ 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());
+ 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);
+ }
+ }
+ }
+
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;
+ 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"},
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index 1724375f4c..43fcc0416d 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -5,6 +5,7 @@
#include <consensus/validation.h>
#include <interfaces/chain.h>
#include <policy/policy.h>
+#include <script/signingprovider.h>
#include <util/check.h>
#include <util/fees.h>
#include <util/moneystr.h>
@@ -31,21 +32,27 @@ std::string COutput::ToString() const
return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue));
}
-int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, bool use_max_sig)
+int CalculateMaximumSignedInputSize(const CTxOut& txout, const SigningProvider* provider, bool use_max_sig)
{
CMutableTransaction txn;
txn.vin.push_back(CTxIn(COutPoint()));
- if (!wallet->DummySignInput(txn.vin[0], txout, use_max_sig)) {
+ if (!provider || !DummySignInput(*provider, txn.vin[0], txout, use_max_sig)) {
return -1;
}
return GetVirtualTransactionInputSize(txn.vin[0]);
}
+int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, bool use_max_sig)
+{
+ const std::unique_ptr<SigningProvider> provider = wallet->GetSolvingProvider(txout.scriptPubKey);
+ return CalculateMaximumSignedInputSize(txout, provider.get(), use_max_sig);
+}
+
// txouts needs to be in the order of tx.vin
-TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig)
+TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, const CCoinControl* coin_control)
{
CMutableTransaction txNew(tx);
- if (!wallet->DummySignTx(txNew, txouts, use_max_sig)) {
+ if (!wallet->DummySignTx(txNew, txouts, coin_control)) {
return TxSize{-1, -1};
}
CTransaction ctx(txNew);
@@ -54,19 +61,27 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *walle
return TxSize{vsize, weight};
}
-TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig)
+TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const CCoinControl* coin_control)
{
std::vector<CTxOut> txouts;
+ // Look up the inputs. The inputs are either in the wallet, or in coin_control.
for (const CTxIn& input : tx.vin) {
const auto mi = wallet->mapWallet.find(input.prevout.hash);
// Can not estimate size without knowing the input details
- if (mi == wallet->mapWallet.end()) {
+ if (mi != wallet->mapWallet.end()) {
+ assert(input.prevout.n < mi->second.tx->vout.size());
+ txouts.emplace_back(mi->second.tx->vout.at(input.prevout.n));
+ } else if (coin_control) {
+ CTxOut txout;
+ if (!coin_control->GetExternalOutput(input.prevout, txout)) {
+ return TxSize{-1, -1};
+ }
+ txouts.emplace_back(txout);
+ } else {
return TxSize{-1, -1};
}
- assert(input.prevout.n < mi->second.tx->vout.size());
- txouts.emplace_back(mi->second.tx->vout[input.prevout.n]);
}
- return CalculateMaximumSignedTxSize(tx, wallet, txouts, use_max_sig);
+ return CalculateMaximumSignedTxSize(tx, wallet, txouts, coin_control);
}
void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount)
@@ -435,32 +450,40 @@ bool SelectCoins(const CWallet& wallet, const std::vector<COutput>& vAvailableCo
std::vector<COutPoint> vPresetInputs;
coin_control.ListSelected(vPresetInputs);
- for (const COutPoint& outpoint : vPresetInputs)
- {
+ for (const COutPoint& outpoint : vPresetInputs) {
+ int input_bytes = -1;
+ CTxOut txout;
std::map<uint256, CWalletTx>::const_iterator it = wallet.mapWallet.find(outpoint.hash);
- if (it != wallet.mapWallet.end())
- {
+ if (it != wallet.mapWallet.end()) {
const CWalletTx& wtx = it->second;
// Clearly invalid input, fail
if (wtx.tx->vout.size() <= outpoint.n) {
return false;
}
- // Just to calculate the marginal byte size
- CInputCoin coin(wtx.tx, outpoint.n, GetTxSpendSize(wallet, wtx, outpoint.n, false));
- nValueFromPresetInputs += coin.txout.nValue;
- if (coin.m_input_bytes <= 0) {
- return false; // Not solvable, can't estimate size for fee
- }
- coin.effective_value = coin.txout.nValue - coin_selection_params.m_effective_feerate.GetFee(coin.m_input_bytes);
- if (coin_selection_params.m_subtract_fee_outputs) {
- value_to_select -= coin.txout.nValue;
- } else {
- value_to_select -= coin.effective_value;
+ input_bytes = GetTxSpendSize(wallet, wtx, outpoint.n, false);
+ txout = wtx.tx->vout.at(outpoint.n);
+ }
+ if (input_bytes == -1) {
+ // The input is external. We either did not find the tx in mapWallet, or we did but couldn't compute the input size with wallet data
+ if (!coin_control.GetExternalOutput(outpoint, txout)) {
+ // Not ours, and we don't have solving data.
+ return false;
}
- setPresetCoins.insert(coin);
+ input_bytes = CalculateMaximumSignedInputSize(txout, &coin_control.m_external_provider, /* use_max_sig */ true);
+ }
+
+ CInputCoin coin(outpoint, txout, input_bytes);
+ nValueFromPresetInputs += coin.txout.nValue;
+ if (coin.m_input_bytes <= 0) {
+ return false; // Not solvable, can't estimate size for fee
+ }
+ coin.effective_value = coin.txout.nValue - coin_selection_params.m_effective_feerate.GetFee(coin.m_input_bytes);
+ if (coin_selection_params.m_subtract_fee_outputs) {
+ value_to_select -= coin.txout.nValue;
} else {
- return false; // TODO: Allow non-wallet inputs
+ value_to_select -= coin.effective_value;
}
+ setPresetCoins.insert(coin);
}
// remove preset inputs from vCoins so that Coin Selection doesn't pick them.
@@ -788,10 +811,10 @@ static bool CreateTransactionInternal(
}
// Calculate the transaction fee
- TxSize tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, coin_control.fAllowWatchOnly);
+ TxSize tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, &coin_control);
int nBytes = tx_sizes.vsize;
if (nBytes < 0) {
- error = _("Signing transaction failed");
+ error = _("Missing solving data for estimating transaction size");
return false;
}
nFeeRet = coin_selection_params.m_effective_feerate.GetFee(nBytes);
@@ -813,7 +836,7 @@ static bool CreateTransactionInternal(
txNew.vout.erase(change_position);
// Because we have dropped this change, the tx size and required fee will be different, so let's recalculate those
- tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, coin_control.fAllowWatchOnly);
+ tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, &coin_control);
nBytes = tx_sizes.vsize;
fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes);
}
diff --git a/src/wallet/spend.h b/src/wallet/spend.h
index e39f134dc3..3a723d9832 100644
--- a/src/wallet/spend.h
+++ b/src/wallet/spend.h
@@ -66,6 +66,7 @@ public:
//Get the marginal bytes of spending the specified output
int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, bool use_max_sig = false);
+int CalculateMaximumSignedInputSize(const CTxOut& txout, const SigningProvider* pwallet, bool use_max_sig = false);
struct TxSize {
int64_t vsize{-1};
@@ -76,8 +77,8 @@ struct TxSize {
* Use DummySignatureCreator, which inserts 71 byte signatures everywhere.
* NOTE: this requires that all inputs must be in mapWallet (eg the tx should
* be AllInputsMine). */
-TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const std::vector<CTxOut>& txouts, bool use_max_sig = false);
-TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, bool use_max_sig = false) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet);
+TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const std::vector<CTxOut>& txouts, const CCoinControl* coin_control = nullptr);
+TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const CCoinControl* coin_control = nullptr) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet);
/**
* populate vCoins with vector of available COutputs.
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 1c18cdb1bc..bf6d8e7510 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1448,19 +1448,13 @@ bool CWallet::AddWalletFlags(uint64_t flags)
// Helper for producing a max-sized low-S low-R signature (eg 71 bytes)
// or a max-sized low-S signature (e.g. 72 bytes) if use_max_sig is true
-bool CWallet::DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig) const
+bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, bool use_max_sig)
{
// Fill in dummy signatures for fee calculation.
const CScript& scriptPubKey = txout.scriptPubKey;
SignatureData sigdata;
- std::unique_ptr<SigningProvider> provider = GetSolvingProvider(scriptPubKey);
- if (!provider) {
- // We don't know about this scriptpbuKey;
- return false;
- }
-
- if (!ProduceSignature(*provider, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) {
+ if (!ProduceSignature(provider, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) {
return false;
}
UpdateInput(tx_in, sigdata);
@@ -1468,14 +1462,21 @@ bool CWallet::DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig
}
// Helper for producing a bunch of max-sized low-S low-R signatures (eg 71 bytes)
-bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, bool use_max_sig) const
+bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, const CCoinControl* coin_control) const
{
// Fill in dummy signatures for fee calculation.
int nIn = 0;
for (const auto& txout : txouts)
{
- if (!DummySignInput(txNew.vin[nIn], txout, use_max_sig)) {
- return false;
+ CTxIn& txin = txNew.vin[nIn];
+ // Use max sig if watch only inputs were used or if this particular input is an external input
+ // to ensure a sufficient fee is attained for the requested feerate.
+ const bool use_max_sig = coin_control && (coin_control->fAllowWatchOnly || coin_control->IsExternalSelected(txin.prevout));
+ const std::unique_ptr<SigningProvider> provider = GetSolvingProvider(txout.scriptPubKey);
+ if (!provider || !DummySignInput(*provider, txin, txout, use_max_sig)) {
+ if (!coin_control || !DummySignInput(coin_control->m_external_provider, txin, txout, use_max_sig)) {
+ return false;
+ }
}
nIn++;
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index cedccf7d44..b949fe3040 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -576,14 +576,13 @@ public:
/** Pass this transaction to node for mempool insertion and relay to peers if flag set to true */
bool SubmitTxMemoryPoolAndRelay(const CWalletTx& wtx, std::string& err_string, bool relay) const;
- bool DummySignTx(CMutableTransaction &txNew, const std::set<CTxOut> &txouts, bool use_max_sig = false) const
+ bool DummySignTx(CMutableTransaction &txNew, const std::set<CTxOut> &txouts, const CCoinControl* coin_control = nullptr) const
{
std::vector<CTxOut> v_txouts(txouts.size());
std::copy(txouts.begin(), txouts.end(), v_txouts.begin());
- return DummySignTx(txNew, v_txouts, use_max_sig);
+ return DummySignTx(txNew, v_txouts, coin_control);
}
- bool DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, bool use_max_sig = false) const;
- bool DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig = false) const;
+ bool DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, const CCoinControl* coin_control = nullptr) const;
bool ImportScripts(const std::set<CScript> scripts, int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool ImportPrivKeys(const std::map<CKeyID, CKey>& privkey_map, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
@@ -928,4 +927,6 @@ bool AddWalletSetting(interfaces::Chain& chain, const std::string& wallet_name);
//! Remove wallet name from persistent configuration so it will not be loaded on startup.
bool RemoveWalletSetting(interfaces::Chain& chain, const std::string& wallet_name);
+bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, bool use_max_sig);
+
#endif // BITCOIN_WALLET_WALLET_H