Skip to content

Commit a0c3afb

Browse files
committed
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.
1 parent 612f1e4 commit a0c3afb

File tree

2 files changed

+78
-0
lines changed

2 files changed

+78
-0
lines changed

src/wallet/feebumper.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Distributed under the MIT software license, see the accompanying
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

5+
#include <consensus/validation.h>
56
#include <interfaces/chain.h>
67
#include <policy/fees.h>
78
#include <policy/policy.h>
@@ -171,6 +172,7 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
171172
// While we're here, calculate the input amount
172173
std::map<COutPoint, Coin> coins;
173174
CAmount input_value = 0;
175+
std::vector<CTxOut> spent_outputs;
174176
for (const CTxIn& txin : wtx.tx->vin) {
175177
coins[txin.prevout]; // Create empty map entry keyed by prevout.
176178
}
@@ -187,6 +189,31 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
187189
new_coin_control.SelectExternal(txin.prevout, coin.out);
188190
}
189191
input_value += coin.out.nValue;
192+
spent_outputs.push_back(coin.out);
193+
}
194+
195+
// Figure out if we need to compute the input weight, and do so if necessary
196+
PrecomputedTransactionData txdata;
197+
txdata.Init(*wtx.tx, std::move(spent_outputs), /* force=*/ true);
198+
for (unsigned int i = 0; i < wtx.tx->vin.size(); ++i) {
199+
const CTxIn& txin = wtx.tx->vin.at(i);
200+
const Coin& coin = coins.at(txin.prevout);
201+
202+
if (new_coin_control.IsExternalSelected(txin.prevout)) {
203+
// For external inputs, we estimate the size using the size of this input
204+
int64_t input_weight = GetTransactionInputWeight(txin);
205+
// Because signatures can have different sizes, we need to figure out all of the
206+
// signature sizes and replace them with the max sized signature.
207+
// In order to do this, we verify the script with a special SignatureChecker which
208+
// will observe the signatures verified and record their sizes.
209+
SignatureWeights weights;
210+
TransactionSignatureChecker tx_checker(wtx.tx.get(), i, coin.out.nValue, txdata, MissingDataBehavior::FAIL);
211+
SignatureWeightChecker size_checker(weights, tx_checker);
212+
VerifyScript(txin.scriptSig, coin.out.scriptPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, size_checker);
213+
// Add the difference between max and current to input_weight so that it represents the largest the input could be
214+
input_weight += weights.GetWeightDiffToMax();
215+
new_coin_control.SetInputWeight(txin.prevout, input_weight);
216+
}
190217
}
191218

192219
Result result = PreconditionChecks(wallet, wtx, errors);

src/wallet/feebumper.h

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#ifndef BITCOIN_WALLET_FEEBUMPER_H
66
#define BITCOIN_WALLET_FEEBUMPER_H
77

8+
#include <consensus/consensus.h>
9+
#include <script/interpreter.h>
810
#include <primitives/transaction.h>
911

1012
class uint256;
@@ -55,6 +57,55 @@ Result CommitTransaction(CWallet& wallet,
5557
std::vector<bilingual_str>& errors,
5658
uint256& bumped_txid);
5759

60+
struct SignatureWeights
61+
{
62+
private:
63+
int m_sigs_count{0};
64+
int64_t m_sigs_weight{0};
65+
66+
public:
67+
void AddSigWeight(const size_t weight, const SigVersion sigversion)
68+
{
69+
switch (sigversion) {
70+
case SigVersion::BASE:
71+
m_sigs_weight += weight * WITNESS_SCALE_FACTOR;
72+
m_sigs_count += 1 * WITNESS_SCALE_FACTOR;
73+
break;
74+
case SigVersion::WITNESS_V0:
75+
m_sigs_weight += weight;
76+
m_sigs_count++;
77+
break;
78+
case SigVersion::TAPROOT:
79+
case SigVersion::TAPSCRIPT:
80+
assert(false);
81+
}
82+
}
83+
84+
int64_t GetWeightDiffToMax() const
85+
{
86+
// Note: the witness scaling factor is already accounted for because the count is multiplied by it.
87+
return (/* max signature size=*/ 72 * m_sigs_count) - m_sigs_weight;
88+
}
89+
};
90+
91+
class SignatureWeightChecker : public DeferringSignatureChecker
92+
{
93+
private:
94+
SignatureWeights& m_weights;
95+
96+
public:
97+
SignatureWeightChecker(SignatureWeights& weights, const BaseSignatureChecker& checker) : DeferringSignatureChecker(checker), m_weights(weights) {}
98+
99+
bool CheckECDSASignature(const std::vector<unsigned char>& sig, const std::vector<unsigned char>& pubkey, const CScript& script, SigVersion sigversion) const override
100+
{
101+
if (m_checker.CheckECDSASignature(sig, pubkey, script, sigversion)) {
102+
m_weights.AddSigWeight(sig.size(), sigversion);
103+
return true;
104+
}
105+
return false;
106+
}
107+
};
108+
58109
} // namespace feebumper
59110
} // namespace wallet
60111

0 commit comments

Comments
 (0)