Skip to content

Commit b3db8c9

Browse files
furszymurchandamus
andcommitted
rpc: bumpfee, improve doc for 'reduce_output' arg
The current argument name and description are dangerous as it don't describe the case where the user selects the recipient output as the change address. This one could end up been increased by the inputs minus outputs remainder. Which, when bumpfee adds new inputs to the transaction, leads the process to send more coins to the recipient. Which is not what the user would expect from a 'reduce_output' param naming. Co-authored-by: Murch <[email protected]>
1 parent 1d4846a commit b3db8c9

File tree

5 files changed

+34
-30
lines changed

5 files changed

+34
-30
lines changed

src/rpc/client.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -263,13 +263,13 @@ static const CRPCConvertParam vRPCConvertParams[] =
263263
{ "bumpfee", 1, "fee_rate"},
264264
{ "bumpfee", 1, "replaceable"},
265265
{ "bumpfee", 1, "outputs"},
266-
{ "bumpfee", 1, "reduce_output"},
266+
{ "bumpfee", 1, "original_change_index"},
267267
{ "psbtbumpfee", 1, "options" },
268268
{ "psbtbumpfee", 1, "conf_target"},
269269
{ "psbtbumpfee", 1, "fee_rate"},
270270
{ "psbtbumpfee", 1, "replaceable"},
271271
{ "psbtbumpfee", 1, "outputs"},
272-
{ "psbtbumpfee", 1, "reduce_output"},
272+
{ "psbtbumpfee", 1, "original_change_index"},
273273
{ "logging", 0, "include" },
274274
{ "logging", 1, "exclude" },
275275
{ "disconnectnode", 1, "nodeid" },

src/wallet/feebumper.cpp

+7-7
Original file line numberDiff line numberDiff line change
@@ -162,11 +162,11 @@ bool TransactionCanBeBumped(const CWallet& wallet, const uint256& txid)
162162
}
163163

