Skip to content

Commit f626594

Browse files
committed
rpc: bumpfee: use correct maximum signed tx size for fee calculation
More accurate than simply adding one byte per input, and properly handles the case where the original transaction happened to have very small signatures
1 parent d625b90 commit f626594

File tree

1 file changed

+35
-4
lines changed

1 file changed

+35
-4
lines changed

src/wallet/rpcwallet.cpp

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2664,6 +2664,33 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
26642664
return result;
26652665
}
26662666

2667+
// Calculate the size of the transaction assuming all signatures are max size
2668+
// Use DummySignatureCreator, which inserts 72 byte signatures everywhere.
2669+
// TODO: re-use this in CWallet::CreateTransaction (right now
2670+
// CreateTransaction uses the constructed dummy-signed tx to do a priority
2671+
// calculation, but we should be able to refactor after priority is removed).
2672+
// NOTE: this requires that all inputs must be in mapWallet (eg the tx should
2673+
// be IsAllFromMe).
2674+
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx)
2675+
{
2676+
CMutableTransaction txNew(tx);
2677+
std::vector<pair<CWalletTx *, unsigned int>> vCoins;
2678+
// Look up the inputs. We should have already checked that this transaction
2679+
// IsAllFromMe(ISMINE_SPENDABLE), so every input should already be in our
2680+
// wallet, with a valid index into the vout array.
2681+
for (auto& input : tx.vin) {
2682+
const auto mi = pwalletMain->mapWallet.find(input.prevout.hash);
2683+
assert(mi != pwalletMain->mapWallet.end() && input.prevout.n < mi->second.tx->vout.size());
2684+
vCoins.emplace_back(make_pair(&(mi->second), input.prevout.n));
2685+
}
2686+
if (!pwalletMain->DummySignTx(txNew, vCoins)) {
2687+
// This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
2688+
// implies that we can sign for every input.
2689+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction contains inputs that cannot be signed");
2690+
}
2691+
return GetVirtualTransactionSize(txNew);
2692+
}
2693+
26672694
UniValue bumpfee(const JSONRPCRequest& request)
26682695
{
26692696
if (!EnsureWalletIsAvailable(request.fHelp)) {
@@ -2769,9 +2796,9 @@ UniValue bumpfee(const JSONRPCRequest& request)
27692796
throw JSONRPCError(RPC_MISC_ERROR, "Transaction does not have a change output");
27702797
}
27712798

2772-
// signature sizes can vary by a byte, so add 1 for each input when calculating the new fee
2799+
// Calculate the expected size of the new transaction.
27732800
int64_t txSize = GetVirtualTransactionSize(*(wtx.tx));
2774-
const int64_t maxNewTxSize = txSize + wtx.tx->vin.size();
2801+
const int64_t maxNewTxSize = CalculateMaximumSignedTxSize(*wtx.tx);
27752802

27762803
// optional parameters
27772804
bool specifiedConfirmTarget = false;
@@ -2845,8 +2872,12 @@ UniValue bumpfee(const JSONRPCRequest& request)
28452872
nNewFeeRate = CFeeRate(nNewFee, maxNewTxSize);
28462873

28472874
// New fee rate must be at least old rate + minimum incremental relay rate
2848-
if (nNewFeeRate.GetFeePerK() < nOldFeeRate.GetFeePerK() + walletIncrementalRelayFee.GetFeePerK()) {
2849-
nNewFeeRate = CFeeRate(nOldFeeRate.GetFeePerK() + walletIncrementalRelayFee.GetFeePerK());
2875+
// walletIncrementalRelayFee.GetFeePerK() should be exact, because it's initialized
2876+
// in that unit (fee per kb).
2877+
// However, nOldFeeRate is a calculated value from the tx fee/size, so
2878+
// add 1 satoshi to the result, because it may have been rounded down.
2879+
if (nNewFeeRate.GetFeePerK() < nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK()) {
2880+
nNewFeeRate = CFeeRate(nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK());
28502881
nNewFee = nNewFeeRate.GetFee(maxNewTxSize);
28512882
}
28522883
}

0 commit comments

Comments
 (0)