Calculate and store the number of bytes required to spend an input

This commit is contained in:
Andrew Chow 2018-03-05 16:37:24 -05:00
parent a34ac6ae07
commit 12ec29d3bb
8 changed files with 127 additions and 68 deletions

View File

@ -101,5 +101,10 @@ static inline int64_t GetBlockWeight(const CBlock& block)
{ {
return ::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * (WITNESS_SCALE_FACTOR - 1) + ::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION); return ::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * (WITNESS_SCALE_FACTOR - 1) + ::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION);
} }
static inline int64_t GetTransationInputWeight(const CTxIn& txin)
{
// scriptWitness size is added here because witnesses and txins are split up in segwit serialization.
return ::GetSerializeSize(txin, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * (WITNESS_SCALE_FACTOR - 1) + ::GetSerializeSize(txin, SER_NETWORK, PROTOCOL_VERSION) + ::GetSerializeSize(txin.scriptWitness.stack, SER_NETWORK, PROTOCOL_VERSION);
}
#endif // BITCOIN_CONSENSUS_VALIDATION_H #endif // BITCOIN_CONSENSUS_VALIDATION_H

View File

@ -258,3 +258,8 @@ int64_t GetVirtualTransactionSize(const CTransaction& tx, int64_t nSigOpCost)
{ {
return GetVirtualTransactionSize(GetTransactionWeight(tx), nSigOpCost); return GetVirtualTransactionSize(GetTransactionWeight(tx), nSigOpCost);
} }
int64_t GetVirtualTransactionInputSize(const CTxIn& txin, int64_t nSigOpCost)
{
return GetVirtualTransactionSize(GetTransationInputWeight(txin), nSigOpCost);
}

View File

@ -102,5 +102,6 @@ extern unsigned int nBytesPerSigOp;
/** Compute the virtual transaction size (weight reinterpreted as bytes). */ /** Compute the virtual transaction size (weight reinterpreted as bytes). */
int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost); int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost);
int64_t GetVirtualTransactionSize(const CTransaction& tx, int64_t nSigOpCost = 0); int64_t GetVirtualTransactionSize(const CTransaction& tx, int64_t nSigOpCost = 0);
int64_t GetVirtualTransactionInputSize(const CTxIn& tx, int64_t nSigOpCost = 0);
#endif // BITCOIN_POLICY_POLICY_H #endif // BITCOIN_POLICY_POLICY_H

View File

@ -194,11 +194,16 @@ SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nI
return data; return data;
} }
void UpdateInput(CTxIn& input, const SignatureData& data)
{
input.scriptSig = data.scriptSig;
input.scriptWitness = data.scriptWitness;
}
void UpdateTransaction(CMutableTransaction& tx, unsigned int nIn, const SignatureData& data) void UpdateTransaction(CMutableTransaction& tx, unsigned int nIn, const SignatureData& data)
{ {
assert(tx.vin.size() > nIn); assert(tx.vin.size() > nIn);
tx.vin[nIn].scriptSig = data.scriptSig; UpdateInput(tx.vin[nIn], data);
tx.vin[nIn].scriptWitness = data.scriptWitness;
} }
bool SignSignature(const CKeyStore &keystore, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CAmount& amount, int nHashType) bool SignSignature(const CKeyStore &keystore, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CAmount& amount, int nHashType)

View File

