From 8258aaa7338948d96c812e7505532f53380ebda4 Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Tue, 22 Oct 2019 15:06:24 -0400 Subject: [PATCH] Allow Coin Selection be able to take external inputs --- src/wallet/coincontrol.cpp | 2 ++ src/wallet/coincontrol.h | 24 +++++++++++++ src/wallet/wallet.cpp | 69 ++++++++++++++++++++++++++------------ src/wallet/wallet.h | 13 +++---- 4 files changed, 79 insertions(+), 29 deletions(-) diff --git a/src/wallet/coincontrol.cpp b/src/wallet/coincontrol.cpp index 6b3090d02c..0c190b7a2c 100644 --- a/src/wallet/coincontrol.cpp +++ b/src/wallet/coincontrol.cpp @@ -19,5 +19,7 @@ void CCoinControl::SetNull() m_confirm_target.reset(); m_signal_bip125_rbf.reset(); m_fee_mode = FeeEstimateMode::UNSET; + m_external_txouts.clear(); + m_external_provider = FlatSigningProvider(); } diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index b9aeb7b08f..0041852ec9 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -37,6 +37,8 @@ public: bool m_avoid_partial_spends; //! Fee estimation mode to control arguments to estimateSmartFee FeeEstimateMode m_fee_mode; + //! SigningProvider that has pubkeys and scripts to do spend size estimation for external inputs + FlatSigningProvider m_external_provider; CCoinControl() { @@ -55,11 +57,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 SelectExternal(const COutPoint& outpoint, const CTxOut& txout) + { + setSelected.insert(outpoint); + m_external_txouts.emplace(outpoint, txout); + } + void UnSelect(const COutPoint& output) { setSelected.erase(output); @@ -77,6 +100,7 @@ public: private: std::set setSelected; + std::map m_external_txouts; }; #endif // BITCOIN_WALLET_COINCONTROL_H diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index bac5164134..ea2fa432a3 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1626,13 +1626,13 @@ int64_t CWalletTx::GetTxTime() const // 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(CMutableTransaction& tx, const size_t nIn, const CTxOut& txout, bool use_max_sig) const +static bool DummySignInput(const SigningProvider* provider, CMutableTransaction& tx, const size_t nIn, const CTxOut& txout, bool use_max_sig) { // Fill in dummy signatures for fee calculation. const CScript& scriptPubKey = txout.scriptPubKey; SignatureData sigdata; - if (!ProduceSignature(*this, 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; } UpdateTransaction(tx, nIn, sigdata); @@ -1640,14 +1640,18 @@ bool CWallet::DummySignInput(CMutableTransaction& tx, const size_t nIn, const CT } // Helper for producing a bunch of max-sized low-S low-R signatures (eg 71 bytes) -bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector &txouts, bool use_max_sig) const +bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector &txouts, const CCoinControl* coin_control) const { // Fill in dummy signatures for fee calculation. int nIn = 0; for (const auto& txout : txouts) { - if (!DummySignInput(txNew, nIn, txout, use_max_sig)) { - return false; + // Use max sig if watch only inputs were used or if this particular input is an external input + bool use_max_sig = coin_control && (coin_control->fAllowWatchOnly || (coin_control && coin_control->IsExternalSelected(txNew.vin[nIn].prevout))); + if (!DummySignInput(this, txNew, nIn, txout, use_max_sig)) { + if (!coin_control || !DummySignInput(&coin_control->m_external_provider, txNew, nIn, txout, use_max_sig)) { + return false; + } } nIn++; @@ -1655,28 +1659,33 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector return true; } -int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig) +int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const CCoinControl* coin_control) { std::vector txouts; - // Look up the inputs. We should have already checked that this transaction - // IsAllFromMe(ISMINE_SPENDABLE), so every input should already be in our - // wallet, with a valid index into the vout array, and the ability to sign. + // 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); - 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[input.prevout.n]); + } else if (coin_control) { + CTxOut txout; + if (!coin_control->GetExternalOutput(input.prevout, txout)) { + return -1; + } + txouts.emplace_back(txout); + } else { return -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); } // txouts needs to be in the order of tx.vin -int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector& txouts, bool use_max_sig) +int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector& txouts, const CCoinControl* coin_control) { CMutableTransaction txNew(tx); - if (!wallet->DummySignTx(txNew, txouts, use_max_sig)) { + if (!wallet->DummySignTx(txNew, txouts, coin_control)) { // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE) // implies that we can sign for every input. return -1; @@ -1688,7 +1697,7 @@ int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, { CMutableTransaction txn; txn.vin.push_back(CTxIn(COutPoint())); - if (!wallet->DummySignInput(txn, 0, txout, use_max_sig)) { + if (!DummySignInput(wallet, txn, 0, txout, use_max_sig)) { // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE) // implies that we can sign for every input. return -1; @@ -2690,8 +2699,18 @@ bool CWallet::SelectCoins(const std::vector& vAvailableCoins, const CAm } mapValueFromPresetInputs[pcoin->GetOutputAsset(outpoint.n)] += amt; setPresetCoins.insert(CInputCoin(pcoin, outpoint.n)); - } else - return false; // TODO: Allow non-wallet inputs + } else { + CTxOut txout; + if (coin_control.GetExternalOutput(outpoint, txout)) { + if (!txout.nValue.IsExplicit() || !txout.nAsset.IsExplicit()) { + return false; // We can't get its value, so abort + } + mapValueFromPresetInputs[txout.nAsset.GetAsset()] += txout.nValue.GetAmount(); + setPresetCoins.insert(CInputCoin(outpoint, txout)); + } else { + return false; + } + } } // remove preset inputs from vCoins @@ -2824,8 +2843,11 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC coinControl.ListSelected(vPresetInputs); for (const COutPoint& presetInput : vPresetInputs) { std::map::const_iterator it = mapWallet.find(presetInput.hash); + CTxOut txout; if (it != mapWallet.end()) { setAssets.insert(it->second.GetOutputAsset(presetInput.n)); + } else if (coinControl.GetExternalOutput(presetInput, txout)) { + setAssets.insert(txout.nAsset.GetAsset()); } } @@ -3162,13 +3184,18 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std std::vector vPresetInputs; coin_control.ListSelected(vPresetInputs); for (const COutPoint& presetInput : vPresetInputs) { + CAsset asset; std::map::const_iterator it = mapWallet.find(presetInput.hash); - if (it == mapWallet.end()) { + CTxOut txout; + if (it != mapWallet.end()) { + asset = it->second.GetOutputAsset(presetInput.n); + } else if (coin_control.GetExternalOutput(presetInput, txout)) { + asset = txout.nAsset.GetAsset(); + } else { // Ignore this here, will fail more gracefully later. continue; } - CAsset asset = it->second.GetOutputAsset(presetInput.n); if (mapScriptChange.find(asset) != mapScriptChange.end()) { // This asset already has a change script. continue; @@ -3509,7 +3536,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std } } - nBytes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly); + nBytes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, &coin_control); if (nBytes < 0) { strFailReason = _("Signing transaction failed"); return false; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 4262744a9a..fd0ce2792b 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1084,14 +1084,13 @@ public: std::string& strFailReason, const CCoinControl& coin_control, bool sign = true, BlindDetails* blind_details = nullptr, const IssuanceDetails* issuance_details = nullptr); bool CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector> orderForm, std::vector>& reservekey, CConnman* connman, CValidationState& state, const BlindDetails* blind_details = nullptr); - bool DummySignTx(CMutableTransaction &txNew, const std::set &txouts, bool use_max_sig = false) const + bool DummySignTx(CMutableTransaction &txNew, const std::set &txouts, const CCoinControl* coin_control = nullptr) const { std::vector 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 &txouts, bool use_max_sig = false) const; - bool DummySignInput(CMutableTransaction &tx, const size_t nIn, const CTxOut &txout, bool use_max_sig = false) const; + bool DummySignTx(CMutableTransaction &txNew, const std::vector &txouts, const CCoinControl* coin_control = nullptr) const; CFeeRate m_pay_tx_fee{DEFAULT_PAY_TX_FEE}; unsigned int m_confirm_target{DEFAULT_TX_CONFIRM_TARGET}; @@ -1445,8 +1444,6 @@ public: // Calculate the size of the transaction assuming all signatures are max size // Use DummySignatureCreator, which inserts 71 byte signatures everywhere. -// NOTE: this requires that all inputs must be in mapWallet (eg the tx should -// be IsAllFromMe). -int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig = false) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet); -int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector& txouts, bool use_max_sig = false); +int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const CCoinControl* coin_control = nullptr) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet); +int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector& txouts, const CCoinControl* coin_control = nullptr); #endif // BITCOIN_WALLET_WALLET_H