Browse Source

Merge #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
0.15
Wladimir J. van der Laan 7 years ago
parent
commit
104f5f21dc
No known key found for this signature in database
GPG Key ID: 1E4AED62986CD25D
  1. 14
      src/policy/fees.cpp
  2. 11
      src/policy/fees.h
  3. 6
      src/qt/coincontroldialog.cpp
  4. 6
      src/qt/sendcoinsdialog.cpp
  5. 3
      src/qt/walletmodel.cpp
  6. 4
      src/rpc/client.cpp
  7. 4
      src/test/policyestimator_tests.cpp
  8. 4
      src/wallet/coincontrol.h
  9. 5
      src/wallet/feebumper.cpp
  10. 3
      src/wallet/feebumper.h
  11. 96
      src/wallet/rpcwallet.cpp
  12. 21
      src/wallet/wallet.cpp
  13. 6
      src/wallet/wallet.h

14
src/policy/fees.cpp

@ -36,6 +36,20 @@ std::string StringForFeeReason(FeeReason reason) { @@ -36,6 +36,20 @@ std::string StringForFeeReason(FeeReason reason) {
return reason_string->second;
}
bool FeeModeFromString(const std::string& mode_string, FeeEstimateMode& fee_estimate_mode) {
static const std::map<std::string, FeeEstimateMode> fee_modes = {
{"UNSET", FeeEstimateMode::UNSET},
{"ECONOMICAL", FeeEstimateMode::ECONOMICAL},
{"CONSERVATIVE", FeeEstimateMode::CONSERVATIVE},
};
auto mode = fee_modes.find(mode_string);
if (mode == fee_modes.end()) return false;
fee_estimate_mode = mode->second;
return true;
}
/**
* We will instantiate an instance of this class to track transactions that were
* included in a block. We will lump transactions into a bucket according to their

11
src/policy/fees.h

@ -90,6 +90,15 @@ enum class FeeReason { @@ -90,6 +90,15 @@ enum class FeeReason {
std::string StringForFeeReason(FeeReason reason);
/* Used to determine type of fee estimation requested */
enum class FeeEstimateMode {
UNSET, //! Use default settings based on other criteria
ECONOMICAL, //! Force estimateSmartFee to use non-conservative estimates
CONSERVATIVE, //! Force estimateSmartFee to use conservative estimates
};
bool FeeModeFromString(const std::string& mode_string, FeeEstimateMode& fee_estimate_mode);
/* Used to return detailed information about a feerate bucket */
struct EstimatorBucket
{
@ -197,7 +206,7 @@ public: @@ -197,7 +206,7 @@ public:
* the closest target where one can be given. 'conservative' estimates are
* valid over longer time horizons also.
*/
CFeeRate estimateSmartFee(int confTarget, FeeCalculation *feeCalc, const CTxMemPool& pool, bool conservative = true) const;
CFeeRate estimateSmartFee(int confTarget, FeeCalculation *feeCalc, const CTxMemPool& pool, bool conservative) const;
/** Return a specific fee estimate calculation with a given success
* threshold and time horizon, and optionally return detailed data about

6
src/qt/coincontroldialog.cpp

@ -490,6 +490,8 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) @@ -490,6 +490,8 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
else nBytesInputs += 148;
}
bool conservative_estimate = CalculateEstimateType(FeeEstimateMode::UNSET, coinControl->signalRbf);
// calculation
if (nQuantity > 0)
{
@ -510,7 +512,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) @@ -510,7 +512,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
nBytes -= 34;
// Fee
nPayFee = CWallet::GetMinimumFee(nBytes, coinControl->nConfirmTarget, ::mempool, ::feeEstimator);
nPayFee = CWallet::GetMinimumFee(nBytes, coinControl->nConfirmTarget, ::mempool, ::feeEstimator, nullptr /* FeeCalculation */, false /* ignoreGlobalPayTxFee */, conservative_estimate);
if (nPayAmount > 0)
{
@ -585,7 +587,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) @@ -585,7 +587,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
if (payTxFee.GetFeePerK() > 0)
dFeeVary = (double)std::max(CWallet::GetRequiredFee(1000), payTxFee.GetFeePerK()) / 1000;
else {
dFeeVary = (double)std::max(CWallet::GetRequiredFee(1000), ::feeEstimator.estimateSmartFee(coinControl->nConfirmTarget, NULL, ::mempool).GetFeePerK()) / 1000;
dFeeVary = (double)std::max(CWallet::GetRequiredFee(1000), ::feeEstimator.estimateSmartFee(coinControl->nConfirmTarget, NULL, ::mempool, conservative_estimate).GetFeePerK()) / 1000;
}
QString toolTip4 = tr("Can vary +/- %1 satoshi(s) per input.").arg(dFeeVary);

6
src/qt/sendcoinsdialog.cpp

@ -166,6 +166,8 @@ void SendCoinsDialog::setModel(WalletModel *_model) @@ -166,6 +166,8 @@ void SendCoinsDialog::setModel(WalletModel *_model)
connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateFeeSectionControls()));
connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateGlobalFeeVariables()));
connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels()));
connect(ui->optInRBF, SIGNAL(stateChanged(int)), this, SLOT(updateSmartFeeLabel()));
connect(ui->optInRBF, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels()));
ui->customFee->setSingleStep(CWallet::GetRequiredFee(1000));
updateFeeSectionControls();
updateMinFeeLabel();
@ -652,7 +654,8 @@ void SendCoinsDialog::updateSmartFeeLabel() @@ -652,7 +654,8 @@ void SendCoinsDialog::updateSmartFeeLabel()
int nBlocksToConfirm = ui->sliderSmartFee->maximum() - ui->sliderSmartFee->value() + 2;
FeeCalculation feeCalc;
CFeeRate feeRate = ::feeEstimator.estimateSmartFee(nBlocksToConfirm, &feeCalc, ::mempool);
bool conservative_estimate = CalculateEstimateType(FeeEstimateMode::UNSET, ui->optInRBF->isChecked());
CFeeRate feeRate = ::feeEstimator.estimateSmartFee(nBlocksToConfirm, &feeCalc, ::mempool, conservative_estimate);
if (feeRate <= CFeeRate(0)) // not enough data => minfee
{
ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(),
@ -827,6 +830,7 @@ void SendCoinsDialog::coinControlUpdateLabels() @@ -827,6 +830,7 @@ void SendCoinsDialog::coinControlUpdateLabels()
} else {
CoinControlDialog::coinControl->nConfirmTarget = model->getDefaultConfirmTarget();
}
CoinControlDialog::coinControl->signalRbf = ui->optInRBF->isChecked();
for(int i = 0; i < ui->entries->count(); ++i)
{

3
src/qt/walletmodel.cpp

@ -19,6 +19,7 @@ @@ -19,6 +19,7 @@
#include "keystore.h"
#include "validation.h"
#include "net.h" // for g_connman
#include "policy/fees.h"
#include "policy/rbf.h"
#include "sync.h"
#include "ui_interface.h"
@ -667,7 +668,7 @@ bool WalletModel::bumpFee(uint256 hash) @@ -667,7 +668,7 @@ bool WalletModel::bumpFee(uint256 hash)
std::unique_ptr<CFeeBumper> feeBump;
{
LOCK2(cs_main, wallet->cs_wallet);
feeBump.reset(new CFeeBumper(wallet, hash, nTxConfirmTarget, false, 0, true));
feeBump.reset(new CFeeBumper(wallet, hash, nTxConfirmTarget, false, 0, true, FeeEstimateMode::UNSET));
}
if (feeBump->getResult() != BumpFeeResult::OK)
{

4
src/rpc/client.cpp

@ -37,6 +37,8 @@ static const CRPCConvertParam vRPCConvertParams[] = @@ -37,6 +37,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "getnetworkhashps", 1, "height" },
{ "sendtoaddress", 1, "amount" },
{ "sendtoaddress", 4, "subtractfeefromamount" },
{ "sendtoaddress", 5 , "replaceable" },
{ "sendtoaddress", 6 , "conf_target" },
{ "settxfee", 0, "amount" },
{ "getreceivedbyaddress", 1, "minconf" },
{ "getreceivedbyaccount", 1, "minconf" },
@ -69,6 +71,8 @@ static const CRPCConvertParam vRPCConvertParams[] = @@ -69,6 +71,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "sendmany", 1, "amounts" },
{ "sendmany", 2, "minconf" },
{ "sendmany", 4, "subtractfeefrom" },
{ "sendmany", 5 , "replaceable" },
{ "sendmany", 6 , "conf_target" },
{ "addmultisigaddress", 0, "nrequired" },
{ "addmultisigaddress", 1, "keys" },
{ "createmultisig", 0, "nrequired" },

