Skip to content

Commit f135923

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 f0bf33d commit f135923

File tree

7 files changed

+109
-18
lines changed

7 files changed

+109
-18
lines changed

src/policy/fees.cpp

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

39+
bool FeeModeFromString(const std::string& mode_string, FeeEstimateMode& fee_estimate_mode) {
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 false;
48+
49+
fee_estimate_mode = mode->second;
50+
return true;
51+
}
52+
3953
/**
4054
* We will instantiate an instance of this class to track transactions that were
4155
* included in a block. We will lump transactions into a bucket according to their

src/policy/fees.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ enum class FeeEstimateMode {
9797
CONSERVATIVE, //! Force estimateSmartFee to use conservative estimates
9898
};
9999

100+
bool FeeModeFromString(const std::string& mode_string, FeeEstimateMode& fee_estimate_mode);
101+
100102
/* Used to return detailed information about a feerate bucket */
101103
struct EstimatorBucket
102104
{

src/qt/walletmodel.cpp

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

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 , "replaceable" },
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 , "replaceable" },
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: 84 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ UniValue getaddressesbyaccount(const JSONRPCRequest& request)
356356
return ret;
357357
}
358358

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

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

404-
if (request.fHelp || request.params.size() < 2 || request.params.size() > 5)
404+
if (request.fHelp || request.params.size() < 2 || request.params.size() > 8)
405405
throw std::runtime_error(
406-
"sendtoaddress \"address\" amount ( \"comment\" \"comment_to\" subtractfeefromamount )\n"
406+
"sendtoaddress \"address\" amount ( \"comment\" \"comment_to\" subtractfeefromamount replaceable conf_target \"estimate_mode\")\n"
407407
"\nSend an amount to a given address.\n"
408408
+ HelpRequiringPassphrase(pwallet) +
409409
"\nArguments:\n"
@@ -416,6 +416,12 @@ UniValue sendtoaddress(const JSONRPCRequest& request)
416416
" transaction, just kept in your wallet.\n"
417417
"5. subtractfeefromamount (boolean, optional, default=false) The fee will be deducted from the amount being sent.\n"
418418
" The recipient will receive less bitcoins than you enter in the amount field.\n"
419+
"6. replaceable (boolean, optional) Allow this transaction to be replaced by a transaction with higher fees via BIP 125\n"
420+
"7. conf_target (numeric, optional) Confirmation target (in blocks)\n"
421+
"8. \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n"
422+
" \"UNSET\"\n"
423+
" \"ECONOMICAL\"\n"
424+
" \"CONSERVATIVE\"\n"
419425
"\nResult:\n"
420426
"\"txid\" (string) The transaction id.\n"
421427
"\nExamples:\n"
@@ -444,12 +450,29 @@ UniValue sendtoaddress(const JSONRPCRequest& request)
444450
wtx.mapValue["to"] = request.params[3].get_str();
445451

446452
bool fSubtractFeeFromAmount = false;
447-
if (request.params.size() > 4)
453+
if (request.params.size() > 4 && !request.params[4].isNull()) {
448454
fSubtractFeeFromAmount = request.params[4].get_bool();
455+
}
456+
457+
CCoinControl coin_control;
458+
if (request.params.size() > 5 && !request.params[5].isNull()) {
459+
coin_control.signalRbf = request.params[5].get_bool();
460+
}
461+
462+
if (request.params.size() > 6 && !request.params[6].isNull()) {
463+
coin_control.nConfirmTarget = request.params[6].get_int();
464+
}
465+
466+
if (request.params.size() > 7 && !request.params[7].isNull()) {
467+
if (!FeeModeFromString(request.params[7].get_str(), coin_control.m_fee_mode)) {
468+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
469+
}
470+
}
471+
449472

450473
EnsureWalletIsUnlocked(pwallet);
451474

452-
SendMoney(pwallet, address.Get(), nAmount, fSubtractFeeFromAmount, wtx);
475+
SendMoney(pwallet, address.Get(), nAmount, fSubtractFeeFromAmount, wtx, &coin_control);
453476

454477
return wtx.GetHash().GetHex();
455478
}
@@ -888,9 +911,9 @@ UniValue sendmany(const JSONRPCRequest& request)
888911
return NullUniValue;
889912
}
890913

