Skip to content

Commit c978483

Browse files
laanwjPastaPastaPasta
authored andcommitted
Merge bitcoin#10589: More economical fee estimates for RBF and RPC options to control
f135923 Add RPC options for RBF, confirmation target, and conservative fee estimation. (Alex Morcos) f0bf33d Change default fee estimation mode. (Alex Morcos) e0738e3 remove default argument from estimateSmartFee (Alex Morcos) d507c30 Introduce a fee estimate mode. (Alex Morcos) cfaef69 remove default argument from GetMinimumFee (Alex Morcos) Tree-SHA512: 49c3a49a6893790a7e8b4e93a48f123dd5307af26c2017800683b76b4df8fc904ba73402917878676242c7440e3e04288d0c1ff3c2c907418724efc03cedab50
1 parent c853c01 commit c978483

File tree

11 files changed

+133
-22
lines changed

11 files changed

+133
-22
lines changed

src/policy/fees.cpp

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

51+
bool FeeModeFromString(const std::string& mode_string, FeeEstimateMode& fee_estimate_mode) {
52+
static const std::map<std::string, FeeEstimateMode> fee_modes = {
53+
{"UNSET", FeeEstimateMode::UNSET},
54+
{"ECONOMICAL", FeeEstimateMode::ECONOMICAL},
55+
{"CONSERVATIVE", FeeEstimateMode::CONSERVATIVE},
56+
};
57+
auto mode = fee_modes.find(mode_string);
58+
59+
if (mode == fee_modes.end()) return false;
60+
61+
fee_estimate_mode = mode->second;
62+
return true;
63+
}
64+
5165
/**
5266
* We will instantiate an instance of this class to track transactions that were
5367
* included in a block. We will lump transactions into a bucket according to their

src/policy/fees.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,15 @@ enum class FeeReason {
9292

9393
std::string StringForFeeReason(FeeReason reason);
9494

95+
/* Used to determine type of fee estimation requested */
96+
enum class FeeEstimateMode {
97+
UNSET, //! Use default settings based on other criteria
98+
ECONOMICAL, //! Force estimateSmartFee to use non-conservative estimates
99+
CONSERVATIVE, //! Force estimateSmartFee to use conservative estimates
100+
};
101+
102+
bool FeeModeFromString(const std::string& mode_string, FeeEstimateMode& fee_estimate_mode);
103+
95104
/* Used to return detailed information about a feerate bucket */
96105
struct EstimatorBucket
97106
{
@@ -199,7 +208,7 @@ class CBlockPolicyEstimator
199208
* the closest target where one can be given. 'conservative' estimates are
200209
* valid over longer time horizons also.
201210
*/
202-
CFeeRate estimateSmartFee(int confTarget, FeeCalculation *feeCalc, const CTxMemPool& pool, bool conservative = true) const;
211+
CFeeRate estimateSmartFee(int confTarget, FeeCalculation *feeCalc, const CTxMemPool& pool, bool conservative) const;
203212

204213
/** Return a specific fee estimate calculation with a given success
205214
* threshold and time horizon, and optionally return detailed data about

src/qt/coincontroldialog.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,8 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
537537
else nBytesInputs += 148;
538538
}
539539

540+
bool conservative_estimate = CalculateEstimateType(FeeEstimateMode::UNSET, coinControl->signalRbf);
541+
540542
// calculation
541543
if (nQuantity > 0)
542544
{
@@ -549,7 +551,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
549551
nBytes -= 34;
550552

551553
// Fee
552-
nPayFee = CWallet::GetMinimumFee(nBytes, coinControl->nConfirmTarget, ::mempool, ::feeEstimator);
554+
nPayFee = CWallet::GetMinimumFee(nBytes, coinControl->nConfirmTarget, ::mempool, ::feeEstimator, nullptr /* FeeCalculation */, false /* ignoreGlobalPayTxFee */, conservative_estimate);
553555

