Skip to content

Commit 6fcf448

Browse files
committed
rpc/wallet: add two explicit modes to estimate_mode
1 parent b188d80 commit 6fcf448

File tree

3 files changed

+58
-41
lines changed

3 files changed

+58
-41
lines changed

src/policy/feerate.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ enum class FeeEstimateMode {
1919
UNSET, //!< Use default settings based on other criteria
2020
ECONOMICAL, //!< Force estimateSmartFee to use non-conservative estimates
2121
CONSERVATIVE, //!< Force estimateSmartFee to use conservative estimates
22+
BTC_KB, //!< Use explicit BTC/kB fee given in coin control
23+
SAT_B, //!< Use explicit sat/B fee given in coin control
2224
};
2325

2426
/**

src/util/fees.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ const std::vector<std::pair<std::string, FeeEstimateMode>>& FeeModeMap()
4040
{"unset", FeeEstimateMode::UNSET},
4141
{"economical", FeeEstimateMode::ECONOMICAL},
4242
{"conservative", FeeEstimateMode::CONSERVATIVE},
43+
{(CURRENCY_UNIT + "/kB"), FeeEstimateMode::BTC_KB},
44+
{(CURRENCY_ATOM + "/B"), FeeEstimateMode::SAT_B},
4345
};
4446
return FEE_MODES;
4547
}

src/wallet/rpcwallet.cpp

Lines changed: 54 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ using interfaces::FoundBlock;
4545
static const std::string WALLET_ENDPOINT_BASE = "/wallet/";
4646
static const std::string HELP_REQUIRING_PASSPHRASE{"\nRequires wallet passphrase to be set with walletpassphrase call if wallet is encrypted.\n"};
4747

48+
static const uint32_t WALLET_BTC_KB_TO_SAT_B = COIN / 1000; // 1 sat / B = 0.00001 BTC / kB
49+
4850
static inline bool GetAvoidReuseFlag(const CWallet* const pwallet, const UniValue& param) {
4951
bool can_avoid_reuse = pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
5052
bool avoid_reuse = param.isNull() ? can_avoid_reuse : param.get_bool();
@@ -191,6 +193,42 @@ static std::string LabelFromValue(const UniValue& value)
191193
return label;
192194
}
193195

196+
/**
197+
* Update coin control with fee estimation based on the given parameters
198+
*
199+
* @param[in] pwallet Wallet pointer
200+
* @param[in,out] cc Coin control which is to be updated
201+
* @param[in] estimate_mode String value (e.g. "ECONOMICAL")
202+
* @param[in] estimate_param Parameter (blocks to confirm, explicit fee rate, etc)
203+
* @throws a JSONRPCError if estimate_mode is unknown, or if estimate_param is missing when required
204+
*/
205+
static void SetFeeEstimateMode(const CWallet* pwallet, CCoinControl& cc, const UniValue& estimate_mode, const UniValue& estimate_param)
206+
{
207+
if (!estimate_mode.isNull()) {
208+
if (!FeeModeFromString(estimate_mode.get_str(), cc.m_fee_mode)) {
209+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
210+
}
211+
}
212+
213+
if (cc.m_fee_mode == FeeEstimateMode::BTC_KB || cc.m_fee_mode == FeeEstimateMode::SAT_B) {
214+
if (estimate_param.isNull()) {
215+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Selected estimate_mode requires a fee rate");
216+
}
217+
218+
CAmount fee_rate = AmountFromValue(estimate_param);
219+
if (cc.m_fee_mode == FeeEstimateMode::SAT_B) {
220+
fee_rate /= WALLET_BTC_KB_TO_SAT_B;
221+
}
222+
223+
cc.m_feerate = CFeeRate(fee_rate);
224+
225+
// default RBF to true for explicit fee rate modes
226+
if (cc.m_signal_bip125_rbf == boost::none) cc.m_signal_bip125_rbf = true;
227+
} else if (!estimate_param.isNull()) {
228+
cc.m_confirm_target = ParseConfirmTarget(estimate_param, pwallet->chain().estimateMaxBlocks());
229+
}
230+
}
231+
194232
static UniValue getnewaddress(const JSONRPCRequest& request)
195233
{
196234
RPCHelpMan{"getnewaddress",
@@ -369,7 +407,7 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
369407
{"subtractfeefromamount", RPCArg::Type::BOOL, /* default */ "false", "The fee will be deducted from the amount being sent.\n"
370408
" The recipient will receive less bitcoins than you enter in the amount field."},
371409
{"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"},
372-
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"},
410+
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks), or fee rate (for " + CURRENCY_UNIT + "/kB or " + CURRENCY_ATOM + "/B estimate modes)"},
373411
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
374412
" \"" + FeeModes("\"\n\"") + "\""},
375413
{"avoid_reuse", RPCArg::Type::BOOL, /* default */ "true", "(only available if avoid_reuse wallet flag is set) Avoid spending from dirty addresses; addresses are considered\n"
@@ -382,6 +420,8 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
382420
HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1")
383421
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"donation\" \"seans outpost\"")
384422
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"\" \"\" true")
423+
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"\" \"\" false true 0.00002 " + (CURRENCY_UNIT + "/kB"))
424+
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"\" \"\" false true 2 " + (CURRENCY_ATOM + "/B"))
385425
+ HelpExampleRpc("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\", 0.1, \"donation\", \"seans outpost\"")
386426
},
387427
}.Check(request);
@@ -423,20 +463,12 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
423463
coin_control.m_signal_bip125_rbf = request.params[5].get_bool();
424464
}
425465