891-
if (request.fHelp || request.params.size() < 2 || request.params.size() > 5)
914+
if (request.fHelp || request.params.size() < 2 || request.params.size() > 8)
892915
throw std::runtime_error(
893-
"sendmany \"fromaccount\" {\"address\":amount,...} ( minconf \"comment\" [\"address\",...] )\n"
916+
"sendmany \"fromaccount\" {\"address\":amount,...} ( minconf \"comment\" [\"address\",...] replaceable conf_target \"estimate_mode\")\n"
894917
"\nSend multiple times. Amounts are double-precision floating point numbers."
895918
+ HelpRequiringPassphrase(pwallet) + "\n"
896919
"\nArguments:\n"
@@ -910,7 +933,13 @@ UniValue sendmany(const JSONRPCRequest& request)
910933
" \"address\" (string) Subtract fee from this address\n"
911934
" ,...\n"
912935
" ]\n"
913-
"\nResult:\n"
936+
"6. replaceable (boolean, optional) Allow this transaction to be replaced by a transaction with higher fees via BIP 125\n"
937+
"7. conf_target (numeric, optional) Confirmation target (in blocks)\n"
938+
"8. \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n"
939+
" \"UNSET\"\n"
940+
" \"ECONOMICAL\"\n"
941+
" \"CONSERVATIVE\"\n"
942+
"\nResult:\n"
914943
"\"txid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n"
915944
" the number of addresses.\n"
916945
"\nExamples:\n"
@@ -942,9 +971,24 @@ UniValue sendmany(const JSONRPCRequest& request)
942971
wtx.mapValue["comment"] = request.params[3].get_str();
943972

944973
UniValue subtractFeeFromAmount(UniValue::VARR);
945-
if (request.params.size() > 4)
974+
if (request.params.size() > 4 && !request.params[4].isNull())
946975
subtractFeeFromAmount = request.params[4].get_array();
947976

977+
CCoinControl coin_control;
978+
if (request.params.size() > 5 && !request.params[5].isNull()) {
979+
coin_control.signalRbf = request.params[5].get_bool();
980+
}
981+
982+
if (request.params.size() > 6 && !request.params[6].isNull()) {
983+
coin_control.nConfirmTarget = request.params[6].get_int();
984+
}
985+
986+
if (request.params.size() > 7 && !request.params[7].isNull()) {
987+
if (!FeeModeFromString(request.params[7].get_str(), coin_control.m_fee_mode)) {
988+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
989+
}
990+
}
991+
948992
std::set<CBitcoinAddress> setAddress;
949993
std::vector<CRecipient> vecSend;
950994

@@ -989,7 +1033,7 @@ UniValue sendmany(const JSONRPCRequest& request)
9891033
CAmount nFeeRequired = 0;
9901034
int nChangePosRet = -1;
9911035
std::string strFailReason;
992-
bool fCreated = pwallet->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, nChangePosRet, strFailReason);
1036+
bool fCreated = pwallet->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, nChangePosRet, strFailReason, &coin_control);
9931037
if (!fCreated)
9941038
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason);
9951039
CValidationState state;
@@ -2658,6 +2702,11 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
26582702
" [vout_index,...]\n"
26592703
" \"replaceable\" (boolean, optional) Marks this transaction as BIP125 replaceable.\n"
26602704
" Allows this transaction to be replaced by a transaction with higher fees\n"
2705+
" \"conf_target\" (numeric, optional) Confirmation target (in blocks)\n"
2706+
" \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n"
2707+
" \"UNSET\"\n"
2708+
" \"ECONOMICAL\"\n"
2709+
" \"CONSERVATIVE\"\n"
26612710
" }\n"
26622711
" for backward compatibility: passing in a true instead of an object will result in {\"includeWatching\":true}\n"
26632712
"\nResult:\n"
@@ -2710,6 +2759,8 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
27102759
{"feeRate", UniValueType()}, // will be checked below
27112760
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
27122761
{"replaceable", UniValueType(UniValue::VBOOL)},
2762+
{"conf_target", UniValueType(UniValue::VNUM)},
2763+
{"estimate_mode", UniValueType(UniValue::VSTR)},
27132764
},
27142765
true, true);
27152766