4
src/test/policyestimator_tests.cpp

@ -184,8 +184,8 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates) @@ -184,8 +184,8 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
mpool.TrimToSize(1);
BOOST_CHECK(mpool.GetMinFee(1).GetFeePerK() > feeV[5]);
for (int i = 1; i < 10; i++) {
BOOST_CHECK(feeEst.estimateSmartFee(i, NULL, mpool).GetFeePerK() >= feeEst.estimateRawFee(i, 0.85, FeeEstimateHorizon::MED_HALFLIFE).GetFeePerK());
BOOST_CHECK(feeEst.estimateSmartFee(i, NULL, mpool).GetFeePerK() >= mpool.GetMinFee(1).GetFeePerK());
BOOST_CHECK(feeEst.estimateSmartFee(i, NULL, mpool, true).GetFeePerK() >= feeEst.estimateRawFee(i, 0.85, FeeEstimateHorizon::MED_HALFLIFE).GetFeePerK());
BOOST_CHECK(feeEst.estimateSmartFee(i, NULL, mpool, true).GetFeePerK() >= mpool.GetMinFee(1).GetFeePerK());
}
}

4
src/wallet/coincontrol.h

@ -6,6 +6,7 @@ @@ -6,6 +6,7 @@
#define BITCOIN_WALLET_COINCONTROL_H
#include "policy/feerate.h"
#include "policy/fees.h"
#include "primitives/transaction.h"
#include "wallet/wallet.h"
@ -26,6 +27,8 @@ public: @@ -26,6 +27,8 @@ public:
int nConfirmTarget;
//! Signal BIP-125 replace by fee.
bool signalRbf;
//! Fee estimation mode to control arguments to estimateSmartFee
FeeEstimateMode m_fee_mode;
CCoinControl()
{
@ -42,6 +45,7 @@ public: @@ -42,6 +45,7 @@ public:
fOverrideFeeRate = false;
nConfirmTarget = 0;
signalRbf = fWalletRbf;
m_fee_mode = FeeEstimateMode::UNSET;
}
bool HasSelected() const

