Skip to content

Commit 0ea47ba

Browse files
committed
generalize bumpfee to add inputs when needed
1 parent c536dfb commit 0ea47ba

File tree

6 files changed

+153
-43
lines changed

6 files changed

+153
-43
lines changed

src/interfaces/wallet.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,13 @@ class WalletImpl : public Wallet
269269
CAmount& new_fee,
270270
CMutableTransaction& mtx) override
271271
{
272-
return feebumper::CreateTransaction(m_wallet.get(), txid, coin_control, total_fee, errors, old_fee, new_fee, mtx) ==
273-
feebumper::Result::OK;
272+
if (total_fee > 0) {
273+
return feebumper::CreateTotalBumpTransaction(m_wallet.get(), txid, coin_control, total_fee, errors, old_fee, new_fee, mtx) ==
274+
feebumper::Result::OK;
275+
} else {
276+
return feebumper::CreateRateBumpTransaction(m_wallet.get(), txid, coin_control, errors, old_fee, new_fee, mtx) ==
277+
feebumper::Result::OK;
278+
}
274279
}
275280
bool signBumpTransaction(CMutableTransaction& mtx) override { return feebumper::SignTransaction(m_wallet.get(), mtx); }
276281
bool commitBumpTransaction(const uint256& txid,

src/wallet/coincontrol.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ class CCoinControl
3636
bool m_avoid_partial_spends;
3737
//! Fee estimation mode to control arguments to estimateSmartFee
3838
FeeEstimateMode m_fee_mode;
39+
//! Minimum chain depth value for coin availability
40+
int m_min_depth{0};
3941

4042
CCoinControl()
4143
{

src/wallet/feebumper.cpp

Lines changed: 121 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,11 @@ bool TransactionCanBeBumped(const CWallet* wallet, const uint256& txid)
7575
return res == feebumper::Result::OK;
7676
}
7777

78-
Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoinControl& coin_control, CAmount total_fee, std::vector<std::string>& errors,
79-
CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx)
78+
Result CreateTotalBumpTransaction(const CWallet* wallet, const uint256& txid, const CCoinControl& coin_control, CAmount total_fee, std::vector<std::string>& errors,
79+
CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx)
8080
{
81+
new_fee = total_fee;
82+
8183
auto locked_chain = wallet->chain().lock();
8284
LOCK(wallet->cs_wallet);
8385
errors.clear();
@@ -121,7 +123,6 @@ Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoin
121123
// calculate the old fee and fee-rate
122124
old_fee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut();
123125
CFeeRate nOldFeeRate(old_fee, txSize);
124-
CFeeRate nNewFeeRate;
125126
// The wallet uses a conservative WALLET_INCREMENTAL_RELAY_FEE value to
126127
// future proof against changes to network wide policy for incremental relay
127128
// fee that our node may not be aware of.
@@ -131,34 +132,17 @@ Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoin
131132
walletIncrementalRelayFee = nodeIncrementalRelayFee;
132133
}
133134