@@ -2746,6 +2797,14 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
27462797
if (options.exists("replaceable")) {
27472798
coinControl.signalRbf = options["replaceable"].get_bool();
27482799
}
2800+
if (options.exists("conf_target")) {
2801+
coinControl.nConfirmTarget = options["conf_target"].get_int();
2802+
}
2803+
if (options.exists("estimate_mode")) {
2804+
if (!FeeModeFromString(options["estimate_mode"].get_str(), coinControl.m_fee_mode)) {
2805+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
2806+
}
2807+
}
27492808
}
27502809
}
27512810

@@ -2823,6 +2882,10 @@ UniValue bumpfee(const JSONRPCRequest& request)
28232882
" so the new transaction will not be explicitly bip-125 replaceable (though it may\n"
28242883
" still be replaceable in practice, for example if it has unconfirmed ancestors which\n"
28252884
" are replaceable).\n"
2885+
" \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n"
2886+
" \"UNSET\"\n"
2887+
" \"ECONOMICAL\"\n"
2888+
" \"CONSERVATIVE\"\n"
28262889
" }\n"
28272890
"\nResult:\n"
28282891
"{\n"
@@ -2845,13 +2908,15 @@ UniValue bumpfee(const JSONRPCRequest& request)
28452908
int newConfirmTarget = nTxConfirmTarget;
28462909
CAmount totalFee = 0;
28472910
bool replaceable = true;
2911+
FeeEstimateMode fee_mode = FeeEstimateMode::UNSET;
28482912
if (request.params.size() > 1) {
28492913
UniValue options = request.params[1];
28502914
RPCTypeCheckObj(options,
28512915
{
28522916
{"confTarget", UniValueType(UniValue::VNUM)},
28532917
{"totalFee", UniValueType(UniValue::VNUM)},
28542918
{"replaceable", UniValueType(UniValue::VBOOL)},
2919+
{"estimate_mode", UniValueType(UniValue::VSTR)},
28552920
},
28562921
true, true);
28572922

@@ -2876,12 +2941,17 @@ UniValue bumpfee(const JSONRPCRequest& request)
28762941
if (options.exists("replaceable")) {
28772942
replaceable = options["replaceable"].get_bool();
28782943
}
2944+
if (options.exists("estimate_mode")) {
2945+
if (!FeeModeFromString(options["estimate_mode"].get_str(), fee_mode)) {
2946+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
2947+
}
2948+
}
28792949
}
28802950

28812951
LOCK2(cs_main, pwallet->cs_wallet);
28822952
EnsureWalletIsUnlocked(pwallet);
28832953

2884-
CFeeBumper feeBump(pwallet, hash, newConfirmTarget, ignoreGlobalPayTxFee, totalFee, replaceable);
2954+
CFeeBumper feeBump(pwallet, hash, newConfirmTarget, ignoreGlobalPayTxFee, totalFee, replaceable, fee_mode);
28852955
BumpFeeResult res = feeBump.getResult();
28862956
if (res != BumpFeeResult::OK)
28872957
{
@@ -3023,8 +3093,8 @@ static const CRPCCommand commands[] =
30233093
{ "wallet", "lockunspent", &lockunspent, true, {"unlock","transactions"} },
30243094
{ "wallet", "move", &movecmd, false, {"fromaccount","toaccount","amount","minconf","comment"} },
30253095
{ "wallet", "sendfrom", &sendfrom, false, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} },
3026-
{ "wallet", "sendmany", &sendmany, false, {"fromaccount","amounts","minconf","comment","subtractfeefrom"} },
3027-
{ "wallet", "sendtoaddress", &sendtoaddress, false, {"address","amount","comment","comment_to","subtractfeefromamount"} },
3096+
{ "wallet", "sendmany", &sendmany, false, {"fromaccount","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode"} },
3097+
{ "wallet", "sendtoaddress", &sendtoaddress, false, {"address","amount","comment","comment_to","subtractfeefromamount","replaceable","conf_target","estimate_mode"} },
30283098
{ "wallet", "setaccount", &setaccount, true, {"address","account"} },
30293099
{ "wallet", "settxfee", &settxfee, true, {"amount"} },
30303100
{ "wallet", "signmessage", &signmessage, true, {"address","message"} },

0 commit comments

Comments
 (0)