5
src/wallet/feebumper.cpp

@ -66,7 +66,7 @@ bool CFeeBumper::preconditionChecks(const CWallet *pWallet, const CWalletTx& wtx @@ -66,7 +66,7 @@ bool CFeeBumper::preconditionChecks(const CWallet *pWallet, const CWalletTx& wtx
return true;
}
CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, int newConfirmTarget, bool ignoreGlobalPayTxFee, CAmount totalFee, bool newTxReplaceable)
CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, int newConfirmTarget, bool ignoreGlobalPayTxFee, CAmount totalFee, bool newTxReplaceable, FeeEstimateMode fee_mode)
:
txid(std::move(txidIn)),
nOldFee(0),
@ -165,7 +165,8 @@ CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, int newConf @@ -165,7 +165,8 @@ CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, int newConf
nNewFee = totalFee;
nNewFeeRate = CFeeRate(totalFee, maxNewTxSize);
} else {
nNewFee = CWallet::GetMinimumFee(maxNewTxSize, newConfirmTarget, mempool, ::feeEstimator, nullptr, ignoreGlobalPayTxFee);
bool conservative_estimate = CalculateEstimateType(fee_mode, newTxReplaceable);
nNewFee = CWallet::GetMinimumFee(maxNewTxSize, newConfirmTarget, mempool, ::feeEstimator, nullptr /* FeeCalculation */, ignoreGlobalPayTxFee, conservative_estimate);
nNewFeeRate = CFeeRate(nNewFee, maxNewTxSize);
// New fee rate must be at least old rate + minimum incremental relay rate

3
src/wallet/feebumper.h

@ -10,6 +10,7 @@ @@ -10,6 +10,7 @@
class CWallet;
class CWalletTx;
class uint256;
enum class FeeEstimateMode;
enum class BumpFeeResult
{
@ -24,7 +25,7 @@ enum class BumpFeeResult @@ -24,7 +25,7 @@ enum class BumpFeeResult
class CFeeBumper
{
public:
CFeeBumper(const CWallet *pWalletIn, const uint256 txidIn, int newConfirmTarget, bool ignoreGlobalPayTxFee, CAmount totalFee, bool newTxReplaceable);
CFeeBumper(const CWallet *pWalletIn, const uint256 txidIn, int newConfirmTarget, bool ignoreGlobalPayTxFee, CAmount totalFee, bool newTxReplaceable, FeeEstimateMode fee_mode);
BumpFeeResult getResult() const { return currentResult; }
const std::vector<std::string>& getErrors() const { return vErrors; }
CAmount getOldFee() const { return nOldFee; }

96
src/wallet/rpcwallet.cpp

@ -356,7 +356,7 @@ UniValue getaddressesbyaccount(const JSONRPCRequest& request) @@ -356,7 +356,7 @@ UniValue getaddressesbyaccount(const JSONRPCRequest& request)
return ret;
}
static void SendMoney(CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, CWalletTx& wtxNew)
static void SendMoney(CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, CWalletTx& wtxNew, CCoinControl *coin_control = nullptr)
{
CAmount curBalance = pwallet->GetBalance();
@ -382,7 +382,7 @@ static void SendMoney(CWallet * const pwallet, const CTxDestination &address, CA @@ -382,7 +382,7 @@ static void SendMoney(CWallet * const pwallet, const CTxDestination &address, CA
int nChangePosRet = -1;
CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount};
vecSend.push_back(recipient);
if (!pwallet->CreateTransaction(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strError)) {
if (!pwallet->CreateTransaction(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strError, coin_control)) {
if (!fSubtractFeeFromAmount && nValue + nFeeRequired > curBalance)
strError = strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired));
throw JSONRPCError(RPC_WALLET_ERROR, strError);
@ -401,9 +401,9 @@ UniValue sendtoaddress(const JSONRPCRequest& request) @@ -401,9 +401,9 @@ UniValue sendtoaddress(const JSONRPCRequest& request)
return NullUniValue;
}
if (request.fHelp || request.params.size() < 2 || request.params.size() > 5)
if (request.fHelp || request.params.size() < 2 || request.params.size() > 8)
throw std::runtime_error(
"sendtoaddress \"address\" amount ( \"comment\" \"comment_to\" subtractfeefromamount )\n"
"sendtoaddress \"address\" amount ( \"comment\" \"comment_to\" subtractfeefromamount replaceable conf_target \"estimate_mode\")\n"
"\nSend an amount to a given address.\n"
+ HelpRequiringPassphrase(pwallet) +
"\nArguments:\n"
@ -416,6 +416,12 @@ UniValue sendtoaddress(const JSONRPCRequest& request) @@ -416,6 +416,12 @@ UniValue sendtoaddress(const JSONRPCRequest& request)
" transaction, just kept in your wallet.\n"
"5. subtractfeefromamount (boolean, optional, default=false) The fee will be deducted from the amount being sent.\n"
" The recipient will receive less bitcoins than you enter in the amount field.\n"
"6. replaceable (boolean, optional) Allow this transaction to be replaced by a transaction with higher fees via BIP 125\n"
"7. conf_target (numeric, optional) Confirmation target (in blocks)\n"
"8. \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n"
" \"UNSET\"\n"
" \"ECONOMICAL\"\n"
" \"CONSERVATIVE\"\n"
"\nResult:\n"
"\"txid\" (string) The transaction id.\n"
"\nExamples:\n"
@ -444,12 +450,29 @@ UniValue sendtoaddress(const JSONRPCRequest& request) @@ -444,12 +450,29 @@ UniValue sendtoaddress(const JSONRPCRequest& request)
wtx.mapValue["to"] = request.params[3].get_str();
bool fSubtractFeeFromAmount = false;
if (request.params.size() > 4)
if (request.params.size() > 4 && !request.params[4].isNull()) {
fSubtractFeeFromAmount = request.params[4].get_bool();
}
CCoinControl coin_control;
if (request.params.size() > 5 && !request.params[5].isNull()) {
coin_control.signalRbf = request.params[5].get_bool();
}
if (request.params.size() > 6 && !request.params[6].isNull()) {
coin_control.nConfirmTarget = request.params[6].get_int();
}
if (request.params.size() > 7 && !request.params[7].isNull()) {
if (!FeeModeFromString(request.params[7].get_str(), coin_control.m_fee_mode)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
}
}
EnsureWalletIsUnlocked(pwallet);
SendMoney(pwallet, address.Get(), nAmount, fSubtractFeeFromAmount, wtx);
SendMoney(pwallet, address.Get(), nAmount, fSubtractFeeFromAmount, wtx, &coin_control);
return wtx.GetHash().GetHex();
}
@ -888,9 +911,9 @@ UniValue sendmany(const JSONRPCRequest& request) @@ -888,9 +911,9 @@ UniValue sendmany(const JSONRPCRequest& request)
return NullUniValue;
}
if (request.fHelp || request.params.size() < 2 || request.params.size() > 5)
if (request.fHelp || request.params.size() < 2 || request.params.size() > 8)
throw std::runtime_error(
"sendmany \"fromaccount\" {\"address\":amount,...} ( minconf \"comment\" [\"address\",...] )\n"
"sendmany \"fromaccount\" {\"address\":amount,...} ( minconf \"comment\" [\"address\",...] replaceable conf_target \"estimate_mode\")\n"
"\nSend multiple times. Amounts are double-precision floating point numbers."
+ HelpRequiringPassphrase(pwallet) + "\n"
"\nArguments:\n"
@ -910,6 +933,12 @@ UniValue sendmany(const JSONRPCRequest& request) @@ -910,6 +933,12 @@ UniValue sendmany(const JSONRPCRequest& request)
" \"address\" (string) Subtract fee from this address\n"
" ,...\n"
" ]\n"
"6. replaceable (boolean, optional) Allow this transaction to be replaced by a transaction with higher fees via BIP 125\n"
"7. conf_target (numeric, optional) Confirmation target (in blocks)\n"
"8. \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n"
" \"UNSET\"\n"
" \"ECONOMICAL\"\n"
" \"CONSERVATIVE\"\n"
"\nResult:\n"
"\"txid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n"
" the number of addresses.\n"
@ -942,9 +971,24 @@ UniValue sendmany(const JSONRPCRequest& request) @@ -942,9 +971,24 @@ UniValue sendmany(const JSONRPCRequest& request)
wtx.mapValue["comment"] = request.params[3].get_str();
UniValue subtractFeeFromAmount(UniValue::VARR);
if (request.params.size() > 4)
if (request.params.size() > 4 && !request.params[4].isNull())
subtractFeeFromAmount = request.params[4].get_array();
CCoinControl coin_control;
if (request.params.size() > 5 && !request.params[5].isNull()) {
coin_control.signalRbf = request.params[5].get_bool();
}
if (request.params.size() > 6 && !request.params[6].isNull()) {
coin_control.nConfirmTarget = request.params[6].get_int();
}
if (request.params.size() > 7 && !request.params[7].isNull()) {
if (!FeeModeFromString(request.params[7].get_str(), coin_control.m_fee_mode)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
}
}
std::set<CBitcoinAddress> setAddress;
std::vector<CRecipient> vecSend;
@ -989,7 +1033,7 @@ UniValue sendmany(const JSONRPCRequest& request) @@ -989,7 +1033,7 @@ UniValue sendmany(const JSONRPCRequest& request)
CAmount nFeeRequired = 0;
int nChangePosRet = -1;
std::string strFailReason;
bool fCreated = pwallet->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, nChangePosRet, strFailReason);
bool fCreated = pwallet->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, nChangePosRet, strFailReason, &coin_control);
if (!fCreated)
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason);
CValidationState state;
@ -2658,6 +2702,11 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) @@ -2658,6 +2702,11 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
" [vout_index,...]\n"
" \"replaceable\" (boolean, optional) Marks this transaction as BIP125 replaceable.\n"
" Allows this transaction to be replaced by a transaction with higher fees\n"
" \"conf_target\" (numeric, optional) Confirmation target (in blocks)\n"
" \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n"
" \"UNSET\"\n"
" \"ECONOMICAL\"\n"
" \"CONSERVATIVE\"\n"
" }\n"
" for backward compatibility: passing in a true instead of an object will result in {\"includeWatching\":true}\n"
"\nResult:\n"
@ -2710,6 +2759,8 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) @@ -2710,6 +2759,8 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
{"feeRate", UniValueType()}, // will be checked below
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
{"replaceable", UniValueType(UniValue::VBOOL)},
{"conf_target", UniValueType(UniValue::VNUM)},
{"estimate_mode", UniValueType(UniValue::VSTR)},
},
true, true);
@ -2746,6 +2797,14 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) @@ -2746,6 +2797,14 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
if (options.exists("replaceable")) {
coinControl.signalRbf = options["replaceable"].get_bool();
}
if (options.exists("conf_target")) {
coinControl.nConfirmTarget = options["conf_target"].get_int();
}
if (options.exists("estimate_mode")) {
if (!FeeModeFromString(options["estimate_mode"].get_str(), coinControl.m_fee_mode)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
}
}
}
}
@ -2823,6 +2882,10 @@ UniValue bumpfee(const JSONRPCRequest& request) @@ -2823,6 +2882,10 @@ UniValue bumpfee(const JSONRPCRequest& request)
" so the new transaction will not be explicitly bip-125 replaceable (though it may\n"
" still be replaceable in practice, for example if it has unconfirmed ancestors which\n"
" are replaceable).\n"
" \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n"
" \"UNSET\"\n"
" \"ECONOMICAL\"\n"
" \"CONSERVATIVE\"\n"
" }\n"
"\nResult:\n"
"{\n"
@ -2845,6 +2908,7 @@ UniValue bumpfee(const JSONRPCRequest& request) @@ -2845,6 +2908,7 @@ UniValue bumpfee(const JSONRPCRequest& request)
int newConfirmTarget = nTxConfirmTarget;
CAmount totalFee = 0;
bool replaceable = true;
FeeEstimateMode fee_mode = FeeEstimateMode::UNSET;
if (request.params.size() > 1) {
UniValue options = request.params[1];
RPCTypeCheckObj(options,
@ -2852,6 +2916,7 @@ UniValue bumpfee(const JSONRPCRequest& request) @@ -2852,6 +2916,7 @@ UniValue bumpfee(const JSONRPCRequest& request)
{"confTarget", UniValueType(UniValue::VNUM)},
{"totalFee", UniValueType(UniValue::VNUM)},
{"replaceable", UniValueType(UniValue::VBOOL)},
{"estimate_mode", UniValueType(UniValue::VSTR)},
},
true, true);
@ -2876,12 +2941,17 @@ UniValue bumpfee(const JSONRPCRequest& request) @@ -2876,12 +2941,17 @@ UniValue bumpfee(const JSONRPCRequest& request)
if (options.exists("replaceable")) {
replaceable = options["replaceable"].get_bool();
}
if (options.exists("estimate_mode")) {
if (!FeeModeFromString(options["estimate_mode"].get_str(), fee_mode)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
}
}
}
LOCK2(cs_main, pwallet->cs_wallet);
EnsureWalletIsUnlocked(pwallet);
CFeeBumper feeBump(pwallet, hash, newConfirmTarget, ignoreGlobalPayTxFee, totalFee, replaceable);
CFeeBumper feeBump(pwallet, hash, newConfirmTarget, ignoreGlobalPayTxFee, totalFee, replaceable, fee_mode);
BumpFeeResult res = feeBump.getResult();
if (res != BumpFeeResult::OK)
{
@ -3023,8 +3093,8 @@ static const CRPCCommand commands[] = @@ -3023,8 +3093,8 @@ static const CRPCCommand commands[] =
{ "wallet", "lockunspent", &lockunspent, true, {"unlock","transactions"} },
{ "wallet", "move", &movecmd, false, {"fromaccount","toaccount","amount","minconf","comment"} },
{ "wallet", "sendfrom", &sendfrom, false, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} },
{ "wallet", "sendmany", &sendmany, false, {"fromaccount","amounts","minconf","comment","subtractfeefrom"} },
{ "wallet", "sendtoaddress", &sendtoaddress, false, {"address","amount","comment","comment_to","subtractfeefromamount"} },
{ "wallet", "sendmany", &sendmany, false, {"fromaccount","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode"} },
{ "wallet", "sendtoaddress", &sendtoaddress, false, {"address","amount","comment","comment_to","subtractfeefromamount","replaceable","conf_target","estimate_mode"} },
{ "wallet", "setaccount", &setaccount, true, {"address","account"} },
{ "wallet", "settxfee", &settxfee, true, {"amount"} },
{ "wallet", "signmessage", &signmessage, true, {"address","message"} },