554556
if (nPayAmount > 0)
555557
{
@@ -630,7 +632,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
630632
if (payTxFee.GetFeePerK() > 0)
631633
dFeeVary = (double)std::max(CWallet::GetRequiredFee(1000), payTxFee.GetFeePerK()) / 1000;
632634
else {
633-
dFeeVary = (double)std::max(CWallet::GetRequiredFee(1000), ::feeEstimator.estimateSmartFee(coinControl->nConfirmTarget, nullptr, ::mempool).GetFeePerK()) / 1000;
635+
dFeeVary = (double)std::max(CWallet::GetRequiredFee(1000), ::feeEstimator.estimateSmartFee(coinControl->nConfirmTarget, nullptr, ::mempool, conservative_estimate).GetFeePerK()) / 1000;
634636
}
635637
QString toolTip4 = tr("Can vary +/- %1 duff(s) per input.").arg(dFeeVary);
636638

src/qt/sendcoinsdialog.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ void SendCoinsDialog::setModel(WalletModel *_model)
223223
connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateFeeSectionControls()));
224224
connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateGlobalFeeVariables()));
225225
connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels()));
226+
connect(ui->optInRBF, SIGNAL(stateChanged(int)), this, SLOT(updateSmartFeeLabel()));
227+
connect(ui->optInRBF, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels()));
226228
ui->customFee->setSingleStep(CWallet::GetRequiredFee(1000));
227229
updateFeeSectionControls();
228230
updateMinFeeLabel();
@@ -764,7 +766,8 @@ void SendCoinsDialog::updateSmartFeeLabel()
764766

765767
int nBlocksToConfirm = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
766768
FeeCalculation feeCalc;
767-
CFeeRate feeRate = ::feeEstimator.estimateSmartFee(nBlocksToConfirm, &feeCalc, ::mempool);
769+
bool conservative_estimate = CalculateEstimateType(FeeEstimateMode::UNSET, ui->optInRBF->isChecked());
770+
CFeeRate feeRate = ::feeEstimator.estimateSmartFee(nBlocksToConfirm, &feeCalc, ::mempool, conservative_estimate);
768771
if (feeRate <= CFeeRate(0)) // not enough data => minfee
769772
{
770773
ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(),
@@ -936,6 +939,7 @@ void SendCoinsDialog::coinControlUpdateLabels()
936939
} else {
937940
CoinControlDialog::coinControl->nConfirmTarget = model->getDefaultConfirmTarget();
938941
}
942+
CoinControlDialog::coinControl->signalRbf = ui->optInRBF->isChecked();
939943

940944
for(int i = 0; i < ui->entries->count(); ++i)
941945
{

src/qt/walletmodel.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "keystore.h"
1919
#include "validation.h"
2020
#include "net.h" // for g_connman
21+
#include "policy/fees.h"
2122
#include "sync.h"
2223
#include "ui_interface.h"
2324
#include "util.h" // for GetBoolArg

src/rpc/client.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
4242
{ "sendtoaddress", 4, "subtractfeefromamount" },
4343
{ "sendtoaddress", 5, "use_is" },
4444
{ "sendtoaddress", 6, "use_ps" },
45+
{ "sendtoaddress", 7, "conf_target" },
4546
{ "instantsendtoaddress", 1, "address" },
4647
{ "instantsendtoaddress", 4, "comment_to" },
4748
{ "settxfee", 0, "amount" },
@@ -92,6 +93,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
9293
{ "sendmany", 5, "subtractfeefrom" },
9394
{ "sendmany", 6, "use_is" },
9495
{ "sendmany", 7, "use_ps" },
96+
{ "sendmany", 8, "conf_target" },
9597
{ "addmultisigaddress", 0, "nrequired" },
9698
{ "addmultisigaddress", 1, "keys" },
9799
{ "createmultisig", 0, "nrequired" },

src/test/policyestimator_tests.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,8 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
183183
mpool.TrimToSize(1);
184184
BOOST_CHECK(mpool.GetMinFee(1).GetFeePerK() > feeV[5]);
185185
for (int i = 1; i < 10; i++) {
186-
BOOST_CHECK(feeEst.estimateSmartFee(i, nullptr, mpool).GetFeePerK() >= feeEst.estimateRawFee(i, 0.85, FeeEstimateHorizon::MED_HALFLIFE).GetFeePerK());
187-
BOOST_CHECK(feeEst.estimateSmartFee(i, nullptr, mpool).GetFeePerK() >= mpool.GetMinFee(1).GetFeePerK());
186+
BOOST_CHECK(feeEst.estimateSmartFee(i, nullptr, mpool, true).GetFeePerK() >= feeEst.estimateRawFee(i, 0.85, FeeEstimateHorizon::MED_HALFLIFE).GetFeePerK());
187+
BOOST_CHECK(feeEst.estimateSmartFee(i, nullptr, mpool, true).GetFeePerK() >= mpool.GetMinFee(1).GetFeePerK());
188188
}
189189
}
190190

