Skip to content

Commit 8e863e3

Browse files
committed
wallet: introduce setfeerate, an improved settxfee in sat/vB
1 parent 0479268 commit 8e863e3

File tree

4 files changed

+90
-0
lines changed

4 files changed

+90
-0
lines changed

doc/release-notes-20391.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
New RPCs
2+
------------
3+
4+
- A new `setfeerate` RPC is added to set the transaction fee for a wallet in
5+
sat/vB, instead of in BTC/kvB like `settxfee`. This RPC is intended to replace
6+
`settxfee`, which becomes a hidden RPC and is marked as deprecated. This
7+
change is part of a larger migration from BTC/kvB to sat/vB units for fee
8+
rates. (#20391)
9+

src/rpc/client.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
4343
{ "sendtoaddress", 8, "avoid_reuse" },
4444
{ "sendtoaddress", 9, "fee_rate"},
4545
{ "sendtoaddress", 10, "verbose"},
46+
{ "setfeerate", 0, "amount" },
4647
{ "settxfee", 0, "amount" },
4748
{ "sethdseed", 0, "newkeypool" },
4849
{ "getreceivedbyaddress", 1, "minconf" },

src/wallet/rpcwallet.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2349,6 +2349,81 @@ static RPCHelpMan settxfee()
23492349
};
23502350
}
23512351