134-
if (total_fee > 0) {
135-
CAmount minTotalFee = nOldFeeRate.GetFee(maxNewTxSize) + nodeIncrementalRelayFee.GetFee(maxNewTxSize);
136-
if (total_fee < minTotalFee) {
137-
errors.push_back(strprintf("Insufficient totalFee, must be at least %s (oldFee %s + incrementalFee %s)",
138-
FormatMoney(minTotalFee), FormatMoney(nOldFeeRate.GetFee(maxNewTxSize)), FormatMoney(nodeIncrementalRelayFee.GetFee(maxNewTxSize))));
139-
return Result::INVALID_PARAMETER;
140-
}
141-
CAmount requiredFee = GetRequiredFee(*wallet, maxNewTxSize);
142-
if (total_fee < requiredFee) {
143-
errors.push_back(strprintf("Insufficient totalFee (cannot be less than required fee %s)",
144-
FormatMoney(requiredFee)));
145-
return Result::INVALID_PARAMETER;
146-
}
147-
new_fee = total_fee;
148-
nNewFeeRate = CFeeRate(total_fee, maxNewTxSize);
149-
} else {
150-
new_fee = GetMinimumFee(*wallet, maxNewTxSize, coin_control, nullptr /* FeeCalculation */);
151-
nNewFeeRate = CFeeRate(new_fee, maxNewTxSize);
152-
153-
// New fee rate must be at least old rate + minimum incremental relay rate
154-
// walletIncrementalRelayFee.GetFeePerK() should be exact, because it's initialized
155-
// in that unit (fee per kb).
156-
// However, nOldFeeRate is a calculated value from the tx fee/size, so
157-
// add 1 satoshi to the result, because it may have been rounded down.
158-
if (nNewFeeRate.GetFeePerK() < nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK()) {
159-
nNewFeeRate = CFeeRate(nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK());
160-
new_fee = nNewFeeRate.GetFee(maxNewTxSize);
161-
}
135+
CAmount minTotalFee = nOldFeeRate.GetFee(maxNewTxSize) + nodeIncrementalRelayFee.GetFee(maxNewTxSize);
136+
if (total_fee < minTotalFee) {
137+
errors.push_back(strprintf("Insufficient totalFee, must be at least %s (oldFee %s + incrementalFee %s)",
138+
FormatMoney(minTotalFee), FormatMoney(nOldFeeRate.GetFee(maxNewTxSize)), FormatMoney(nodeIncrementalRelayFee.GetFee(maxNewTxSize))));
139+
return Result::INVALID_PARAMETER;
140+
}
141+
CAmount requiredFee = GetRequiredFee(*wallet, maxNewTxSize);
142+
if (total_fee < requiredFee) {
143+
errors.push_back(strprintf("Insufficient totalFee (cannot be less than required fee %s)",
144+
FormatMoney(requiredFee)));
145+
return Result::INVALID_PARAMETER;
162146
}
163147

164148
// Check that in all cases the new fee doesn't violate maxTxFee
@@ -175,14 +159,14 @@ Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoin
175159
// in a rare situation where the mempool minimum fee increased significantly since the fee estimation just a
176160
// moment earlier. In this case, we report an error to the user, who may use total_fee to make an adjustment.
177161
CFeeRate minMempoolFeeRate = wallet->chain().mempoolMinFee();
162+
CFeeRate nNewFeeRate = CFeeRate(total_fee, maxNewTxSize);
178163
if (nNewFeeRate.GetFeePerK() < minMempoolFeeRate.GetFeePerK()) {
179164
errors.push_back(strprintf(
180165
"New fee rate (%s) is lower than the minimum fee rate (%s) to get into the mempool -- "
181-
"the totalFee value should be at least %s or the settxfee value should be at least %s to add transaction",
166+
"the totalFee value should be at least %s to add transaction",
182167
FormatMoney(nNewFeeRate.GetFeePerK()),
183168
FormatMoney(minMempoolFeeRate.GetFeePerK()),
184-
FormatMoney(minMempoolFeeRate.GetFee(maxNewTxSize)),
185-
FormatMoney(minMempoolFeeRate.GetFeePerK())));
169+
FormatMoney(minMempoolFeeRate.GetFee(maxNewTxSize))));
186170
return Result::WALLET_ERROR;
187171
}
188172

@@ -212,6 +196,109 @@ Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoin
212196
}
213197
}
214198

