Skip to content

Commit 1bc6a4a

Browse files
committed
Add RPC options for RBF, confirmation target, and conservative fee estimation.
Add support for setting each of these attributes on a per RPC call basis to sendtoaddress, sendmany, fundrawtransaction (already had RBF), and bumpfee (already had RBF and conf target).
1 parent d5300fc commit 1bc6a4a

File tree

7 files changed

+120
-16
lines changed

7 files changed

+120
-16
lines changed

src/policy/fees.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,19 @@ std::string StringForFeeReason(FeeReason reason) {
3636
return reason_string->second;
3737
}
3838

39+
boost::optional<FeeEstimateMode> FeeModeForString(std::string mode_string) {
40+
static const std::map<std::string, FeeEstimateMode> fee_modes = {
41+
{"UNSET", FeeEstimateMode::UNSET},
42+
{"ECONOMICAL", FeeEstimateMode::ECONOMICAL},
43+
{"CONSERVATIVE", FeeEstimateMode::CONSERVATIVE},
44+
};
45+
auto mode = fee_modes.find(mode_string);
46+
47+
if (mode == fee_modes.end()) return boost::none;
48+
49+
return mode->second;
50+
}
51+
3952
/**
4053
* We will instantiate an instance of this class to track transactions that were
4154
* included in a block. We will lump transactions into a bucket according to their

src/policy/fees.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#include "random.h"
1212
#include "sync.h"
1313

14+
#include <boost/optional.hpp>
15+
1416
#include <map>
1517
#include <string>
1618
#include <vector>
@@ -97,6 +99,8 @@ enum class FeeEstimateMode {
9799
CONSERVATIVE, //! Force estimateSmartFee to use conservative estimates
98100
};
99101

102+
boost::optional<FeeEstimateMode> FeeModeForString(std::string mode_string);
103+
100104
/* Used to return detailed information about a feerate bucket */
101105
struct EstimatorBucket
102106
{

src/qt/walletmodel.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -669,7 +669,7 @@ bool WalletModel::bumpFee(uint256 hash)
669669
std::unique_ptr<CFeeBumper> feeBump;
670670
{
671671
LOCK2(cs_main, wallet->cs_wallet);
672-
feeBump.reset(new CFeeBumper(wallet, hash, nTxConfirmTarget, false, 0, true));
672+
feeBump.reset(new CFeeBumper(wallet, hash, nTxConfirmTarget, false, 0, true, FeeEstimateMode::UNSET));
673673
}
674674
if (feeBump->getResult() != BumpFeeResult::OK)
675675
{

src/rpc/client.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
3737
{ "getnetworkhashps", 1, "height" },
3838
{ "sendtoaddress", 1, "amount" },
3939
{ "sendtoaddress", 4, "subtractfeefromamount" },
40+
{ "sendtoaddress", 5 , "opt_in_rbf" },
41+
{ "sendtoaddress", 6 , "conf_target" },
4042
{ "settxfee", 0, "amount" },
4143
{ "getreceivedbyaddress", 1, "minconf" },
4244
{ "getreceivedbyaccount", 1, "minconf" },
@@ -69,6 +71,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
6971
{ "sendmany", 1, "amounts" },
7072
{ "sendmany", 2, "minconf" },
7173
{ "sendmany", 4, "subtractfeefrom" },
74+
{ "sendmany", 5 , "opt_in_rbf" },
75+
{ "sendmany", 6 , "conf_target" },
7276
{ "addmultisigaddress", 0, "nrequired" },
7377
{ "addmultisigaddress", 1, "keys" },
7478
{ "createmultisig", 0, "nrequired" },

src/wallet/feebumper.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ bool CFeeBumper::preconditionChecks(const CWallet *pWallet, const CWalletTx& wtx
6666
return true;
6767
}
6868

69-
CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, int newConfirmTarget, bool ignoreGlobalPayTxFee, CAmount totalFee, bool newTxReplaceable)
69+
CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, int newConfirmTarget, bool ignoreGlobalPayTxFee, CAmount totalFee, bool newTxReplaceable, FeeEstimateMode fee_mode)
7070
:
7171
txid(std::move(txidIn)),
7272
nOldFee(0),
@@ -165,7 +165,7 @@ CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, int newConf
165165
nNewFee = totalFee;
166166
nNewFeeRate = CFeeRate(totalFee, maxNewTxSize);
167167
} else {
168-
bool conservative_estimate = CalculateEstimateType(FeeEstimateMode::UNSET, newTxReplaceable);
168+
bool conservative_estimate = CalculateEstimateType(fee_mode, newTxReplaceable);
169169
nNewFee = CWallet::GetMinimumFee(maxNewTxSize, newConfirmTarget, mempool, ::feeEstimator, nullptr /* FeeCalculation */, ignoreGlobalPayTxFee, conservative_estimate);
170170
nNewFeeRate = CFeeRate(nNewFee, maxNewTxSize);
171171

src/wallet/feebumper.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
class CWallet;
1111
class CWalletTx;
1212
class uint256;
13+
enum class FeeEstimateMode;
1314

1415
enum class BumpFeeResult
1516
{
@@ -24,7 +25,7 @@ enum class BumpFeeResult
2425
class CFeeBumper
2526
{
2627
public:
27-
CFeeBumper(const CWallet *pWalletIn, const uint256 txidIn, int newConfirmTarget, bool ignoreGlobalPayTxFee, CAmount totalFee, bool newTxReplaceable);
28+
CFeeBumper(const CWallet *pWalletIn, const uint256 txidIn, int newConfirmTarget, bool ignoreGlobalPayTxFee, CAmount totalFee, bool newTxReplaceable, FeeEstimateMode fee_mode);
2829
BumpFeeResult getResult() const { return currentResult; }
2930
const std::vector<std::string>& getErrors() const { return vErrors; }
3031
CAmount getOldFee() const { return nOldFee; }

src/wallet/rpcwallet.cpp

Lines changed: 94 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ UniValue getaddressesbyaccount(const JSONRPCRequest& request)
355355
return ret;
356356
}
357357

358-
static void SendMoney(CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, CWalletTx& wtxNew)
358+
static void SendMoney(CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, CWalletTx& wtxNew, CCoinControl *coin_control = nullptr)
359359
{
360360
CAmount curBalance = pwallet->GetBalance();
361361

@@ -381,7 +381,7 @@ static void SendMoney(CWallet * const pwallet, const CTxDestination &address, CA
381381
int nChangePosRet = -1;
382382
CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount};
383383
vecSend.push_back(recipient);
384-
if (!pwallet->CreateTransaction(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strError)) {
384+
if (!pwallet->CreateTransaction(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strError, coin_control)) {
385385
if (!fSubtractFeeFromAmount && nValue + nFeeRequired > curBalance)
386386
strError = strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired));
387387
throw JSONRPCError(RPC_WALLET_ERROR, strError);
@@ -400,7 +400,7 @@ UniValue sendtoaddress(const JSONRPCRequest& request)
400400
return NullUniValue;
401401
}
402402

403-
if (request.fHelp || request.params.size() < 2 || request.params.size() > 5)
403+
if (request.fHelp || request.params.size() < 2 || request.params.size() > 8)
404404
throw std::runtime_error(
405405
"sendtoaddress \"address\" amount ( \"comment\" \"comment_to\" subtractfeefromamount )\n"
406406
"\nSend an amount to a given address.\n"
@@ -415,6 +415,12 @@ UniValue sendtoaddress(const JSONRPCRequest& request)
415415
" transaction, just kept in your wallet.\n"
416416
"5. subtractfeefromamount (boolean, optional, default=false) The fee will be deducted from the amount being sent.\n"
417417
" The recipient will receive less bitcoins than you enter in the amount field.\n"
418+
"6. opt_in_rbf (boolean, optional) Allow this transaction to be replaced by a transaction with higher fees\n"
419+
"7. conf_target (numeric, optional) Confirmation target (in blocks)\n"
420+
"8. \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n"
421+
" \"UNSET\"\n"
422+
" \"ECONOMICAL\"\n"
423+
" \"CONSERVATIVE\"\n"
418424
"\nResult:\n"
419425
"\"txid\" (string) The transaction id.\n"
420426
"\nExamples:\n"
@@ -443,12 +449,32 @@ UniValue sendtoaddress(const JSONRPCRequest& request)
443449
wtx.mapValue["to"] = request.params[3].get_str();
444450

445451
bool fSubtractFeeFromAmount = false;
446-
if (request.params.size() > 4)
452+
if (request.params.size() > 4 && !request.params[4].isNull()) {
447453
fSubtractFeeFromAmount = request.params[4].get_bool();
454+
}
455+
456+
CCoinControl coin_control;
457+
if (request.params.size() > 5 && !request.params[5].isNull()) {
458+
coin_control.signalRbf = request.params[5].get_bool();
459+
}
460+
461+
if (request.params.size() > 6 && !request.params[6].isNull()) {
462+
coin_control.nConfirmTarget = request.params[6].get_int();
463+
}
464+
465+
if (request.params.size() > 7 && !request.params[7].isNull()) {
466+
if (boost::optional<FeeEstimateMode> fee_mode = FeeModeForString(request.params[7].get_str())) {
467+
coin_control.m_fee_mode = *fee_mode;
468+
}
469+
else {
470+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
471+
}
472+
}
473+
448474

449475
EnsureWalletIsUnlocked(pwallet);
450476

451-
SendMoney(pwallet, address.Get(), nAmount, fSubtractFeeFromAmount, wtx);
477+
SendMoney(pwallet, address.Get(), nAmount, fSubtractFeeFromAmount, wtx, &coin_control);
452478

453479
return wtx.GetHash().GetHex();
454480
}
@@ -887,7 +913,7 @@ UniValue sendmany(const JSONRPCRequest& request)
887913
return NullUniValue;
888914
}
889915

890-
if (request.fHelp || request.params.size() < 2 || request.params.size() > 5)
916+
if (request.fHelp || request.params.size() < 2 || request.params.size() > 8)
891917
throw std::runtime_error(
892918
"sendmany \"fromaccount\" {\"address\":amount,...} ( minconf \"comment\" [\"address\",...] )\n"
893919
"\nSend multiple times. Amounts are double-precision floating point numbers."
@@ -909,7 +935,13 @@ UniValue sendmany(const JSONRPCRequest& request)
909935
" \"address\" (string) Subtract fee from this address\n"
910936
" ,...\n"
911937
" ]\n"
912-
"\nResult:\n"
938+
"6. opt_in_rbf (boolean, optional) Allow this transaction to be replaced by a transaction with higher fees\n"
939+
"7. conf_target (numeric, optional) Confirmation target (in blocks)\n"
940+
"8. \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n"
941+
" \"UNSET\"\n"
942+
" \"ECONOMICAL\"\n"
943+
" \"CONSERVATIVE\"\n"
944+
"\nResult:\n"
913945
"\"txid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n"
914946
" the number of addresses.\n"
915947
"\nExamples:\n"
@@ -941,9 +973,27 @@ UniValue sendmany(const JSONRPCRequest& request)
941973
wtx.mapValue["comment"] = request.params[3].get_str();
942974

943975
UniValue subtractFeeFromAmount(UniValue::VARR);
944-
if (request.params.size() > 4)
976+
if (request.params.size() > 4 && !request.params[4].isNull())
945977
subtractFeeFromAmount = request.params[4].get_array();
946978

979+
CCoinControl coin_control;
980+
if (request.params.size() > 5 && !request.params[5].isNull()) {
981+
coin_control.signalRbf = request.params[5].get_bool();
982+
}
983+
984+
if (request.params.size() > 6 && !request.params[6].isNull()) {
985+
coin_control.nConfirmTarget = request.params[6].get_int();
986+
}
987+
988+
if (request.params.size() > 7 && !request.params[7].isNull()) {
989+
if (boost::optional<FeeEstimateMode> fee_mode = FeeModeForString(request.params[7].get_str())) {
990+
coin_control.m_fee_mode = *fee_mode;
991+
}
992+
else {
993+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
994+
}
995+
}
996+
947997
std::set<CBitcoinAddress> setAddress;
948998
std::vector<CRecipient> vecSend;
949999

@@ -988,7 +1038,7 @@ UniValue sendmany(const JSONRPCRequest& request)
9881038
CAmount nFeeRequired = 0;
9891039
int nChangePosRet = -1;
9901040
std::string strFailReason;
991-
bool fCreated = pwallet->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, nChangePosRet, strFailReason);
1041+
bool fCreated = pwallet->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, nChangePosRet, strFailReason, &coin_control);
9921042
if (!fCreated)
9931043
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason);
9941044
CValidationState state;
@@ -2657,6 +2707,11 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
26572707
" If no outputs are specified here, the sender pays the fee.\n"
26582708
" [vout_index,...]\n"
26592709
" \"optIntoRbf\" (boolean, optional) Allow this transaction to be replaced by a transaction with higher fees\n"
2710+
" \"conf_target\" (numeric, optional) Confirmation target (in blocks)\n"
2711+
" \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n"
2712+
" \"UNSET\"\n"
2713+
" \"ECONOMICAL\"\n"
2714+
" \"CONSERVATIVE\"\n"
26602715
" }\n"
26612716
" for backward compatibility: passing in a true instead of an object will result in {\"includeWatching\":true}\n"
26622717
"\nResult:\n"
@@ -2709,6 +2764,8 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
27092764
{"feeRate", UniValueType()}, // will be checked below
27102765
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
27112766
{"optIntoRbf", UniValueType(UniValue::VBOOL)},
2767+
{"conf_target", UniValueType(UniValue::VNUM)},
2768+
{"estimate_mode", UniValueType(UniValue::VSTR)},
27122769
},
27132770
true, true);
27142771