2352+
static RPCHelpMan setfeerate()
2353+
{
2354+
return RPCHelpMan{
2355+
"setfeerate",
2356+
"\nSet the transaction fee rate in " + CURRENCY_ATOM + "/vB for this wallet.\n"
2357+
"Overrides the global -paytxfee configuration option. Like -paytxfee, it is not persisted after bitcoind shutdown/restart.\n"
2358+
"Can be deactivated by passing 0 as the fee rate, in which case automatic fee selection will be used by default.\n",
2359+
{
2360+
{"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The transaction fee rate in " + CURRENCY_ATOM + "/vB to set (0 to unset)"},
2361+
},
2362+
RPCResult{
2363+
RPCResult::Type::OBJ, "", "",
2364+
{
2365+
{RPCResult::Type::STR, "wallet_name", "Name of the wallet the fee rate setting applies to"},
2366+
{RPCResult::Type::NUM, "fee_rate", "Fee rate in " + CURRENCY_ATOM + "/vB for the wallet after this operation"},
2367+
{RPCResult::Type::STR, "result", /* optional */ true, "Description of result, if successful"},
2368+
{RPCResult::Type::STR, "error", /* optional */ true, "Description of error, if any"},
2369+
},
2370+
},
2371+
RPCExamples{
2372+
""
2373+
"\nSet a fee rate of 1 " + CURRENCY_ATOM + "/vB\n"
2374+
+ HelpExampleCli("setfeerate", "1") +
2375+
"\nSet a fee rate of 3.141 " + CURRENCY_ATOM + "/vB\n"
2376+
+ HelpExampleCli("setfeerate", "3.141") +
2377+
"\nSet a fee rate of 7.75 " + CURRENCY_ATOM + "/vB with named arguments\n"
2378+
+ HelpExampleCli("-named setfeerate", "amount=\"7.75\"") +
2379+
"\nSet a fee rate of 25 " + CURRENCY_ATOM + "/vB with the RPC\n"
2380+
+ HelpExampleRpc("setfeerate", "25")
2381+
},
2382+
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
2383+
std::shared_ptr<CWallet> const rpc_wallet{GetWalletForJSONRPCRequest(request)};
2384+
if (!rpc_wallet) return NullUniValue;
2385+
CWallet& wallet = *rpc_wallet;
2386+
2387+
LOCK(wallet.cs_wallet);
2388+
2389+
const CFeeRate amount{CFeeRate::FromSatB(AmountFromValue(request.params[0]))};
2390+
const CFeeRate relay_min_feerate{wallet.chain().relayMinFee().GetFeePerK()};
2391+
const CFeeRate wallet_min_feerate{wallet.m_min_fee.GetFeePerK()};
2392+
const CFeeRate wallet_max_feerate{CFeeRate::FromBtcKb(wallet.m_default_max_tx_fee)};
2393+
const CFeeRate zero{CFeeRate{0}};
2394+
const std::string amount_str{amount.ToString(FeeEstimateMode::SAT_VB)};
2395+
const std::string current_setting{strprintf("The current setting of %s for this wallet remains unchanged.", wallet.m_pay_tx_fee == zero ? "0 (unset)" : wallet.m_pay_tx_fee.ToString(FeeEstimateMode::SAT_VB))};
2396+
std::string result, error;
2397+
2398+
if (amount == zero) {
2399+
if (request.params[0].get_real() != 0) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount");
2400+
wallet.m_pay_tx_fee = amount;
2401+
result = "Fee rate for transactions with this wallet successfully unset. By default, automatic fee selection will be used.";
2402+
} else if (amount < relay_min_feerate) {
2403+
error = strprintf("The requested fee rate of %s cannot be less than the minimum relay fee rate of %s. %s", amount_str, relay_min_feerate.ToString(FeeEstimateMode::SAT_VB), current_setting);
2404+
} else if (amount < wallet_min_feerate) {
2405+
error = strprintf("The requested fee rate of %s cannot be less than the wallet min fee rate of %s. %s", amount_str, wallet_min_feerate.ToString(FeeEstimateMode::SAT_VB), current_setting);
2406+
} else if (amount > wallet_max_feerate) {
2407+
error = strprintf("The requested fee rate of %s cannot be greater than the wallet max fee rate of %s. %s", amount_str, wallet_max_feerate.ToString(FeeEstimateMode::SAT_VB), current_setting);
2408+
} else {
2409+
wallet.m_pay_tx_fee = amount;
2410+
result = "Fee rate for transactions with this wallet successfully set to " + amount_str;
2411+
}
2412+
CHECK_NONFATAL(result.empty() != error.empty());
2413+
2414+
UniValue obj{UniValue::VOBJ};
2415+
obj.pushKV("wallet_name", wallet.GetName());
2416+
obj.pushKV("fee_rate", ValueFromFeeRate(wallet.m_pay_tx_fee));
2417+
if (error.empty()) {
2418+
obj.pushKV("result", result);
2419+
} else {
2420+
obj.pushKV("error", error);
2421+
}
2422+
return obj;
2423+
},
2424+
};
2425+
}
2426+
23522427
static RPCHelpMan getbalances()
23532428
{
23542429
return RPCHelpMan{
@@ -4607,6 +4682,7 @@ static const CRPCCommand commands[] =
46074682
{ "wallet", &send, },
46084683
{ "wallet", &sendmany, },
46094684
{ "wallet", &sendtoaddress, },
4685+
{ "wallet", &setfeerate, },
46104686
{ "wallet", &sethdseed, },
46114687
{ "wallet", &setlabel, },
46124688
{ "wallet", &settxfee, },

test/functional/rpc_help.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ def test_client_conversion_table(self):
8686
assert argname == 'dummy', ('WARNING: conversion mismatch for argument named %s (%s)' % (argname, list(zip(all_methods_by_argname[argname], converts_by_argname[argname]))))
8787

8888
def test_categories(self):
89+
self.log.info("Test RPC help categories")
8990
node = self.nodes[0]
9091

9192
# wrong argument count
@@ -111,6 +112,7 @@ def test_categories(self):
111112
assert_equal(titles, components)
112113

113114
def dump_help(self):
115+
self.log.info("Test dump RPC help")
114116
dump_dir = os.path.join(self.options.tmpdir, 'rpc_help_dump')
115117
os.mkdir(dump_dir)
116118
calls = [line.split(' ', 1)[0] for line in self.nodes[0].help().splitlines() if line and not line.startswith('==')]
@@ -120,9 +122,11 @@ def dump_help(self):
120122
f.write(self.nodes[0].help(call))
121123

122124
def wallet_help(self):
125+
self.log.info("Test wallet RPC help")
123126
assert 'getnewaddress ( "label" "address_type" )' in self.nodes[0].help('getnewaddress')
124127
self.restart_node(0, extra_args=['-nowallet=1'])
125128
assert 'getnewaddress ( "label" "address_type" )' in self.nodes[0].help('getnewaddress')
129+
assert "setfeerate amount" in self.nodes[0].help("setfeerate")
126130

127131

128132
if __name__ == '__main__':

0 commit comments

Comments
 (0)