164164
Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCoinControl& coin_control, std::vector<bilingual_str>& errors,
165-
CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx, bool require_mine, const std::vector<CTxOut>& outputs, std::optional<uint32_t> reduce_output)
165+
CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx, bool require_mine, const std::vector<CTxOut>& outputs, std::optional<uint32_t> original_change_index)
166166
{
167-
// Cannot both specify new outputs and an output to reduce
168-
if (!outputs.empty() && reduce_output.has_value()) {
169-
errors.push_back(Untranslated("Cannot specify both new outputs to use and an output index to reduce"));
167+
// For now, cannot specify both new outputs to use and an output index to send change
168+
if (!outputs.empty() && original_change_index.has_value()) {
169+
errors.push_back(Untranslated("The options 'outputs' and 'original_change_index' are incompatible. You can only either specify a new set of outputs, or designate a change output to be recycled."));
170170
return Result::INVALID_PARAMETER;
171171
}
172172

@@ -182,8 +182,8 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
182182
}
183183
const CWalletTx& wtx = it->second;
184184

185-
// Make sure that reduce_output is valid
186-
if (reduce_output.has_value() && reduce_output.value() >= wtx.tx->vout.size()) {
185+
// Make sure that original_change_index is valid
186+
if (original_change_index.has_value() && original_change_index.value() >= wtx.tx->vout.size()) {
187187
errors.push_back(Untranslated("Change position is out of range"));
188188
return Result::INVALID_PARAMETER;
189189
}
@@ -259,7 +259,7 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
259259
const CTxOut& output = txouts.at(i);
260260
CTxDestination dest;
261261
ExtractDestination(output.scriptPubKey, dest);
262-
if (reduce_output.has_value() ? reduce_output.value() == i : OutputIsChange(wallet, output)) {
262+
if (original_change_index.has_value() ? original_change_index.value() == i : OutputIsChange(wallet, output)) {
263263
new_coin_control.destChange = dest;
264264
} else {
265265
CRecipient recipient = {dest, output.nValue, false};

src/wallet/feebumper.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ bool TransactionCanBeBumped(const CWallet& wallet, const uint256& txid);
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
4646
* @param[in] outputs Vector of new outputs to replace the bumped transaction's outputs
47-
* @param[in] reduce_output The position of the change output to deduct the fee from in the transaction being bumped
47+
* @param[in] original_change_index The position of the change output to deduct the fee from in the transaction being bumped
4848
*/
4949
Result CreateRateBumpTransaction(CWallet& wallet,
5050
const uint256& txid,
@@ -55,7 +55,7 @@ Result CreateRateBumpTransaction(CWallet& wallet,
5555
CMutableTransaction& mtx,
5656
bool require_mine,
5757
const std::vector<CTxOut>& outputs,
58-
std::optional<uint32_t> reduce_output = std::nullopt);
58+
std::optional<uint32_t> original_change_index = std::nullopt);
5959

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

src/wallet/rpc/spend.cpp

+11-7
Original file line numberDiff line numberDiff line change
@@ -1016,10 +1016,14 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
10161016
{"outputs", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "The outputs specified as key-value pairs.\n"
10171017
"Each key may only appear once, i.e. there can only be one 'data' output, and no address may be duplicated.\n"
10181018
"At least one output of either type must be specified.\n"
1019-
"Cannot be provided if 'reduce_output' is specified.",
1019+
"Cannot be provided if 'original_change_index' is specified.",
10201020
OutputsDoc(),
10211021
RPCArgOptions{.skip_type_check = true}},
1022-
{"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. Cannot be provided if 'outputs' is specified."},
1022+
{"original_change_index", RPCArg::Type::NUM, RPCArg::DefaultHint{"not set, detect change automatically"}, "The 0-based index of the change output on the original transaction. "
1023+
"The indicated output will be recycled into the new change output on the bumped transaction. "
1024+
"The remainder after paying the recipients and fees will be sent to the output script of the "
1025+
"original change output. The change output’s amount can increase if bumping the transaction "
1026+
"adds new inputs, otherwise it will decrease. Cannot be used in combination with the 'outputs' option."},
10231027
},
10241028
RPCArgOptions{.oneline_description="options"}},
10251029
},
@@ -1058,7 +1062,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
10581062
coin_control.m_signal_bip125_rbf = true;
10591063
std::vector<CTxOut> outputs;
10601064

1061-
std::optional<uint32_t> reduce_output;
1065+
std::optional<uint32_t> original_change_index;
10621066

10631067
if (!request.params[1].isNull()) {
10641068
UniValue options = request.params[1];
@@ -1070,7 +1074,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
10701074
{"replaceable", UniValueType(UniValue::VBOOL)},
10711075
{"estimate_mode", UniValueType(UniValue::VSTR)},
10721076
{"outputs", UniValueType()}, // will be checked by AddOutputs()
1073-
{"reduce_output", UniValueType(UniValue::VNUM)},
1077+
{"original_change_index", UniValueType(UniValue::VNUM)},
10741078
},
10751079
true, true);
10761080

@@ -1095,8 +1099,8 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
10951099
outputs = tempTx.vout;
10961100
}
10971101

1098-
if (options.exists("reduce_output")) {
1099-
reduce_output = options["reduce_output"].getInt<uint32_t>();
1102+
if (options.exists("original_change_index")) {
1103+
original_change_index = options["original_change_index"].getInt<uint32_t>();
11001104
}
11011105
}
11021106