@@ -2745,6 +2802,17 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
27452802
if (options.exists("optIntoRbf")) {
27462803
coinControl.signalRbf = options["optIntoRbf"].get_bool();
27472804
}
2805+
if (options.exists("conf_target")) {
2806+
coinControl.nConfirmTarget = options["conf_target"].get_int();
2807+
}
2808+
if (options.exists("estimate_mode")) {
2809+
if (boost::optional<FeeEstimateMode> fee_mode = FeeModeForString(options["estimate_mode"].get_str())) {
2810+
coinControl.m_fee_mode = *fee_mode;
2811+
}
2812+
else {
2813+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
2814+
}
2815+
}
27482816
}
27492817
}
27502818

@@ -2822,6 +2890,10 @@ UniValue bumpfee(const JSONRPCRequest& request)
28222890
" so the new transaction will not be explicitly bip-125 replaceable (though it may\n"
28232891
" still be replaceable in practice, for example if it has unconfirmed ancestors which\n"
28242892
" are replaceable).\n"
2893+
" \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n"
2894+
" \"UNSET\"\n"
2895+
" \"ECONOMICAL\"\n"
2896+
" \"CONSERVATIVE\"\n"
28252897
" }\n"
28262898
"\nResult:\n"
28272899
"{\n"
@@ -2844,13 +2916,15 @@ UniValue bumpfee(const JSONRPCRequest& request)
28442916
int newConfirmTarget = nTxConfirmTarget;
28452917
CAmount totalFee = 0;
28462918
bool replaceable = true;
2919+
FeeEstimateMode fee_mode = FeeEstimateMode::UNSET;
28472920
if (request.params.size() > 1) {
28482921
UniValue options = request.params[1];
28492922
RPCTypeCheckObj(options,
28502923
{
28512924
{"confTarget", UniValueType(UniValue::VNUM)},
28522925
{"totalFee", UniValueType(UniValue::VNUM)},
28532926
{"replaceable", UniValueType(UniValue::VBOOL)},
2927+
{"estimate_mode", UniValueType(UniValue::VSTR)},
28542928
},
28552929
true, true);
28562930

@@ -2875,12 +2949,20 @@ UniValue bumpfee(const JSONRPCRequest& request)
28752949
if (options.exists("replaceable")) {
28762950
replaceable = options["replaceable"].get_bool();
28772951
}
2952+
if (options.exists("estimate_mode")) {
2953+
if (boost::optional<FeeEstimateMode> desired_fee_mode = FeeModeForString(options["estimate_mode"].get_str())) {
2954+
fee_mode = *desired_fee_mode;
2955+
}
2956+
else {
2957+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
2958+
}
2959+
}
28782960
}
28792961