426-
if (!request.params[6].isNull()) {
427-
coin_control.m_confirm_target = ParseConfirmTarget(request.params[6], pwallet->chain().estimateMaxBlocks());
428-
}
429-
430-
if (!request.params[7].isNull()) {
431-
if (!FeeModeFromString(request.params[7].get_str(), coin_control.m_fee_mode)) {
432-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
433-
}
434-
}
435-
436466
coin_control.m_avoid_address_reuse = GetAvoidReuseFlag(pwallet, request.params[8]);
437467
// We also enable partial spend avoidance if reuse avoidance is set.
438468
coin_control.m_avoid_partial_spends |= coin_control.m_avoid_address_reuse;
439469

470+
SetFeeEstimateMode(pwallet, coin_control, request.params[7], request.params[6]);
471+
440472
EnsureWalletIsUnlocked(pwallet);
441473

442474
CTransactionRef tx = SendMoney(pwallet, dest, nAmount, fSubtractFeeFromAmount, coin_control, std::move(mapValue));
@@ -778,7 +810,7 @@ static UniValue sendmany(const JSONRPCRequest& request)
778810
},
779811
},
780812
{"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"},
781-
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"},
813+
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks), or fee rate (for " + CURRENCY_UNIT + "/kB or " + CURRENCY_ATOM + "/B estimate modes)"},
782814
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
783815
" \"" + FeeModes("\"\n\"") + "\""},
784816
},
@@ -826,15 +858,7 @@ static UniValue sendmany(const JSONRPCRequest& request)
826858
coin_control.m_signal_bip125_rbf = request.params[5].get_bool();
827859
}
828860

829-
if (!request.params[6].isNull()) {
830-
coin_control.m_confirm_target = ParseConfirmTarget(request.params[6], pwallet->chain().estimateMaxBlocks());
831-
}
832-
833-
if (!request.params[7].isNull()) {
834-
if (!FeeModeFromString(request.params[7].get_str(), coin_control.m_fee_mode)) {
835-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
836-
}
837-
}
861+
SetFeeEstimateMode(pwallet, coin_control, request.params[7], request.params[6]);
838862

839863
std::set<CTxDestination> destinations;
840864
std::vector<CRecipient> vecSend;
@@ -2978,6 +3002,12 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f
29783002

29793003
if (options.exists("feeRate"))
29803004
{
3005+
if (options.exists("conf_target")) {
3006+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both conf_target and feeRate");
3007+
}
3008+
if (options.exists("estimate_mode")) {
3009+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and feeRate");
3010+
}
29813011
coinControl.m_feerate = CFeeRate(AmountFromValue(options["feeRate"]));
29823012
coinControl.fOverrideFeeRate = true;
29833013
}
@@ -2988,20 +3018,7 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f
29883018
if (options.exists("replaceable")) {
29893019
coinControl.m_signal_bip125_rbf = options["replaceable"].get_bool();
29903020
}
2991-
if (options.exists("conf_target")) {
2992-
if (options.exists("feeRate")) {
2993-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both conf_target and feeRate");
2994-
}
2995-
coinControl.m_confirm_target = ParseConfirmTarget(options["conf_target"], pwallet->chain().estimateMaxBlocks());
2996-
}
2997-
if (options.exists("estimate_mode")) {
2998-
if (options.exists("feeRate")) {
2999-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and feeRate");
3000-
}
3001-
if (!FeeModeFromString(options["estimate_mode"].get_str(), coinControl.m_fee_mode)) {
3002-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
3003-
}
3004-
}
3021+
SetFeeEstimateMode(pwallet, coinControl, options["estimate_mode"], options["conf_target"]);
30053022
}
30063023
} else {
30073024
// if options is null and not a bool
@@ -3068,7 +3085,7 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request)
30683085
},
30693086
{"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Marks this transaction as BIP125 replaceable.\n"
30703087
" Allows this transaction to be replaced by a transaction with higher fees"},
3071-
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"},
3088+
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks), or fee rate (for " + CURRENCY_UNIT + "/kB or " + CURRENCY_ATOM + "/B estimate modes)"},
30723089
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
30733090
" \"" + FeeModes("\"\n\"") + "\""},
30743091
},
@@ -3315,11 +3332,7 @@ static UniValue bumpfee(const JSONRPCRequest& request)
33153332
if (options.exists("replaceable")) {
33163333
coin_control.m_signal_bip125_rbf = options["replaceable"].get_bool();
33173334
}
3318-
if (options.exists("estimate_mode")) {
3319-
if (!FeeModeFromString(options["estimate_mode"].get_str(), coin_control.m_fee_mode)) {
3320-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
3321-
}
3322-
}
3335+
SetFeeEstimateMode(pwallet, coin_control, options["estimate_mode"], conf_target);
33233336
}
33243337

33253338
// Make sure the results are valid at least up to the most recent block

0 commit comments

Comments
 (0)