@@ -1115,7 +1119,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
11151119
CMutableTransaction mtx;
11161120
feebumper::Result res;
11171121
// Targeting feerate bump.
1118-
res = feebumper::CreateRateBumpTransaction(*pwallet, hash, coin_control, errors, old_fee, new_fee, mtx, /*require_mine=*/ !want_psbt, outputs, reduce_output);
1122+
res = feebumper::CreateRateBumpTransaction(*pwallet, hash, coin_control, errors, old_fee, new_fee, mtx, /*require_mine=*/ !want_psbt, outputs, original_change_index);
11191123
if (res != feebumper::Result::OK) {
11201124
switch(res) {
11211125
case feebumper::Result::INVALID_ADDRESS_OR_KEY:

test/functional/wallet_bumpfee.py

+12-12
Original file line numberDiff line numberDiff line change
@@ -178,12 +178,12 @@ def test_invalid_parameters(self, rbf_node, peer_node, dest_address):
178178
assert_raises_rpc_error(-8, "Invalid parameter, duplicate key: data",
179179
rbf_node.bumpfee, rbfid, {"outputs": [{"data": "deadbeef"}, {"data": "deadbeef"}]})
180180

181-
self.log.info("Test reduce_output option")
182-
assert_raises_rpc_error(-1, "JSON integer out of range", rbf_node.bumpfee, rbfid, {"reduce_output": -1})
183-
assert_raises_rpc_error(-8, "Change position is out of range", rbf_node.bumpfee, rbfid, {"reduce_output": 2})
181+
self.log.info("Test original_change_index option")
182+
assert_raises_rpc_error(-1, "JSON integer out of range", rbf_node.bumpfee, rbfid, {"original_change_index": -1})
183+
assert_raises_rpc_error(-8, "Change position is out of range", rbf_node.bumpfee, rbfid, {"original_change_index": 2})
184184

185-
self.log.info("Test outputs and reduce_output cannot both be provided")
186-
assert_raises_rpc_error(-8, "Cannot specify both new outputs to use and an output index to reduce", rbf_node.bumpfee, rbfid, {"reduce_output": 2, "outputs": [{dest_address: 0.1}]})
185+
self.log.info("Test outputs and original_change_index cannot both be provided")
186+
assert_raises_rpc_error(-8, "The options 'outputs' and 'original_change_index' are incompatible. You can only either specify a new set of outputs, or designate a change output to be recycled.", rbf_node.bumpfee, rbfid, {"original_change_index": 2, "outputs": [{dest_address: 0.1}]})
187187

188188
self.clear_mempool()
189189

@@ -237,7 +237,7 @@ def test_bump_back_to_yourself(self):
237237
node.unloadwallet("back_to_yourself")
238238

239239
def test_provided_change_pos(self, rbf_node):
240-
self.log.info("Test the reduce_output option")
240+
self.log.info("Test the original_change_index option")
241241

242242
change_addr = rbf_node.getnewaddress()
243243
dest_addr = rbf_node.getnewaddress()
@@ -254,7 +254,7 @@ def test_provided_change_pos(self, rbf_node):
254254
change_pos = find_vout_for_address(rbf_node, txid, change_addr)
255255
change_value = tx["decoded"]["vout"][change_pos]["value"]
256256

257-
bumped = rbf_node.bumpfee(txid, {"reduce_output": change_pos})
257+
bumped = rbf_node.bumpfee(txid, {"original_change_index": change_pos})
258258
new_txid = bumped["txid"]
259259

260260
new_tx = rbf_node.gettransaction(txid=new_txid, verbose=True)
@@ -282,18 +282,18 @@ def test_single_output(self):
282282

283283
tx = wallet.sendall(recipients=[wallet.getnewaddress()], fee_rate=2, options={"inputs": [utxos[0]]})
284284

285-
# Reduce the only output with a crazy high feerate, should fail as the output would be dust
286-
assert_raises_rpc_error(-4, "The transaction amount is too small to pay the fee", wallet.bumpfee, txid=tx["txid"], options={"fee_rate": 1100, "reduce_output": 0})
285+
# Set the only output with a crazy high feerate as change, should fail as the output would be dust
286+
assert_raises_rpc_error(-4, "The transaction amount is too small to pay the fee", wallet.bumpfee, txid=tx["txid"], options={"fee_rate": 1100, "original_change_index": 0})
287287

288-
# Reduce the only output successfully
289-
bumped = wallet.bumpfee(txid=tx["txid"], options={"fee_rate": 10, "reduce_output": 0})
288+
# Specify single output as change successfully
289+
bumped = wallet.bumpfee(txid=tx["txid"], options={"fee_rate": 10, "original_change_index": 0})
290290
bumped_tx = wallet.gettransaction(txid=bumped["txid"], verbose=True)
291291
assert_equal(len(bumped_tx["decoded"]["vout"]), 1)
292292
assert_equal(len(bumped_tx["decoded"]["vin"]), 1)
293293
assert_equal(bumped_tx["decoded"]["vout"][0]["value"] + bumped["fee"], amount)
294294
assert_fee_amount(bumped["fee"], bumped_tx["decoded"]["vsize"], Decimal(10) / Decimal(1e8) * 1000)
295295

296-
# Bumping without reducing adds a new input and output
296+
# Bumping without specifying change adds a new input and output
297297
bumped = wallet.bumpfee(txid=bumped["txid"], options={"fee_rate": 20})
298298
bumped_tx = wallet.gettransaction(txid=bumped["txid"], verbose=True)
299299
assert_equal(len(bumped_tx["decoded"]["vout"]), 2)

0 commit comments

Comments
 (0)