src/wallet/coincontrol.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#define BITCOIN_WALLET_COINCONTROL_H
77

88
#include "policy/feerate.h"
9+
#include "policy/fees.h"
910
#include "primitives/transaction.h"
1011

1112
/** Coin Control Features. */
@@ -26,6 +27,8 @@ class CCoinControl
2627
CFeeRate nFeeRate;
2728
//! Override the default confirmation target, 0 = use default
2829
int nConfirmTarget;
30+
//! Fee estimation mode to control arguments to estimateSmartFee
31+
FeeEstimateMode m_fee_mode;
2932

3033
CCoinControl()
3134
{
@@ -43,6 +46,7 @@ class CCoinControl
4346
nFeeRate = CFeeRate(0);
4447
fOverrideFeeRate = false;
4548
nConfirmTarget = 0;
49+
m_fee_mode = FeeEstimateMode::UNSET;
4650
}
4751

4852
bool HasSelected() const

src/wallet/rpcwallet.cpp

Lines changed: 69 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ UniValue getaddressesbyaccount(const JSONRPCRequest& request)
374374
return ret;
375375
}
376376

377-
static void SendMoney(CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, CWalletTx& wtxNew, bool fUsePrivateSend = false)
377+
static void SendMoney(CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, CWalletTx& wtxNew, bool fUsePrivateSend = false, CCoinControl *coin_control = nullptr)
378378
{
379379
CAmount curBalance = pwallet->GetBalance();
380380

@@ -401,7 +401,7 @@ static void SendMoney(CWallet * const pwallet, const CTxDestination &address, CA
401401
CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount};
402402
vecSend.push_back(recipient);
403403
if (!pwallet->CreateTransaction(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet,
404-
strError, nullptr, true, fUsePrivateSend ? ONLY_DENOMINATED : ALL_COINS)) {
404+
strError, nullptr, true, fUsePrivateSend ? ONLY_DENOMINATED : ALL_COINS, coin_control)) {
405405
if (!fSubtractFeeFromAmount && nValue + nFeeRequired > curBalance)
406406
strError = strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired));
407407
throw JSONRPCError(RPC_WALLET_ERROR, strError);
@@ -420,9 +420,9 @@ UniValue sendtoaddress(const JSONRPCRequest& request)
420420
return NullUniValue;
421421
}
422422

