Skip to content

Commit 8a85377

Browse files
committed
Merge #18654: rpc: separate bumpfee's psbt creation function into psbtbumpfee
79d6332 moveonly: Fix indentation in bumpfee RPC (Andrew Chow) 431071c Hide bumpfee's psbt creation behavior behind -deprecatedrpc (Andrew Chow) 4638224 Add psbtbumpfee RPC (Andrew Chow) Pull request description: Adds a new RPC `psbtbumpfee` which always creates a psbt. `bumpfee` will then only be able to create and broadcast fee bumping transactions instead of changing its behavior based on `IsWalletSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)`. Split from #18627 ACKs for top commit: Sjors: re-utACK 79d6332 meshcollider: utACK 79d6332 fjahr: Code review ACK 79d6332 Tree-SHA512: 1c92c4b4461bb30e78be3ee73165f624398ef33996ce36043b61a8931be667030d0fca12fd0b30097b78c56e4e9092c69582b237cbdac51d56f6be23d8c0f1bb
2 parents 038a04e + 79d6332 commit 8a85377

File tree

4 files changed

+109
-51
lines changed

4 files changed

+109
-51
lines changed

src/rpc/client.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
151151
{ "getmempoolancestors", 1, "verbose" },
152152
{ "getmempooldescendants", 1, "verbose" },
153153
{ "bumpfee", 1, "options" },
154+
{ "psbtbumpfee", 1, "options" },
154155
{ "logging", 0, "include" },
155156
{ "logging", 1, "exclude" },
156157
{ "disconnectnode", 1, "nodeid" },

src/wallet/rpcwallet.cpp

Lines changed: 67 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3222,59 +3222,73 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request)
32223222