199+
return Result::OK;
200+
}
201+
202+
203+
Result CreateRateBumpTransaction(CWallet* wallet, const uint256& txid, const CCoinControl& coin_control, std::vector<std::string>& errors,
204+
CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx)
205+
{
206+
// We are going to modify coin control later, copy to re-use
207+
CCoinControl new_coin_control(coin_control);
208+
209+
auto locked_chain = wallet->chain().lock();
210+
LOCK(wallet->cs_wallet);
211+
errors.clear();
212+
auto it = wallet->mapWallet.find(txid);
213+
if (it == wallet->mapWallet.end()) {
214+
errors.push_back("Invalid or non-wallet transaction id");
215+
return Result::INVALID_ADDRESS_OR_KEY;
216+
}
217+
const CWalletTx& wtx = it->second;
218+
219+
Result result = PreconditionChecks(*locked_chain, wallet, wtx, errors);
220+
if (result != Result::OK) {
221+
return result;
222+
}
223+
224+
// Fill in recipients(and preserve a single change key if there is one)
225+
std::vector<CRecipient> recipients;
226+
for (const auto& output : wtx.tx->vout) {
227+
if (!wallet->IsChange(output)) {
228+
CRecipient recipient = {output.scriptPubKey, output.nValue, false};
229+
recipients.push_back(recipient);
230+
} else {
231+
CTxDestination change_dest;
232+
ExtractDestination(output.scriptPubKey, change_dest);
233+
new_coin_control.destChange = change_dest;
234+
}
235+
}
236+
237+
// Get the fee rate of the original transaction. This is calculated from
238+
// the tx fee/vsize, so it may have been rounded down. Add 1 satoshi to the
239+
// result.
240+
old_fee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut();
241+
int64_t txSize = GetVirtualTransactionSize(*(wtx.tx));
242+
// Feerate of thing we are bumping
243+
CFeeRate feerate(old_fee, txSize);
244+
feerate += CFeeRate(1);
245+
246+
// The node has a configurable incremental relay fee. Increment the fee by
247+
// the minimum of that and the wallet's conservative
248+
// WALLET_INCREMENTAL_RELAY_FEE value to future proof against changes to
249+
// network wide policy for incremental relay fee that our node may not be
250+
// aware of. This ensures we're over the over the required relay fee rate
251+
// (BIP 125 rule 4). The replacement tx will be at least as large as the
252+
// original tx, so the total fee will be greater (BIP 125 rule 3)
253+
CFeeRate node_incremental_relay_fee = wallet->chain().relayIncrementalFee();
254+
CFeeRate wallet_incremental_relay_fee = CFeeRate(WALLET_INCREMENTAL_RELAY_FEE);
255+
feerate += std::max(node_incremental_relay_fee, wallet_incremental_relay_fee);
256+
257+
// Fee rate must also be at least the wallet's GetMinimumFeeRate
258+
CFeeRate min_feerate(GetMinimumFeeRate(*wallet, new_coin_control, /* feeCalc */ nullptr));
259+
260+
// Set the required fee rate for the replacement transaction in coin control.
261+
new_coin_control.m_feerate = std::max(feerate, min_feerate);
262+
263+
// Fill in required inputs we are double-spending(all of them)
264+
// N.B.: bip125 doesn't require all the inputs in the replaced transaction to be
265+
// used in the replacement transaction, but it's very important for wallets to make
266+
// sure that happens. If not, it would be possible to bump a transaction A twice to
267+
// A2 and A3 where A2 and A3 don't conflict (or alternatively bump A to A2 and A2
268+
// to A3 where A and A3 don't conflict). If both later get confirmed then the sender
269+
// has accidentally double paid.
270+
for (const auto& inputs : wtx.tx->vin) {
271+
new_coin_control.Select(COutPoint(inputs.prevout));
272+
}
273+
new_coin_control.fAllowOtherInputs = true;
274+
275+
// We cannot source new unconfirmed inputs(bip125 rule 2)
276+
new_coin_control.m_min_depth = 1;
277+
278+
CTransactionRef tx_new = MakeTransactionRef();
279+
CReserveKey reservekey(wallet);
280+
CAmount fee_ret;
281+
int change_pos_in_out = -1; // No requested location for change
282+
std::string fail_reason;
283+
if (!wallet->CreateTransaction(*locked_chain, recipients, tx_new, reservekey, fee_ret, change_pos_in_out, fail_reason, new_coin_control, false)) {
284+
errors.push_back("Unable to create transaction: " + fail_reason);
285+
return Result::WALLET_ERROR;
286+
}
287+
288+
// If change key hasn't been ReturnKey'ed by this point, we take it out of keypool
289+
reservekey.KeepKey();
290+
291+
// Write back new fee if successful
292+
new_fee = fee_ret;
293+
294+
// Write back transaction
295+
mtx = CMutableTransaction(*tx_new);
296+
// Mark new tx not replaceable, if requested.
297+
if (!coin_control.m_signal_bip125_rbf.get_value_or(wallet->m_signal_rbf)) {
298+
for (auto& input : mtx.vin) {
299+
if (input.nSequence < 0xfffffffe) input.nSequence = 0xfffffffe;
300+
}
301+
}
215302

216303
return Result::OK;
217304
}

