mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-08 10:31:50 -05:00
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.
This commit is contained in:
parent
a36134fcc7
commit
7d83502d3d
4 changed files with 36 additions and 9 deletions
|
@ -259,11 +259,13 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||
{ "bumpfee", 1, "fee_rate"},
|
||||
{ "bumpfee", 1, "replaceable"},
|
||||
{ "bumpfee", 1, "outputs"},
|
||||
{ "bumpfee", 1, "reduce_output"},
|
||||
{ "psbtbumpfee", 1, "options" },
|
||||
{ "psbtbumpfee", 1, "conf_target"},
|
||||
{ "psbtbumpfee", 1, "fee_rate"},
|
||||
{ "psbtbumpfee", 1, "replaceable"},
|
||||
{ "psbtbumpfee", 1, "outputs"},
|
||||
{ "psbtbumpfee", 1, "reduce_output"},
|
||||
{ "logging", 0, "include" },
|
||||
{ "logging", 1, "exclude" },
|
||||
{ "disconnectnode", 1, "nodeid" },
|
||||
|
|
|
@ -152,8 +152,14 @@ bool TransactionCanBeBumped(const CWallet& wallet, const uint256& txid)
|
|||
}
|
||||
|
||||
Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCoinControl& coin_control, std::vector<bilingual_str>& errors,
|
||||
CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx, bool require_mine, const std::vector<CTxOut>& outputs)
|
||||
CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx, bool require_mine, const std::vector<CTxOut>& outputs, std::optional<uint32_t> reduce_output)
|
||||
{
|
||||
// Cannot both specify new outputs and an output to reduce
|
||||
if (!outputs.empty() && reduce_output.has_value()) {
|
||||
errors.push_back(Untranslated("Cannot specify both new outputs to use and an output index to reduce"));
|
||||
return Result::INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
// We are going to modify coin control later, copy to re-use
|
||||
CCoinControl new_coin_control(coin_control);
|
||||
|
||||
|
@ -166,6 +172,12 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
|
|||
}
|
||||
const CWalletTx& wtx = it->second;
|
||||
|
||||
// Make sure that reduce_output is valid
|
||||
if (reduce_output.has_value() && reduce_output.value() >= wtx.tx->vout.size()) {
|
||||
errors.push_back(Untranslated("Change position is out of range"));
|
||||
return Result::INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
// Retrieve all of the UTXOs and add them to coin control
|
||||
// While we're here, calculate the input amount
|
||||
std::map<COutPoint, Coin> coins;
|
||||
|
@ -233,14 +245,15 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
|
|||
std::vector<CRecipient> recipients;
|
||||
CAmount new_outputs_value = 0;
|
||||
const auto& txouts = outputs.empty() ? wtx.tx->vout : outputs;
|
||||
for (const auto& output : txouts) {
|
||||
if (!OutputIsChange(wallet, output)) {
|
||||
CRecipient recipient = {output.scriptPubKey, output.nValue, false};
|
||||
recipients.push_back(recipient);
|
||||
} else {
|
||||
for (size_t i = 0; i < txouts.size(); ++i) {
|
||||
const CTxOut& output = txouts.at(i);
|
||||
if (reduce_output.has_value() ? reduce_output.value() == i : OutputIsChange(wallet, output)) {
|
||||
CTxDestination change_dest;
|
||||
ExtractDestination(output.scriptPubKey, change_dest);
|
||||
new_coin_control.destChange = change_dest;
|
||||
} else {
|
||||
CRecipient recipient = {output.scriptPubKey, output.nValue, false};
|
||||
recipients.push_back(recipient);
|
||||
}
|
||||
new_outputs_value += output.nValue;
|
||||
}
|
||||
|
|
|
@ -43,6 +43,8 @@ bool TransactionCanBeBumped(const CWallet& wallet, const uint256& txid);
|
|||
* @param[out] new_fee the fee that the bump transaction pays
|
||||
* @param[out] mtx The bump transaction itself
|
||||
* @param[in] require_mine Whether the original transaction must consist of inputs that can be spent by the wallet
|
||||
* @param[in] outputs Vector of new outputs to replace the bumped transaction's outputs
|
||||
* @param[in] reduce_output The position of the change output to deduct the fee from in the transaction being bumped
|
||||
*/
|
||||
Result CreateRateBumpTransaction(CWallet& wallet,
|
||||
const uint256& txid,
|
||||
|
@ -52,7 +54,8 @@ Result CreateRateBumpTransaction(CWallet& wallet,
|
|||
CAmount& new_fee,
|
||||
CMutableTransaction& mtx,
|
||||
bool require_mine,
|
||||
const std::vector<CTxOut>& outputs);
|
||||
const std::vector<CTxOut>& outputs,
|
||||
std::optional<uint32_t> reduce_output = std::nullopt);
|
||||
|
||||
//! Sign the new transaction,
|
||||
//! @return false if the tx couldn't be found or if it was
|
||||
|
|
|
@ -1015,9 +1015,11 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
|
|||
"\"" + FeeModes("\"\n\"") + "\""},
|
||||
{"outputs", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "New outputs (key-value pairs) which will replace\n"
|
||||
"the original ones, if provided. Each address can only appear once and there can\n"
|
||||
"only be one \"data\" object.\n",
|
||||
"only be one \"data\" object.\n"
|
||||
"Cannot be provided if 'reduce_output' is specified.",
|
||||
OutputsDoc(),
|
||||
RPCArgOptions{.skip_type_check = true}},
|
||||
{"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."},
|
||||
},
|
||||
RPCArgOptions{.oneline_description="options"}},
|
||||
},
|
||||
|
@ -1056,6 +1058,8 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
|
|||
coin_control.m_signal_bip125_rbf = true;
|
||||
std::vector<CTxOut> outputs;
|
||||
|
||||
std::optional<uint32_t> reduce_output;
|
||||
|
||||
if (!request.params[1].isNull()) {
|
||||
UniValue options = request.params[1];
|
||||
RPCTypeCheckObj(options,
|
||||
|
@ -1066,6 +1070,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
|
|||
{"replaceable", UniValueType(UniValue::VBOOL)},
|
||||
{"estimate_mode", UniValueType(UniValue::VSTR)},
|
||||
{"outputs", UniValueType()}, // will be checked by AddOutputs()
|
||||
{"reduce_output", UniValueType(UniValue::VNUM)},
|
||||
},
|
||||
true, true);
|
||||
|
||||
|
@ -1089,6 +1094,10 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
|
|||
AddOutputs(tempTx, options["outputs"]);
|
||||
outputs = tempTx.vout;
|
||||
}
|
||||
|
||||
if (options.exists("reduce_output")) {
|
||||
reduce_output = options["reduce_output"].getInt<uint32_t>();
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the results are valid at least up to the most recent block
|
||||
|
@ -1106,7 +1115,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
|
|||
CMutableTransaction mtx;
|
||||
feebumper::Result res;
|
||||
// Targeting feerate bump.
|
||||
res = feebumper::CreateRateBumpTransaction(*pwallet, hash, coin_control, errors, old_fee, new_fee, mtx, /*require_mine=*/ !want_psbt, outputs);
|
||||
res = feebumper::CreateRateBumpTransaction(*pwallet, hash, coin_control, errors, old_fee, new_fee, mtx, /*require_mine=*/ !want_psbt, outputs, reduce_output);
|
||||
if (res != feebumper::Result::OK) {
|
||||
switch(res) {
|
||||
case feebumper::Result::INVALID_ADDRESS_OR_KEY:
|
||||
|
|
Loading…
Add table
Reference in a new issue