@ -80,6 +80,7 @@ SignatureData CombineSignatures(const CScript& scriptPubKey, const BaseSignature
/** Extract signature data from a transaction, and insert it. */ /** Extract signature data from a transaction, and insert it. */
SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn); SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn);
void UpdateTransaction(CMutableTransaction& tx, unsigned int nIn, const SignatureData& data); void UpdateTransaction(CMutableTransaction& tx, unsigned int nIn, const SignatureData& data);
void UpdateInput(CTxIn& input, const SignatureData& data);
/* Check whether we know how to sign for an output like this, assuming we /* Check whether we know how to sign for an output like this, assuming we
* have all private keys. While this function does not need private keys, the passed * have all private keys. While this function does not need private keys, the passed

View File

@ -16,33 +16,6 @@
#include <util.h> #include <util.h>
#include <net.h> #include <net.h>
// Calculate the size of the transaction assuming all signatures are max size
// Use DummySignatureCreator, which inserts 72 byte signatures everywhere.
// TODO: re-use this in CWallet::CreateTransaction (right now
// CreateTransaction uses the constructed dummy-signed tx to do a priority
// calculation, but we should be able to refactor after priority is removed).
// NOTE: this requires that all inputs must be in mapWallet (eg the tx should
// be IsAllFromMe).
static int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet)
{
CMutableTransaction txNew(tx);
std::vector<CInputCoin> vCoins;
// 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.
for (auto& input : tx.vin) {
const auto mi = wallet->mapWallet.find(input.prevout.hash);
assert(mi != wallet->mapWallet.end() && input.prevout.n < mi->second.tx->vout.size());
vCoins.emplace_back(CInputCoin(&(mi->second), input.prevout.n));
}
if (!wallet->DummySignTx(txNew, vCoins)) {
// This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
// implies that we can sign for every input.
return -1;
}
return GetVirtualTransactionSize(txNew);
}
//! Check whether transaction has descendant in wallet or mempool, or has been //! Check whether transaction has descendant in wallet or mempool, or has been
//! mined, or conflicts with a mined transaction. Return a feebumper::Result. //! mined, or conflicts with a mined transaction. Return a feebumper::Result.
static feebumper::Result PreconditionChecks(const CWallet* wallet, const CWalletTx& wtx, std::vector<std::string>& errors) static feebumper::Result PreconditionChecks(const CWallet* wallet, const CWalletTx& wtx, std::vector<std::string>& errors)

View File

@ -1543,6 +1543,79 @@ int CWalletTx::GetRequestCount() const
return nRequests; 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;
} else {
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<CTxOut> &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<CTxOut> 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.hash);
if (mi == wallet->mapWallet.end()) {
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);
}
// txouts needs to be in the order of tx.vin
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& 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(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<COutputEntry>& listReceived, void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived,
std::list<COutputEntry>& listSent, CAmount& nFee, std::string& strSentAccount, const isminefilter& filter) const std::list<COutputEntry>& listSent, CAmount& nFee, std::string& strSentAccount, const isminefilter& filter) const
{ {
@ -2752,7 +2825,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
assert(txNew.nLockTime < LOCKTIME_THRESHOLD); assert(txNew.nLockTime < LOCKTIME_THRESHOLD);
FeeCalculation feeCalc; FeeCalculation feeCalc;
CAmount nFeeNeeded; CAmount nFeeNeeded;
unsigned int nBytes; int nBytes;
{ {
std::set<CInputCoin> setCoins; std::set<CInputCoin> setCoins;
LOCK2(cs_main, cs_wallet); LOCK2(cs_main, cs_wallet);
@ -2903,20 +2976,12 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
txNew.vin.push_back(CTxIn(coin.outpoint,CScript(), txNew.vin.push_back(CTxIn(coin.outpoint,CScript(),
nSequence)); nSequence));
// Fill in dummy signatures for fee calculation. nBytes = CalculateMaximumSignedTxSize(txNew, this);
if (!DummySignTx(txNew, setCoins)) { if (nBytes < 0) {
strFailReason = _("Signing transaction failed"); strFailReason = _("Signing transaction failed");
return false; return false;
} }
nBytes = GetVirtualTransactionSize(txNew);
// Remove scriptSigs to eliminate the fee calculation dummy signatures
for (auto& vin : txNew.vin) {
vin.scriptSig = CScript();
vin.scriptWitness.SetNull();
}
nFeeNeeded = GetMinimumFee(nBytes, coin_control, ::mempool, ::feeEstimator, &feeCalc); nFeeNeeded = GetMinimumFee(nBytes, coin_control, ::mempool, ::feeEstimator, &feeCalc);
if (feeCalc.reason == FeeReason::FALLBACK && !g_wallet_allow_fallback_fee) { if (feeCalc.reason == FeeReason::FALLBACK && !g_wallet_allow_fallback_fee) {
// eventually allow a fallback fee // eventually allow a fallback fee

View File

@ -269,6 +269,9 @@ public:
bool IsCoinBase() const { return tx->IsCoinBase(); } bool IsCoinBase() const { return tx->IsCoinBase(); }
}; };
//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. * 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 block chain. * It includes any unrecorded transactions needed to link it back to the block chain.
@ -462,6 +465,12 @@ public:
CAmount GetAvailableWatchOnlyCredit(const bool fUseCache=true) const; CAmount GetAvailableWatchOnlyCredit(const bool fUseCache=true) const;
CAmount GetChange() const; CAmount 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<COutputEntry>& listReceived, void GetAmounts(std::list<COutputEntry>& listReceived,
std::list<COutputEntry>& listSent, CAmount& nFee, std::string& strSentAccount, const isminefilter& filter) const; std::list<COutputEntry>& listSent, CAmount& nFee, std::string& strSentAccount, const isminefilter& filter) const;
@ -525,6 +534,9 @@ public:
int i; int i;
int nDepth; 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 */ /** Whether we have the private keys to spend this output */
bool fSpendable; bool fSpendable;
@ -540,7 +552,12 @@ public:
COutput(const CWalletTx *txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, bool fSafeIn) COutput(const CWalletTx *txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, bool fSafeIn)
{ {
tx = txIn; i = iIn; nDepth = nDepthIn; fSpendable = fSpendableIn; fSolvable = fSolvableIn; fSafe = fSafeIn; tx = txIn; i = iIn; nDepth = nDepthIn; 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; std::string ToString() const;
@ -981,8 +998,14 @@ public:
void ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& entries); void ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& entries);
bool AddAccountingEntry(const CAccountingEntry&); bool AddAccountingEntry(const CAccountingEntry&);
bool AddAccountingEntry(const CAccountingEntry&, CWalletDB *pwalletdb); bool AddAccountingEntry(const CAccountingEntry&, CWalletDB *pwalletdb);
template <typename ContainerType> bool DummySignTx(CMutableTransaction &txNew, const std::set<CTxOut> &txouts) const
bool DummySignTx(CMutableTransaction &txNew, const ContainerType &coins) const; {
std::vector<CTxOut> 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<CTxOut> &txouts) const;
bool DummySignInput(CTxIn &tx_in, const CTxOut &txout) const;
static CFeeRate minTxFee; static CFeeRate minTxFee;
static CFeeRate fallbackFee; static CFeeRate fallbackFee;
@ -1227,31 +1250,6 @@ public:
} }
}; };
// Helper for producing a bunch of max-sized low-S signatures (eg 72 bytes)
// ContainerType is meant to hold pair<CWalletTx *, int>, and be iterable
// so that each entry corresponds to each vIn, in order.
template <typename ContainerType>
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 = OUTPUT_TYPE_DEFAULT); OutputType ParseOutputType(const std::string& str, OutputType default_type = OUTPUT_TYPE_DEFAULT);
const std::string& FormatOutputType(OutputType type); const std::string& FormatOutputType(OutputType type);
@ -1299,4 +1297,10 @@ public:
} }
}; };
// 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<CTxOut>& txouts);
#endif // BITCOIN_WALLET_WALLET_H #endif // BITCOIN_WALLET_WALLET_H