Skip to content

Commit 55bf001

Browse files
committed
bumpfee: Allow original change position to be specified
If the user used a custom change address, it may not be detected as a change output, resulting in an additional change output being added to the bumped transaction. We can avoid this issue by allowing the user to specify the position of the change output.
1 parent 50422b7 commit 55bf001

File tree

3 files changed

+25
-8
lines changed

3 files changed

+25
-8
lines changed

src/wallet/feebumper.cpp

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ bool TransactionCanBeBumped(const CWallet& wallet, const uint256& txid)
155155
}
156156

157157
Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCoinControl& coin_control, std::vector<bilingual_str>& errors,
158-
CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx, bool require_mine)
158+
CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx, bool require_mine, std::optional<uint32_t> reduce_output)
159159
{
160160
// We are going to modify coin control later, copy to re-use
161161
CCoinControl new_coin_control(coin_control);
@@ -169,6 +169,12 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
169169
}
170170
const CWalletTx& wtx = it->second;
171171

172+
// Make sure that reduce_output is valid
173+
if (reduce_output.has_value() && reduce_output.value() >= wtx.tx->vout.size()) {
174+
errors.push_back(Untranslated("Change position is out of range"));
175+
return Result::INVALID_PARAMETER;
176+
}
177+
172178
// Retrieve all of the UTXOs and add them to coin control
173179
// While we're here, calculate the input amount
174180
std::map<COutPoint, Coin> coins;
@@ -226,14 +232,15 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
226232
// While we're here, calculate the output amount
227233
std::vector<CRecipient> recipients;
228234
CAmount output_value = 0;
229-
for (const auto& output : wtx.tx->vout) {
230-
if (!OutputIsChange(wallet, output)) {
231-
CRecipient recipient = {output.scriptPubKey, output.nValue, false};
232-
recipients.push_back(recipient);
233-
} else {
235+
for (size_t i = 0; i < wtx.tx->vout.size(); ++i) {
236+
const CTxOut& output = wtx.tx->vout.at(i);
237+
if ((reduce_output.has_value() && reduce_output.value() == i) || (!reduce_output.has_value() && OutputIsChange(wallet, output))) {
234238
CTxDestination change_dest;
235239
ExtractDestination(output.scriptPubKey, change_dest);
236240
new_coin_control.destChange = change_dest;
241+
} else {
242+
CRecipient recipient = {output.scriptPubKey, output.nValue, false};
243+
recipients.push_back(recipient);
237244
}
238245
output_value += output.nValue;
239246
}

src/wallet/feebumper.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ bool TransactionCanBeBumped(const CWallet& wallet, const uint256& txid);
4343
* @param[out] new_fee the fee that the bump transaction pays
4444
* @param[out] mtx The bump transaction itself
4545
* @param[in] require_mine Whether the original transaction must consist of inputs that can be spent by the wallet
46+
* @param[in] reduce_output The position of the change output to deduct the fee from in the transaction being bumped
4647
*/
4748
Result CreateRateBumpTransaction(CWallet& wallet,
4849
const uint256& txid,
@@ -51,7 +52,8 @@ Result CreateRateBumpTransaction(CWallet& wallet,
5152
CAmount& old_fee,
5253
CAmount& new_fee,
5354
CMutableTransaction& mtx,
54-
bool require_mine);
55+
bool require_mine,
56+
std::optional<uint32_t> reduce_output = std::nullopt);
5557

5658
//! Sign the new transaction,
5759
//! @return false if the tx couldn't be found or if it was

src/wallet/rpc/spend.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
967967
"are replaceable).\n"},
968968
{"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n"
969969
"\"" + FeeModes("\"\n\"") + "\""},
970+
{"reduce_output", RPCArg::Type::NUM, RPCArg::DefaultHint{"not set, detect change automatically"}, "The 0-based index of the output from which the additional fees will be deducted. In general, this should be the position of change output."},
970971
},
971972
RPCArgOptions{.oneline_description="options"}},
972973
},
@@ -1005,6 +1006,8 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
10051006
// optional parameters
10061007
coin_control.m_signal_bip125_rbf = true;
10071008

1009+
std::optional<uint32_t> reduce_output;
1010+
10081011
if (!request.params[1].isNull()) {
10091012
UniValue options = request.params[1];
10101013
RPCTypeCheckObj(options,
@@ -1014,6 +1017,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
10141017
{"fee_rate", UniValueType()}, // will be checked by AmountFromValue() in SetFeeEstimateMode()
10151018
{"replaceable", UniValueType(UniValue::VBOOL)},
10161019
{"estimate_mode", UniValueType(UniValue::VSTR)},
1020+
{"reduce_output", UniValueType(UniValue::VNUM)},
10171021
},
10181022
true, true);
10191023

@@ -1027,6 +1031,10 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
10271031
coin_control.m_signal_bip125_rbf = options["replaceable"].get_bool();
10281032
}
10291033
SetFeeEstimateMode(*pwallet, coin_control, conf_target, options["estimate_mode"], options["fee_rate"], /*override_min_fee=*/false);
1034+
1035+
if (options.exists("reduce_output")) {
1036+
reduce_output = options["reduce_output"].getInt<uint32_t>();
1037+
}
10301038
}
10311039

10321040
// Make sure the results are valid at least up to the most recent block
@@ -1044,7 +1052,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
10441052
CMutableTransaction mtx;
10451053
feebumper::Result res;
10461054
// Targeting feerate bump.
1047-
res = feebumper::CreateRateBumpTransaction(*pwallet, hash, coin_control, errors, old_fee, new_fee, mtx, /*require_mine=*/ !want_psbt);
1055+
res = feebumper::CreateRateBumpTransaction(*pwallet, hash, coin_control, errors, old_fee, new_fee, mtx, /*require_mine=*/ !want_psbt, reduce_output);
10481056
if (res != feebumper::Result::OK) {
10491057
switch(res) {
10501058
case feebumper::Result::INVALID_ADDRESS_OR_KEY:

0 commit comments

Comments
 (0)