src/wallet/feebumper.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ enum class Result
2828
//! Return whether transaction can be bumped.
2929
bool TransactionCanBeBumped(const CWallet* wallet, const uint256& txid);
3030

31-
//! Create bumpfee transaction.
32-
Result CreateTransaction(const CWallet* wallet,
31+
//! Create bumpfee transaction based on total amount.
32+
Result CreateTotalBumpTransaction(const CWallet* wallet,
3333
const uint256& txid,
3434
const CCoinControl& coin_control,
3535
CAmount total_fee,
@@ -38,6 +38,15 @@ Result CreateTransaction(const CWallet* wallet,
3838
CAmount& new_fee,
3939
CMutableTransaction& mtx);
4040

41+
//! Create bumpfee transaction based on feerate estimates.
42+
Result CreateRateBumpTransaction(CWallet* wallet,
43+
const uint256& txid,
44+
const CCoinControl& coin_control,
45+
std::vector<std::string>& errors,
46+
CAmount& old_fee,
47+
CAmount& new_fee,
48+
CMutableTransaction& mtx);
49+
4150
//! Sign the new transaction,
4251
//! @return false if the tx couldn't be found or if it was
4352
//! impossible to create the signature(s)

src/wallet/rpcwallet.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3167,9 +3167,9 @@ static UniValue bumpfee(const JSONRPCRequest& request)
31673167
RPCHelpMan{"bumpfee",
31683168
"\nBumps the fee of an opt-in-RBF transaction T, replacing it with a new transaction B.\n"
31693169
"An opt-in RBF transaction with the given txid must be in the wallet.\n"
3170-
"The command will pay the additional fee by decreasing (or perhaps removing) its change output.\n"
3171-
"If the change output is not big enough to cover the increased fee, the command will currently fail\n"
3172-
"instead of adding new inputs to compensate. (A future implementation could improve this.)\n"
3170+
"The command will pay the additional fee by reducing change outputs or adding inputs when necessary. It may add a new change output if one does not already exist.\n"
3171+
"If `totalFee` is given, adding inputs is not supported, so there must be a single change output that is big enough or it will fail.\n"
3172+
"All inputs in the original transaction will be included in the replacement transaction.\n"
31733173
"The command will fail if the wallet or mempool contains a transaction that spends one of T's outputs.\n"
31743174
"By default, the new fee will be calculated automatically using estimatesmartfee.\n"
31753175
"The user can specify a confirmation target for estimatesmartfee.\n"
@@ -3266,7 +3266,14 @@ static UniValue bumpfee(const JSONRPCRequest& request)
32663266
CAmount old_fee;
32673267
CAmount new_fee;
32683268
CMutableTransaction mtx;
3269-
feebumper::Result res = feebumper::CreateTransaction(pwallet, hash, coin_control, totalFee, errors, old_fee, new_fee, mtx);
3269+
feebumper::Result res;
3270+
if (totalFee > 0) {
3271+
// Targeting total fee bump. Requires a change output of sufficient size.
3272+
res = feebumper::CreateTotalBumpTransaction(pwallet, hash, coin_control, totalFee, errors, old_fee, new_fee, mtx);
3273+
} else {
3274+
// Targeting feerate bump.
3275+
res = feebumper::CreateRateBumpTransaction(pwallet, hash, coin_control, errors, old_fee, new_fee, mtx);
3276+
}
32703277
if (res != feebumper::Result::OK) {
32713278
switch(res) {
32723279
case feebumper::Result::INVALID_ADDRESS_OR_KEY:

src/wallet/wallet.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2736,7 +2736,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
27362736
LOCK(cs_wallet);
27372737
{
27382738
std::vector<COutput> vAvailableCoins;
2739-
AvailableCoins(*locked_chain, vAvailableCoins, true, &coin_control);
2739+
AvailableCoins(*locked_chain, vAvailableCoins, true, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0, coin_control.m_min_depth);
27402740
CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy
27412741

27422742
// Create change script that will be used if we need change

0 commit comments

Comments
 (0)