mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-05-03 08:21:29 +02:00
Allow Coin Selection be able to take external inputs
This commit is contained in:
parent
a00eb388e8
commit
d5cfb864ae
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
setPresetCoins.insert(coin);
|
||||
} else {
|
||||
return false; // TODO: Allow non-wallet inputs
|
||||
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;
|
||||
}
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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++;
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user