32233223
static UniValue bumpfee(const JSONRPCRequest& request)
32243224
{
3225-
RPCHelpMan{"bumpfee",
3226-
"\nBumps the fee of an opt-in-RBF transaction T, replacing it with a new transaction B.\n"
3227-
"An opt-in RBF transaction with the given txid must be in the wallet.\n"
3228-
"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"
3229-
"All inputs in the original transaction will be included in the replacement transaction.\n"
3230-
"The command will fail if the wallet or mempool contains a transaction that spends one of T's outputs.\n"
3231-
"By default, the new fee will be calculated automatically using estimatesmartfee.\n"
3232-
"The user can specify a confirmation target for estimatesmartfee.\n"
3233-
"Alternatively, the user can specify a fee_rate (" + CURRENCY_UNIT + " per kB) for the new transaction.\n"
3234-
"At a minimum, the new fee rate must be high enough to pay an additional new relay fee (incrementalfee\n"
3235-
"returned by getnetworkinfo) to enter the node's mempool.\n",
3225+
bool want_psbt = request.strMethod == "psbtbumpfee";
3226+
3227+
RPCHelpMan{request.strMethod,
3228+
"\nBumps the fee of an opt-in-RBF transaction T, replacing it with a new transaction B.\n"
3229+
+ std::string(want_psbt ? "Returns a PSBT instead of creating and signing a new transaction.\n" : "") +
3230+
"An opt-in RBF transaction with the given txid must be in the wallet.\n"
3231+
"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"
3232+
"All inputs in the original transaction will be included in the replacement transaction.\n"
3233+
"The command will fail if the wallet or mempool contains a transaction that spends one of T's outputs.\n"
3234+
"By default, the new fee will be calculated automatically using estimatesmartfee.\n"
3235+
"The user can specify a confirmation target for estimatesmartfee.\n"
3236+
"Alternatively, the user can specify a fee_rate (" + CURRENCY_UNIT + " per kB) for the new transaction.\n"
3237+
"At a minimum, the new fee rate must be high enough to pay an additional new relay fee (incrementalfee\n"
3238+
"returned by getnetworkinfo) to enter the node's mempool.\n",
3239+
{
3240+
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The txid to be bumped"},
3241+
{"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "",
32363242
{
3237-
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The txid to be bumped"},
3238-
{"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "",
3239-
{
3240-
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"},
3241-
{"fee_rate", RPCArg::Type::NUM, /* default */ "fall back to 'conf_target'", "fee rate (NOT total fee) to pay, in " + CURRENCY_UNIT + " per kB\n"
3242-
" Specify a fee rate instead of relying on the built-in fee estimator.\n"
3243-
"Must be at least 0.0001 " + CURRENCY_UNIT + " per kB higher than the current transaction fee rate.\n"},
3244-
{"replaceable", RPCArg::Type::BOOL, /* default */ "true", "Whether the new transaction should still be\n"
3245-
" marked bip-125 replaceable. If true, the sequence numbers in the transaction will\n"
3246-
" be left unchanged from the original. If false, any input sequence numbers in the\n"
3247-
" original transaction that were less than 0xfffffffe will be increased to 0xfffffffe\n"
3248-
" so the new transaction will not be explicitly bip-125 replaceable (though it may\n"
3249-
" still be replaceable in practice, for example if it has unconfirmed ancestors which\n"
3250-
" are replaceable)."},
3251-
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
3252-
" \"" + FeeModes("\"\n\"") + "\""},
3253-
},
3254-
"options"},
3255-
},
3256-
RPCResult{
3257-
RPCResult::Type::OBJ, "", "", {
3258-
{RPCResult::Type::STR, "psbt", "The base64-encoded unsigned PSBT of the new transaction. Only returned when wallet private keys are disabled."},
3259-
{RPCResult::Type::STR_HEX, "txid", "The id of the new transaction. Only returned when wallet private keys are enabled."},
3260-
{RPCResult::Type::STR_AMOUNT, "origfee", "The fee of the replaced transaction."},
3261-
{RPCResult::Type::STR_AMOUNT, "fee", "The fee of the new transaction."},
3262-
{RPCResult::Type::ARR, "errors", "Errors encountered during processing (may be empty).",
3263-
{
3264-
{RPCResult::Type::STR, "", ""},
3265-
}},
3266-
}
3267-
},
3268-
RPCExamples{
3269-
"\nBump the fee, get the new transaction\'s txid\n" +
3270-
HelpExampleCli("bumpfee", "<txid>")
3243+
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"},
3244+
{"fee_rate", RPCArg::Type::NUM, /* default */ "fall back to 'conf_target'", "fee rate (NOT total fee) to pay, in " + CURRENCY_UNIT + " per kB\n"
3245+
" Specify a fee rate instead of relying on the built-in fee estimator.\n"
3246+
"Must be at least 0.0001 " + CURRENCY_UNIT + " per kB higher than the current transaction fee rate.\n"},
3247+
{"replaceable", RPCArg::Type::BOOL, /* default */ "true", "Whether the new transaction should still be\n"
3248+
" marked bip-125 replaceable. If true, the sequence numbers in the transaction will\n"
3249+
" be left unchanged from the original. If false, any input sequence numbers in the\n"
3250+
" original transaction that were less than 0xfffffffe will be increased to 0xfffffffe\n"
3251+
" so the new transaction will not be explicitly bip-125 replaceable (though it may\n"
3252+
" still be replaceable in practice, for example if it has unconfirmed ancestors which\n"
3253+
" are replaceable)."},
3254+
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
3255+
" \"" + FeeModes("\"\n\"") + "\""},
32713256
},
3272-
}.Check(request);
3257+
"options"},
3258+
},
3259+
RPCResult{
3260+
RPCResult::Type::OBJ, "", "", Cat(Cat<std::vector<RPCResult>>(
3261+
{
3262+
{RPCResult::Type::STR, "psbt", "The base64-encoded unsigned PSBT of the new transaction." + std::string(want_psbt ? "" : " Only returned when wallet private keys are disabled. (DEPRECATED)")},
3263+
},
3264+
want_psbt ? std::vector<RPCResult>{} : std::vector<RPCResult>{{RPCResult::Type::STR_HEX, "txid", "The id of the new transaction. Only returned when wallet private keys are enabled."}}
3265+
),
3266+
{
3267+
{RPCResult::Type::STR_AMOUNT, "origfee", "The fee of the replaced transaction."},
3268+
{RPCResult::Type::STR_AMOUNT, "fee", "The fee of the new transaction."},
3269+
{RPCResult::Type::ARR, "errors", "Errors encountered during processing (may be empty).",
3270+
{
3271+
{RPCResult::Type::STR, "", ""},
3272+
}},
3273+
})
3274+
},
3275+
RPCExamples{
3276+
"\nBump the fee, get the new transaction\'s" + std::string(want_psbt ? "psbt" : "txid") + "\n" +
3277+
HelpExampleCli(request.strMethod, "<txid>")
3278+
},
3279+
}.Check(request);
32733280

