diff --git a/src/policy/policy.h b/src/policy/policy.h --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -14,6 +14,7 @@ class CCoinsViewCache; class CTransaction; +class CTxIn; class CTxOut; /** @@ -133,9 +134,15 @@ unsigned int bytes_per_sigop); int64_t GetVirtualTransactionSize(const CTransaction &tx, int64_t nSigOpCost, unsigned int bytes_per_sigop); +int64_t GetVirtualTransactionInputSize(const CTxIn &txin, int64_t nSigOpCost, + unsigned int bytes_per_sigop); static inline int64_t GetVirtualTransactionSize(const CTransaction &tx) { return GetVirtualTransactionSize(tx, 0, 0); } +static inline int64_t GetVirtualTransactionInputSize(const CTxIn &txin) { + return GetVirtualTransactionInputSize(txin, 0, 0); +} + #endif // BITCOIN_POLICY_POLICY_H diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -189,3 +189,10 @@ ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION), nSigOpCost, bytes_per_sigop); } + +int64_t GetVirtualTransactionInputSize(const CTxIn &txin, int64_t nSigOpCost, + unsigned int bytes_per_sigop) { + return GetVirtualTransactionSize( + ::GetSerializeSize(txin, SER_NETWORK, PROTOCOL_VERSION), nSigOpCost, + bytes_per_sigop); +} diff --git a/src/script/sign.h b/src/script/sign.h --- a/src/script/sign.h +++ b/src/script/sign.h @@ -105,5 +105,6 @@ unsigned int nIn); void UpdateTransaction(CMutableTransaction &tx, unsigned int nIn, const SignatureData &data); +void UpdateInput(CTxIn &input, const SignatureData &data); #endif // BITCOIN_SCRIPT_SIGN_H diff --git a/src/script/sign.cpp b/src/script/sign.cpp --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -174,10 +174,14 @@ return data; } +void UpdateInput(CTxIn &input, const SignatureData &data) { + input.scriptSig = data.scriptSig; +} + void UpdateTransaction(CMutableTransaction &tx, unsigned int nIn, const SignatureData &data) { assert(tx.vin.size() > nIn); - tx.vin[nIn].scriptSig = data.scriptSig; + UpdateInput(tx.vin[nIn], data); } bool SignSignature(const CKeyStore &keystore, const CScript &fromPubKey, diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -267,6 +267,10 @@ bool IsImmatureCoinBase() const; }; +// Get the marginal bytes of spending the specified output +int CalculateMaximumSignedInputSize(const CTxOut &txout, + const CWallet *pwallet); + /** * A transaction with a bunch of additional info that only the owner cares * about. It includes any unrecorded transactions needed to link it back to the @@ -450,6 +454,12 @@ Amount GetAvailableWatchOnlyCredit(const bool fUseCache = true) const; Amount GetChange() const; + // Get the marginal bytes if spending the specified output from this + // transaction + int GetSpendSize(unsigned int out) const { + return CalculateMaximumSignedInputSize(tx->vout[out], pwallet); + } + void GetAmounts(std::list &listReceived, std::list &listSent, Amount &nFee, std::string &strSentAccount, @@ -516,6 +526,12 @@ int i; int nDepth; + /** + * Pre-computed estimated size of this output as a fully-signed input in a + * transaction. Can be -1 if it could not be calculated. + */ + int nInputBytes; + /** Whether we have the private keys to spend this output */ bool fSpendable; @@ -537,6 +553,12 @@ fSpendable = fSpendableIn; fSolvable = fSolvableIn; fSafe = fSafeIn; + nInputBytes = -1; + // If known and signable by the given wallet, compute nInputBytes + // Failure will keep this value -1 + if (fSpendable && tx) { + nInputBytes = tx->GetSpendSize(i); + } } std::string ToString() const; @@ -1048,9 +1070,15 @@ std::list &entries); bool AddAccountingEntry(const CAccountingEntry &); bool AddAccountingEntry(const CAccountingEntry &, CWalletDB *pwalletdb); - template bool DummySignTx(CMutableTransaction &txNew, - const ContainerType &coins) const; + const std::set &txouts) const { + std::vector v_txouts(txouts.size()); + std::copy(txouts.begin(), txouts.end(), v_txouts.begin()); + return DummySignTx(txNew, v_txouts); + } + bool DummySignTx(CMutableTransaction &txNew, + const std::vector &txouts) const; + bool DummySignInput(CTxIn &tx_in, const CTxOut &txout) const; static CFeeRate fallbackFee; @@ -1311,31 +1339,6 @@ } }; -// Helper for producing a bunch of max-sized low-S signatures (eg 72 bytes) -// ContainerType is meant to hold pair, and be iterable so -// that each entry corresponds to each vIn, in order. -template -bool CWallet::DummySignTx(CMutableTransaction &txNew, - const ContainerType &coins) const { - // Fill in dummy signatures for fee calculation. - int nIn = 0; - for (const auto &coin : coins) { - const CScript &scriptPubKey = coin.txout.scriptPubKey; - SignatureData sigdata; - - if (!ProduceSignature(DummySignatureCreator(this), scriptPubKey, - sigdata)) { - return false; - } else { - UpdateTransaction(txNew, nIn, sigdata); - } - - nIn++; - } - - return true; -} - OutputType ParseOutputType(const std::string &str, OutputType default_type = OutputType::DEFAULT); const std::string &FormatOutputType(OutputType type); @@ -1384,4 +1387,14 @@ } }; +// Calculate the size of the transaction assuming all signatures are max size +// Use DummySignatureCreator, which inserts 72 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); +int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, + const CWallet *wallet, + const std::vector &txouts); + #endif // BITCOIN_WALLET_WALLET_H diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1619,6 +1619,77 @@ return nRequests; } +// Helper for producing a max-sized low-S signature (eg 72 bytes) +bool CWallet::DummySignInput(CTxIn &tx_in, const CTxOut &txout) const { + // Fill in dummy signatures for fee calculation. + const CScript &scriptPubKey = txout.scriptPubKey; + SignatureData sigdata; + + if (!ProduceSignature(DummySignatureCreator(this), scriptPubKey, sigdata)) { + return false; + } + + UpdateInput(tx_in, sigdata); + return true; +} + +// Helper for producing a bunch of max-sized low-S signatures (eg 72 bytes) +bool CWallet::DummySignTx(CMutableTransaction &txNew, + const std::vector &txouts) const { + // Fill in dummy signatures for fee calculation. + int nIn = 0; + for (const auto &txout : txouts) { + if (!DummySignInput(txNew.vin[nIn], txout)) { + return false; + } + + nIn++; + } + return true; +} + +int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, + const CWallet *wallet) { + 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. + for (auto &input : tx.vin) { + const auto mi = wallet->mapWallet.find(input.prevout.GetTxId()); + if (mi == wallet->mapWallet.end()) { + return -1; + } + assert(input.prevout.GetN() < mi->second.tx->vout.size()); + txouts.emplace_back(mi->second.tx->vout[input.prevout.GetN()]); + } + return CalculateMaximumSignedTxSize(tx, wallet, txouts); +} + +// txouts needs to be in the order of tx.vin +int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, + const CWallet *wallet, + const std::vector &txouts) { + CMutableTransaction txNew(tx); + if (!wallet->DummySignTx(txNew, txouts)) { + // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE) + // implies that we can sign for every input. + return -1; + } + return GetVirtualTransactionSize(CTransaction(txNew)); +} + +int CalculateMaximumSignedInputSize(const CTxOut &txout, + const CWallet *wallet) { + CMutableTransaction txn; + txn.vin.push_back(CTxIn(COutPoint())); + if (!wallet->DummySignInput(txn.vin[0], txout)) { + // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE) + // implies that we can sign for every input. + return -1; + } + return GetVirtualTransactionInputSize(txn.vin[0]); +} + void CWalletTx::GetAmounts(std::list &listReceived, std::list &listSent, Amount &nFee, std::string &strSentAccount, @@ -3100,21 +3171,13 @@ std::numeric_limits::max() - 1)); } - // Fill in dummy signatures for fee calculation. - if (!DummySignTx(txNew, setCoins)) { + CTransaction txNewConst(txNew); + int nBytes = CalculateMaximumSignedTxSize(txNewConst, this); + if (nBytes < 0) { strFailReason = _("Signing transaction failed"); return false; } - CTransaction txNewConst(txNew); - unsigned int nBytes = txNewConst.GetTotalSize(); - - // Remove scriptSigs to eliminate the fee calculation dummy - // signatures. - for (auto &vin : txNew.vin) { - vin.scriptSig = CScript(); - } - Amount nFeeNeeded = GetMinimumFee(nBytes, coinControl, g_mempool); // If we made it here and we aren't even able to meet the relay fee