Skip to content

Commit c0ebb98

Browse files
committed
wallet: add outputs arguments to bumpfee and psbtbumpfee
1 parent a804f3c commit c0ebb98

6 files changed

+70
-30
lines changed

src/rpc/rawtransaction_util.cpp

+26-17
Original file line numberDiff line numberDiff line change
@@ -21,31 +21,15 @@
2121
#include <util/strencodings.h>
2222
#include <util/translation.h>
2323

24-
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf)
24+
void AddInputs(CMutableTransaction& rawTx, const UniValue& inputs_in, std::optional<bool> rbf)
2525
{
26-
if (outputs_in.isNull()) {
27-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output argument must be non-null");
28-
}
29-
3026
UniValue inputs;
3127
if (inputs_in.isNull()) {
3228
inputs = UniValue::VARR;
3329
} else {
3430
inputs = inputs_in.get_array();
3531
}
3632

37-
const bool outputs_is_obj = outputs_in.isObject();
38-
UniValue outputs = outputs_is_obj ? outputs_in.get_obj() : outputs_in.get_array();
39-
40-
CMutableTransaction rawTx;
41-
42-
if (!locktime.isNull()) {
43-
int64_t nLockTime = locktime.getInt<int64_t>();
44-
if (nLockTime < 0 || nLockTime > LOCKTIME_MAX)
45-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range");
46-
rawTx.nLockTime = nLockTime;
47-
}
48-
4933
for (unsigned int idx = 0; idx < inputs.size(); idx++) {
5034
const UniValue& input = inputs[idx];
5135
const UniValue& o = input.get_obj();
@@ -84,6 +68,16 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
8468

8569
rawTx.vin.push_back(in);
8670
}
71+
}
72+
73+
void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in)
74+
{
75+
if (outputs_in.isNull()) {
76+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output argument must be non-null");
77+
}
78+
79+
const bool outputs_is_obj = outputs_in.isObject();
80+
UniValue outputs = outputs_is_obj ? outputs_in.get_obj() : outputs_in.get_array();
8781

8882
if (!outputs_is_obj) {
8983
// Translate array of key-value pairs into dict
@@ -132,6 +126,21 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
132126
rawTx.vout.push_back(out);
133127
}
134128
}
129+
}
130+
131+
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf)
132+
{
133+
CMutableTransaction rawTx;
134+
135+
if (!locktime.isNull()) {
136+
int64_t nLockTime = locktime.getInt<int64_t>();
137+
if (nLockTime < 0 || nLockTime > LOCKTIME_MAX)
138+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range");
139+
rawTx.nLockTime = nLockTime;
140+
}
141+
142+
AddInputs(rawTx, inputs_in, rbf);
143+
AddOutputs(rawTx, outputs_in);
135144

136145
if (rbf.has_value() && rbf.value() && rawTx.vin.size() > 0 && !SignalsOptInRBF(CTransaction(rawTx))) {
137146
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict replaceable option");

src/rpc/rawtransaction_util.h

+7
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const
3838
*/
3939
void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keystore, std::map<COutPoint, Coin>& coins);
4040

41+
42+
/** Normalize univalue-represented inputs and add them to the transaction */
43+
void AddInputs(CMutableTransaction& rawTx, const UniValue& inputs_in, bool rbf);
44+
45+
/** Normalize univalue-represented outputs and add them to the transaction */
46+
void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in);
47+
4148
/** Create a transaction from univalue parameters */
4249
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf);
4350

src/wallet/feebumper.cpp

+13-8
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, const std::vector<CTxOut>& outputs)
159159
{
160160
// We are going to modify coin control later, copy to re-use
161161
CCoinControl new_coin_control(coin_control);
@@ -222,11 +222,19 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
222222
return result;
223223
}
224224