32743281
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
32753282
if (!wallet) return NullUniValue;
32763283
CWallet* const pwallet = wallet.get();
32773284

3285+
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !want_psbt) {
3286+
if (!pwallet->chain().rpcEnableDeprecated("bumpfee")) {
3287+
throw JSONRPCError(RPC_METHOD_DEPRECATED, "Using bumpfee with wallets that have private keys disabled is deprecated. Use psbtbumpfee instead or restart bitcoind with -deprecatedrpc=bumpfee. This functionality will be removed in 0.22");
3288+
}
3289+
want_psbt = true;
3290+
}
3291+
32783292
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ});
32793293
uint256 hash(ParseHashV(request.params[0], "txid"));
32803294

@@ -3359,7 +3373,7 @@ static UniValue bumpfee(const JSONRPCRequest& request)
33593373

33603374
// If wallet private keys are enabled, return the new transaction id,
33613375
// otherwise return the base64-encoded unsigned PSBT of the new transaction.
3362-
if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
3376+
if (!want_psbt) {
33633377
if (!feebumper::SignTransaction(*pwallet, mtx)) {
33643378
throw JSONRPCError(RPC_WALLET_ERROR, "Can't sign transaction.");
33653379
}
@@ -3392,6 +3406,11 @@ static UniValue bumpfee(const JSONRPCRequest& request)
33923406
return result;
33933407
}
33943408

3409+
static UniValue psbtbumpfee(const JSONRPCRequest& request)
3410+
{
3411+
return bumpfee(request);
3412+
}
3413+
33953414
UniValue rescanblockchain(const JSONRPCRequest& request)
33963415
{
33973416
RPCHelpMan{"rescanblockchain",
@@ -4137,6 +4156,7 @@ static const CRPCCommand commands[] =
41374156
{ "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","label","address_type"} },
41384157
{ "wallet", "backupwallet", &backupwallet, {"destination"} },
41394158
{ "wallet", "bumpfee", &bumpfee, {"txid", "options"} },
4159+
{ "wallet", "psbtbumpfee", &psbtbumpfee, {"txid", "options"} },
41404160
{ "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank", "passphrase", "avoid_reuse", "descriptors"} },
41414161
{ "wallet", "dumpprivkey", &dumpprivkey, {"address"} },
41424162
{ "wallet", "dumpwallet", &dumpwallet, {"filename"} },

test/functional/rpc_deprecated.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55
"""Test deprecation of RPC calls."""
66
from test_framework.test_framework import BitcoinTestFramework
7-
# from test_framework.util import assert_raises_rpc_error
7+
from test_framework.util import assert_raises_rpc_error, find_vout_for_address
88

99
class DeprecatedRpcTest(BitcoinTestFramework):
1010
def set_test_params(self):
1111
self.num_nodes = 2
1212
self.setup_clean_chain = True
13-
self.extra_args = [[], []]
13+
self.extra_args = [[], ['-deprecatedrpc=bumpfee']]
1414