28802962
LOCK2(cs_main, pwallet->cs_wallet);
28812963
EnsureWalletIsUnlocked(pwallet);
28822964

2883-
CFeeBumper feeBump(pwallet, hash, newConfirmTarget, ignoreGlobalPayTxFee, totalFee, replaceable);
2965+
CFeeBumper feeBump(pwallet, hash, newConfirmTarget, ignoreGlobalPayTxFee, totalFee, replaceable, fee_mode);
28842966
BumpFeeResult res = feeBump.getResult();
28852967
if (res != BumpFeeResult::OK)
28862968
{
@@ -2977,8 +3059,8 @@ static const CRPCCommand commands[] =
29773059
{ "wallet", "lockunspent", &lockunspent, true, {"unlock","transactions"} },
29783060
{ "wallet", "move", &movecmd, false, {"fromaccount","toaccount","amount","minconf","comment"} },
29793061
{ "wallet", "sendfrom", &sendfrom, false, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} },
2980-
{ "wallet", "sendmany", &sendmany, false, {"fromaccount","amounts","minconf","comment","subtractfeefrom"} },
2981-
{ "wallet", "sendtoaddress", &sendtoaddress, false, {"address","amount","comment","comment_to","subtractfeefromamount"} },
3062+
{ "wallet", "sendmany", &sendmany, false, {"fromaccount","amounts","minconf","comment","subtractfeefrom","opt_in_rbf","conf_target","estimate_mode"} },
3063+
{ "wallet", "sendtoaddress", &sendtoaddress, false, {"address","amount","comment","comment_to","subtractfeefromamount","opt_in_rbf","conf_target","estimate_mode"} },
29823064
{ "wallet", "setaccount", &setaccount, true, {"address","account"} },
29833065
{ "wallet", "settxfee", &settxfee, true, {"amount"} },
29843066
{ "wallet", "signmessage", &signmessage, true, {"address","message"} },

0 commit comments

Comments
 (0)