225-
// Fill in recipients(and preserve a single change key if there is one)
226-
// While we're here, calculate the output amount
227-
std::vector<CRecipient> recipients;
225+
// Calculate the old output amount.
228226
CAmount output_value = 0;
229-
for (const auto& output : wtx.tx->vout) {
227+
for (const auto& old_output : wtx.tx->vout) {
228+
output_value += old_output.nValue;
229+
}
230+
231+
old_fee = input_value - output_value;
232+
233+
// Fill in recipients (and preserve a single change key if there
234+
// is one). If outputs vector is non-empty, replace original
235+
// outputs with its contents, otherwise use original outputs.
236+
std::vector<CRecipient> recipients;
237+
for (const auto& output : outputs.empty() ? wtx.tx->vout : outputs) {
230238
if (!OutputIsChange(wallet, output)) {
231239
CRecipient recipient = {output.scriptPubKey, output.nValue, false};
232240
recipients.push_back(recipient);
@@ -235,11 +243,8 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
235243
ExtractDestination(output.scriptPubKey, change_dest);
236244
new_coin_control.destChange = change_dest;
237245
}
238-
output_value += output.nValue;
239246
}
240247

241-
old_fee = input_value - output_value;
242-
243248
if (coin_control.m_feerate) {
244249
// The user provided a feeRate argument.
245250
// We calculate this here to avoid compiler warning on the cs_wallet lock

src/wallet/feebumper.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ Result CreateRateBumpTransaction(CWallet& wallet,
5151
CAmount& old_fee,
5252
CAmount& new_fee,
5353
CMutableTransaction& mtx,
54-
bool require_mine);
54+
bool require_mine,
55+
const std::vector<CTxOut>& outputs);
5556

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

src/wallet/interfaces.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,8 @@ class WalletImpl : public Wallet
291291
CAmount& new_fee,
292292
CMutableTransaction& mtx) override
293293
{
294-
return feebumper::CreateRateBumpTransaction(*m_wallet.get(), txid, coin_control, errors, old_fee, new_fee, mtx, /* require_mine= */ true) == feebumper::Result::OK;
294+
std::vector<CTxOut> outputs; // just an empty list of new recipients for now
295+
return feebumper::CreateRateBumpTransaction(*m_wallet.get(), txid, coin_control, errors, old_fee, new_fee, mtx, /* require_mine= */ true, outputs) == feebumper::Result::OK;
295296
}
296297
bool signBumpTransaction(CMutableTransaction& mtx) override { return feebumper::SignTransaction(*m_wallet.get(), mtx); }
297298
bool commitBumpTransaction(const uint256& txid,

src/wallet/rpc/spend.cpp

+20-3
Original file line numberDiff line numberDiff line change
@@ -958,7 +958,7 @@ RPCHelpMan signrawtransactionwithwallet()
958958
}
959959

960960
// Definition of allowed formats of specifying transaction outputs in
961-
// `send` and `walletcreatefundedpsbt` RPCs.
961+
// `bumpfee`, `psbtbumpfee`, `send` and `walletcreatefundedpsbt` RPCs.
962962
static std::vector<RPCArg> OutputsDoc()
963963
{
964964
return
@@ -1013,7 +1013,12 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
10131013
"still be replaceable in practice, for example if it has unconfirmed ancestors which\n"
10141014
"are replaceable).\n"},
10151015
{"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n"
1016-
"\"" + FeeModes("\"\n\"") + "\""},
1016+
"\"" + FeeModes("\"\n\"") + "\""},
1017+
{"outputs", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "New outputs (key-value pairs) which will replace\n"
1018+
"the original ones, if provided. Each address can only appear once and there can\n"
1019+
"only be one \"data\" object.\n",
1020+
OutputsDoc(),
1021+
RPCArgOptions{.skip_type_check = true}},
10171022
},
10181023
RPCArgOptions{.oneline_description="options"}},
10191024
},
@@ -1050,6 +1055,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
10501055
coin_control.fAllowWatchOnly = pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
10511056
// optional parameters
10521057
coin_control.m_signal_bip125_rbf = true;
1058+
std::vector<CTxOut> outputs;
10531059

10541060
if (!request.params[1].isNull()) {
10551061
UniValue options = request.params[1];
@@ -1060,6 +1066,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
10601066
{"fee_rate", UniValueType()}, // will be checked by AmountFromValue() in SetFeeEstimateMode()
10611067
{"replaceable", UniValueType(UniValue::VBOOL)},
10621068
{"estimate_mode", UniValueType(UniValue::VSTR)},
1069+
{"outputs", UniValueType()}, // will be checked by AddOutputs()
10631070
},
10641071
true, true);
10651072

@@ -1073,6 +1080,16 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
10731080
coin_control.m_signal_bip125_rbf = options["replaceable"].get_bool();
10741081
}
10751082
SetFeeEstimateMode(*pwallet, coin_control, conf_target, options["estimate_mode"], options["fee_rate"], /*override_min_fee=*/false);
1083+
1084+
// Prepare new outputs by creating a temporary tx and calling AddOutputs().
1085+
if (!options["outputs"].isNull()) {
1086+
if (options["outputs"].isArray() && options["outputs"].empty()) {
1087+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output argument cannot be an empty array");
1088+
}
1089+
CMutableTransaction tempTx;
1090+
AddOutputs(tempTx, options["outputs"]);
1091+
outputs = tempTx.vout;
1092+
}
10761093
}
10771094

10781095
// Make sure the results are valid at least up to the most recent block
@@ -1090,7 +1107,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
10901107
CMutableTransaction mtx;
10911108
feebumper::Result res;
10921109
// Targeting feerate bump.
1093-
res = feebumper::CreateRateBumpTransaction(*pwallet, hash, coin_control, errors, old_fee, new_fee, mtx, /*require_mine=*/ !want_psbt);
1110+
res = feebumper::CreateRateBumpTransaction(*pwallet, hash, coin_control, errors, old_fee, new_fee, mtx, /*require_mine=*/ !want_psbt, outputs);
10941111
if (res != feebumper::Result::OK) {
10951112
switch(res) {
10961113
case feebumper::Result::INVALID_ADDRESS_OR_KEY:

0 commit comments

Comments
 (0)