bumpfee: extract weights of external inputs when bumping fee

When bumping the fee of a transaction containing external inputs,
determine the weights of those inputs. Because signatures can have a
variable size, the script is executed with a special SignatureChecker
which will compute the total weight of the signatures in the transaction
and the weight if they were all maximum size signatures. This allows us
to compute the maximum weight of the input for use during coin
selection.
This commit is contained in:
Andrew Chow 2021-10-05 21:41:54 -04:00
parent 612f1e44fe
commit a0c3afb898
2 changed files with 78 additions and 0 deletions

View file

@ -2,6 +2,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <consensus/validation.h>
#include <interfaces/chain.h>
#include <policy/fees.h>
#include <policy/policy.h>
@ -171,6 +172,7 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
// While we're here, calculate the input amount
std::map<COutPoint, Coin> coins;
CAmount input_value = 0;
std::vector<CTxOut> spent_outputs;
for (const CTxIn& txin : wtx.tx->vin) {
coins[txin.prevout]; // Create empty map entry keyed by prevout.
}
@ -187,6 +189,31 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
new_coin_control.SelectExternal(txin.prevout, coin.out);
}
input_value += coin.out.nValue;
spent_outputs.push_back(coin.out);
}
// Figure out if we need to compute the input weight, and do so if necessary
PrecomputedTransactionData txdata;
txdata.Init(*wtx.tx, std::move(spent_outputs), /* force=*/ true);
for (unsigned int i = 0; i < wtx.tx->vin.size(); ++i) {
const CTxIn& txin = wtx.tx->vin.at(i);
const Coin& coin = coins.at(txin.prevout);
if (new_coin_control.IsExternalSelected(txin.prevout)) {
// For external inputs, we estimate the size using the size of this input
int64_t input_weight = GetTransactionInputWeight(txin);
// Because signatures can have different sizes, we need to figure out all of the
// signature sizes and replace them with the max sized signature.
// In order to do this, we verify the script with a special SignatureChecker which
// will observe the signatures verified and record their sizes.
SignatureWeights weights;
TransactionSignatureChecker tx_checker(wtx.tx.get(), i, coin.out.nValue, txdata, MissingDataBehavior::FAIL);
SignatureWeightChecker size_checker(weights, tx_checker);
VerifyScript(txin.scriptSig, coin.out.scriptPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, size_checker);
// Add the difference between max and current to input_weight so that it represents the largest the input could be
input_weight += weights.GetWeightDiffToMax();
new_coin_control.SetInputWeight(txin.prevout, input_weight);
}
}
Result result = PreconditionChecks(wallet, wtx, errors);

View file

@ -5,6 +5,8 @@
#ifndef BITCOIN_WALLET_FEEBUMPER_H
#define BITCOIN_WALLET_FEEBUMPER_H
#include <consensus/consensus.h>
#include <script/interpreter.h>
#include <primitives/transaction.h>
class uint256;
@ -55,6 +57,55 @@ Result CommitTransaction(CWallet& wallet,
std::vector<bilingual_str>& errors,
uint256& bumped_txid);
struct SignatureWeights
{
private:
int m_sigs_count{0};
int64_t m_sigs_weight{0};
public:
void AddSigWeight(const size_t weight, const SigVersion sigversion)
{
switch (sigversion) {
case SigVersion::BASE:
m_sigs_weight += weight * WITNESS_SCALE_FACTOR;
m_sigs_count += 1 * WITNESS_SCALE_FACTOR;
break;
case SigVersion::WITNESS_V0:
m_sigs_weight += weight;
m_sigs_count++;
break;
case SigVersion::TAPROOT:
case SigVersion::TAPSCRIPT:
assert(false);
}
}
int64_t GetWeightDiffToMax() const
{
// Note: the witness scaling factor is already accounted for because the count is multiplied by it.
return (/* max signature size=*/ 72 * m_sigs_count) - m_sigs_weight;
}
};
class SignatureWeightChecker : public DeferringSignatureChecker
{
private:
SignatureWeights& m_weights;
public:
SignatureWeightChecker(SignatureWeights& weights, const BaseSignatureChecker& checker) : DeferringSignatureChecker(checker), m_weights(weights) {}
bool CheckECDSASignature(const std::vector<unsigned char>& sig, const std::vector<unsigned char>& pubkey, const CScript& script, SigVersion sigversion) const override
{
if (m_checker.CheckECDSASignature(sig, pubkey, script, sigversion)) {
m_weights.AddSigWeight(sig.size(), sigversion);
return true;
}
return false;
}
};
} // namespace feebumper
} // namespace wallet