423-
if (request.fHelp || request.params.size() < 2 || request.params.size() > 7)
423+
if (request.fHelp || request.params.size() < 2 || request.params.size() > 9)
424424
throw std::runtime_error(
425-
"sendtoaddress \"address\" amount ( \"comment\" \"comment_to\" subtractfeefromamount use_is use_ps )\n"
425+
"sendtoaddress \"address\" amount ( \"comment\" \"comment_to\" subtractfeefromamount use_is use_ps conf_target \"estimate_mode\")\n"
426426
"\nSend an amount to a given address.\n"
427427
+ HelpRequiringPassphrase(pwallet) +
428428
"\nArguments:\n"
@@ -437,6 +437,11 @@ UniValue sendtoaddress(const JSONRPCRequest& request)
437437
" The recipient will receive less amount of Dash than you enter in the amount field.\n"
438438
"6. \"use_is\" (bool, optional, default=false) Deprecated and ignored\n"
439439
"7. \"use_ps\" (bool, optional, default=false) Use anonymized funds only\n"
440+
"8. conf_target (numeric, optional) Confirmation target (in blocks)\n"
441+
"9. \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n"
442+
" \"UNSET\"\n"
443+
" \"ECONOMICAL\"\n"
444+
" \"CONSERVATIVE\"\n"
440445
"\nResult:\n"
441446
"\"txid\" (string) The transaction id.\n"
442447
"\nExamples:\n"
@@ -465,16 +470,33 @@ UniValue sendtoaddress(const JSONRPCRequest& request)
465470
wtx.mapValue["to"] = request.params[3].get_str();
466471

467472
bool fSubtractFeeFromAmount = false;
468-
if (request.params.size() > 4)
473+
if (request.params.size() > 4 && !request.params[4].isNull()) {
469474
fSubtractFeeFromAmount = request.params[4].get_bool();
475+
}
476+
477+
CCoinControl coin_control;
478+
if (request.params.size() > 5 && !request.params[5].isNull()) {
479+
coin_control.signalRbf = request.params[5].get_bool();
480+
}
481+
482+
if (request.params.size() > 6 && !request.params[6].isNull()) {
483+
coin_control.nConfirmTarget = request.params[6].get_int();
484+
}
485+
486+
if (request.params.size() > 7 && !request.params[7].isNull()) {
487+
if (!FeeModeFromString(request.params[7].get_str(), coin_control.m_fee_mode)) {
488+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
489+
}
490+
}
491+
470492

471493
bool fUsePrivateSend = false;
472494
if (request.params.size() > 6)
473495
fUsePrivateSend = request.params[6].get_bool();
474496

475497
EnsureWalletIsUnlocked(pwallet);
476498

477-
SendMoney(pwallet, address.Get(), nAmount, fSubtractFeeFromAmount, wtx, fUsePrivateSend);
499+
SendMoney(pwallet, address.Get(), nAmount, fSubtractFeeFromAmount, wtx, fUsePrivateSend, &coin_control);
478500

479501
return wtx.GetHash().GetHex();
480502
}
@@ -964,7 +986,7 @@ UniValue sendmany(const JSONRPCRequest& request)
964986

965987
if (request.fHelp || request.params.size() < 2 || request.params.size() > 8)
966988
throw std::runtime_error(
967-
"sendmany \"fromaccount\" {\"address\":amount,...} ( minconf addlocked \"comment\" [\"address\",...] subtractfeefrom use_is use_ps )\n"
989+
"sendmany \"fromaccount\" {\"address\":amount,...} ( minconf \"comment\" [\"address\",...] conf_target \"estimate_mode\")\n"
968990
"\nSend multiple times. Amounts are double-precision floating point numbers."
969991
+ HelpRequiringPassphrase(pwallet) + "\n"
970992
"\nArguments:\n"
@@ -987,7 +1009,12 @@ UniValue sendmany(const JSONRPCRequest& request)
9871009
" ]\n"
9881010
"7. \"use_is\" (bool, optional, default=false) Deprecated and ignored\n"
9891011
"8. \"use_ps\" (bool, optional, default=false) Use anonymized funds only\n"
990-
"\nResult:\n"
1012+
"9. conf_target (numeric, optional) Confirmation target (in blocks)\n"
1013+
"10. \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n"
1014+
" \"UNSET\"\n"
1015+
" \"ECONOMICAL\"\n"
1016+
" \"CONSERVATIVE\"\n"
1017+
"\nResult:\n"
9911018
"\"txid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n"
9921019
" the number of addresses.\n"
9931020
"\nExamples:\n"
@@ -1018,9 +1045,24 @@ UniValue sendmany(const JSONRPCRequest& request)
10181045
wtx.mapValue["comment"] = request.params[4].get_str();
10191046

10201047
UniValue subtractFeeFrom(UniValue::VARR);
1021-
if (request.params.size() > 5)
1048+
if (request.params.size() > 5 && !request.params[5].isNull())
10221049
subtractFeeFrom = request.params[5].get_array();
10231050

1051+
CCoinControl coin_control;
1052+
if (request.params.size() > 5 && !request.params[5].isNull()) {
1053+
coin_control.signalRbf = request.params[5].get_bool();
1054+
}
1055+
1056+
if (request.params.size() > 6 && !request.params[6].isNull()) {
1057+
coin_control.nConfirmTarget = request.params[6].get_int();
1058+
}
1059+
1060+
if (request.params.size() > 7 && !request.params[7].isNull()) {
1061+
if (!FeeModeFromString(request.params[7].get_str(), coin_control.m_fee_mode)) {
1062+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
1063+
}
1064+
}
1065+
10241066
std::set<CBitcoinAddress> setAddress;
10251067
std::vector<CRecipient> vecSend;
10261068

@@ -1070,7 +1112,7 @@ UniValue sendmany(const JSONRPCRequest& request)
10701112
fUsePrivateSend = request.params[7].get_bool();
10711113

10721114
bool fCreated = pwallet->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, nChangePosRet, strFailReason,
1073-
nullptr, true, fUsePrivateSend ? ONLY_DENOMINATED : ALL_COINS);
1115+
nullptr, true, fUsePrivateSend ? ONLY_DENOMINATED : ALL_COINS, &coin_control);
10741116
if (!fCreated)
10751117
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason);
10761118
CValidationState state;
@@ -2887,6 +2929,11 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
28872929
" Those recipients will receive less dash than you enter in their corresponding amount field.\n"
28882930
" If no outputs are specified here, the sender pays the fee.\n"
28892931
" [vout_index,...]\n"
2932+
" \"conf_target\" (numeric, optional) Confirmation target (in blocks)\n"
2933+
" \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n"
2934+
" \"UNSET\"\n"
2935+
" \"ECONOMICAL\"\n"
2936+
" \"CONSERVATIVE\"\n"
28902937
" }\n"
28912938
" for backward compatibility: passing in a true instead of an object will result in {\"includeWatching\":true}\n"
28922939
"\nResult:\n"
@@ -2937,6 +2984,8 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
29372984
{"reserveChangeKey", UniValueType(UniValue::VBOOL)},
29382985
{"feeRate", UniValueType()}, // will be checked below
29392986
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
2987+
{"conf_target", UniValueType(UniValue::VNUM)},
2988+
{"estimate_mode", UniValueType(UniValue::VSTR)},
29402989
},
29412990
true, true);
29422991