1515
def run_test(self):
1616
# This test should be used to verify correct behaviour of deprecated
@@ -23,7 +23,38 @@ def run_test(self):
2323
# self.log.info("Test generate RPC")
2424
# assert_raises_rpc_error(-32, 'The wallet generate rpc method is deprecated', self.nodes[0].rpc.generate, 1)
2525
# self.nodes[1].generate(1)
26-
self.log.info("No tested deprecated RPC methods")
26+
27+
if self.is_wallet_compiled():
28+
self.log.info("Test bumpfee RPC")
29+
self.nodes[0].generate(101)
30+
self.nodes[0].createwallet(wallet_name='nopriv', disable_private_keys=True)
31+
noprivs0 = self.nodes[0].get_wallet_rpc('nopriv')
32+
w0 = self.nodes[0].get_wallet_rpc('')
33+
self.nodes[1].createwallet(wallet_name='nopriv', disable_private_keys=True)
34+
noprivs1 = self.nodes[1].get_wallet_rpc('nopriv')
35+
36+
address = w0.getnewaddress()
37+
desc = w0.getaddressinfo(address)['desc']
38+
change_addr = w0.getrawchangeaddress()
39+
change_desc = w0.getaddressinfo(change_addr)['desc']
40+
txid = w0.sendtoaddress(address=address, amount=10)
41+
vout = find_vout_for_address(w0, txid, address)
42+
self.nodes[0].generate(1)
43+
rawtx = w0.createrawtransaction([{'txid': txid, 'vout': vout}], {w0.getnewaddress(): 5}, 0, True)
44+
rawtx = w0.fundrawtransaction(rawtx, {'changeAddress': change_addr})
45+
signed_tx = w0.signrawtransactionwithwallet(rawtx['hex'])['hex']
46+
47+
noprivs0.importmulti([{'desc': desc, 'timestamp': 0}, {'desc': change_desc, 'timestamp': 0, 'internal': True}])
48+
noprivs1.importmulti([{'desc': desc, 'timestamp': 0}, {'desc': change_desc, 'timestamp': 0, 'internal': True}])
49+
50+
txid = w0.sendrawtransaction(signed_tx)
51+
self.sync_all()
52+
53+
assert_raises_rpc_error(-32, 'Using bumpfee with wallets that have private keys disabled is deprecated. Use psbtbumpfee instead or restart bitcoind with -deprecatedrpc=bumpfee. This functionality will be removed in 0.22', noprivs0.bumpfee, txid)
54+
bumped_psbt = noprivs1.bumpfee(txid)
55+
assert 'psbt' in bumped_psbt
56+
else:
57+
self.log.info("No tested deprecated RPC methods")
2758

2859
if __name__ == '__main__':
2960
DeprecatedRpcTest().main()

test/functional/wallet_bumpfee.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,13 +123,19 @@ def test_simple_bumpfee_succeeds(self, mode, rbf_node, peer_node, dest_address):
123123
self.sync_mempools((rbf_node, peer_node))
124124
assert rbfid in rbf_node.getrawmempool() and rbfid in peer_node.getrawmempool()
125125
if mode == "fee_rate":
126+
bumped_psbt = rbf_node.psbtbumpfee(rbfid, {"fee_rate": NORMAL})
126127
bumped_tx = rbf_node.bumpfee(rbfid, {"fee_rate": NORMAL})
127128
else:
129+
bumped_psbt = rbf_node.psbtbumpfee(rbfid)
128130
bumped_tx = rbf_node.bumpfee(rbfid)
129131
assert_equal(bumped_tx["errors"], [])
130132
assert bumped_tx["fee"] > -rbftx["fee"]
131133
assert_equal(bumped_tx["origfee"], -rbftx["fee"])
132134
assert "psbt" not in bumped_tx
135+
assert_equal(bumped_psbt["errors"], [])
136+
assert bumped_psbt["fee"] > -rbftx["fee"]
137+
assert_equal(bumped_psbt["origfee"], -rbftx["fee"])
138+
assert "psbt" in bumped_psbt
133139
# check that bumped_tx propagates, original tx was evicted and has a wallet conflict
134140
self.sync_mempools((rbf_node, peer_node))
135141
assert bumped_tx["txid"] in rbf_node.getrawmempool()
@@ -391,7 +397,7 @@ def test_watchonly_psbt(self, peer_node, rbf_node, dest_address):
391397
assert_equal(len(watcher.decodepsbt(psbt)["tx"]["vin"]), 1)
392398

393399
# Bump fee, obnoxiously high to add additional watchonly input
394-
bumped_psbt = watcher.bumpfee(original_txid, {"fee_rate": HIGH})
400+
bumped_psbt = watcher.psbtbumpfee(original_txid, {"fee_rate": HIGH})
395401
assert_greater_than(len(watcher.decodepsbt(bumped_psbt['psbt'])["tx"]["vin"]), 1)
396402
assert "txid" not in bumped_psbt
397403
assert_equal(bumped_psbt["origfee"], -watcher.gettransaction(original_txid)["fee"])

0 commit comments

Comments
 (0)