21
src/wallet/wallet.cpp

@ -2724,7 +2724,10 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT @@ -2724,7 +2724,10 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
if (coinControl && coinControl->nConfirmTarget > 0)
currentConfirmationTarget = coinControl->nConfirmTarget;
CAmount nFeeNeeded = GetMinimumFee(nBytes, currentConfirmationTarget, ::mempool, ::feeEstimator, &feeCalc);
// Allow to override the default fee estimate mode over the CoinControl instance
bool conservative_estimate = CalculateEstimateType(coinControl ? coinControl->m_fee_mode : FeeEstimateMode::UNSET, rbf);
CAmount nFeeNeeded = GetMinimumFee(nBytes, currentConfirmationTarget, ::mempool, ::feeEstimator, &feeCalc, false /* ignoreGlobalPayTxFee */, conservative_estimate);
if (coinControl && coinControl->fOverrideFeeRate)
nFeeNeeded = coinControl->nFeeRate.GetFee(nBytes);
@ -2905,13 +2908,13 @@ CAmount CWallet::GetRequiredFee(unsigned int nTxBytes) @@ -2905,13 +2908,13 @@ CAmount CWallet::GetRequiredFee(unsigned int nTxBytes)
return std::max(minTxFee.GetFee(nTxBytes), ::minRelayTxFee.GetFee(nTxBytes));
}
CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation *feeCalc, bool ignoreGlobalPayTxFee)
CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation *feeCalc, bool ignoreGlobalPayTxFee, bool conservative_estimate)
{
// payTxFee is the user-set global for desired feerate
CAmount nFeeNeeded = payTxFee.GetFee(nTxBytes);
// User didn't set: use -txconfirmtarget to estimate...
if (nFeeNeeded == 0 || ignoreGlobalPayTxFee) {
nFeeNeeded = estimator.estimateSmartFee(nConfirmTarget, feeCalc, pool, true).GetFee(nTxBytes);
nFeeNeeded = estimator.estimateSmartFee(nConfirmTarget, feeCalc, pool, conservative_estimate).GetFee(nTxBytes);
// ... unless we don't have enough mempool data for estimatefee, then use fallbackFee
if (nFeeNeeded == 0) {
nFeeNeeded = fallbackFee.GetFee(nTxBytes);
@ -4154,3 +4157,15 @@ bool CMerkleTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& @@ -4154,3 +4157,15 @@ bool CMerkleTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState&
{
return ::AcceptToMemoryPool(mempool, state, tx, true, NULL, NULL, false, nAbsurdFee);
}
bool CalculateEstimateType(FeeEstimateMode mode, bool opt_in_rbf) {
switch (mode) {
case FeeEstimateMode::UNSET:
return !opt_in_rbf; // Allow for lower fees if RBF is an option
case FeeEstimateMode::CONSERVATIVE:
return true;
case FeeEstimateMode::ECONOMICAL:
return false;
}
return true;
}

6
src/wallet/wallet.h

@ -80,6 +80,7 @@ class CTxMemPool; @@ -80,6 +80,7 @@ class CTxMemPool;
class CBlockPolicyEstimator;
class CWalletTx;
struct FeeCalculation;
enum class FeeEstimateMode;
/** (client) version numbers for particular wallet features */
enum WalletFeature
@ -963,7 +964,7 @@ public: @@ -963,7 +964,7 @@ public:
* Estimate the minimum fee considering user set parameters
* and the required fee
*/
static CAmount GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation *feeCalc = nullptr, bool ignoreGlobalPayTxFee = false);
static CAmount GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation *feeCalc, bool ignoreGlobalPayTxFee, bool conservative_estimate);
/**
* Return the minimum required fee taking into account the
* floating relay fee and user set minimum transaction fee
@ -1211,4 +1212,7 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const ContainerType &coins @@ -1211,4 +1212,7 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const ContainerType &coins
}
return true;
}
bool CalculateEstimateType(FeeEstimateMode mode, bool opt_in_rbf);
#endif // BITCOIN_WALLET_WALLET_H

Loading…
Cancel
Save