@@ -2969,6 +3018,14 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
29693018

29703019
if (options.exists("subtractFeeFromOutputs"))
29713020
subtractFeeFromOutputs = options["subtractFeeFromOutputs"].get_array();
3021+
if (options.exists("conf_target")) {
3022+
coinControl.nConfirmTarget = options["conf_target"].get_int();
3023+
}
3024+
if (options.exists("estimate_mode")) {
3025+
if (!FeeModeFromString(options["estimate_mode"].get_str(), coinControl.m_fee_mode)) {
3026+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
3027+
}
3028+
}
29723029
}
29733030
}
29743031

@@ -3134,8 +3191,8 @@ static const CRPCCommand commands[] =
31343191
{ "wallet", "lockunspent", &lockunspent, true, {"unlock","transactions"} },
31353192
{ "wallet", "move", &movecmd, false, {"fromaccount","toaccount","amount","minconf","comment"} },
31363193
{ "wallet", "sendfrom", &sendfrom, false, {"fromaccount","toaddress","amount","minconf","addlocked","comment","comment_to"} },
3137-
{ "wallet", "sendmany", &sendmany, false, {"fromaccount","amounts","minconf","addlocked","comment","subtractfeefrom"} },
3138-
{ "wallet", "sendtoaddress", &sendtoaddress, false, {"address","amount","comment","comment_to","subtractfeefromamount"} },
3194+
{ "wallet", "sendmany", &sendmany, false, {"fromaccount","amounts","minconf","addlocked","comment","subtractfeefrom","replaceable","conf_target","estimate_mode"} },
3195+
{ "wallet", "sendtoaddress", &sendtoaddress, false, {"address","amount","comment","comment_to","subtractfeefromamount","conf_target","estimate_mode"} },
31393196
{ "wallet", "setaccount", &setaccount, true, {"address","account"} },
31403197
{ "wallet", "settxfee", &settxfee, true, {"amount"} },
31413198
{ "wallet", "setprivatesendrounds", &setprivatesendrounds, true, {"rounds"} },

0 commit comments

Comments
 (0)