diff --git a/src/policy/fees.cpp b/src/policy/fees.cpp index 771491770..c39a2d55c 100644 --- a/src/policy/fees.cpp +++ b/src/policy/fees.cpp @@ -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 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 diff --git a/src/policy/fees.h b/src/policy/fees.h index 2029ce374..fea279e47 100644 --- a/src/policy/fees.h +++ b/src/policy/fees.h @@ -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: * 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 diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index af9a888d9..c19420beb 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -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) 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) 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); diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 12d2d0f31..27634eb17 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -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() 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() } else { CoinControlDialog::coinControl->nConfirmTarget = model->getDefaultConfirmTarget(); } + CoinControlDialog::coinControl->signalRbf = ui->optInRBF->isChecked(); for(int i = 0; i < ui->entries->count(); ++i) { diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 7eff783fe..60b55da3e 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -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) std::unique_ptr 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) { diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 8fac8af3e..278c4ecc2 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -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[] = { "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" }, diff --git a/src/test/policyestimator_tests.cpp b/src/test/policyestimator_tests.cpp index 6bfd31564..8cdd39210 100644 --- a/src/test/policyestimator_tests.cpp +++ b/src/test/policyestimator_tests.cpp @@ -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()); } } diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index cb4719ae9..bdd01bec1 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -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: 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: fOverrideFeeRate = false; nConfirmTarget = 0; signalRbf = fWalletRbf; + m_fee_mode = FeeEstimateMode::UNSET; } bool HasSelected() const diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index 6a9e6cf9f..607ecf418 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -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 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 diff --git a/src/wallet/feebumper.h b/src/wallet/feebumper.h index fc3231670..11e2f5f95 100644 --- a/src/wallet/feebumper.h +++ b/src/wallet/feebumper.h @@ -10,6 +10,7 @@ class CWallet; class CWalletTx; class uint256; +enum class FeeEstimateMode; 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& getErrors() const { return vErrors; } CAmount getOldFee() const { return nOldFee; } diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 867ccd424..5f72e3b6f 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -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 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) 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) " 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) 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) 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,7 +933,13 @@ UniValue sendmany(const JSONRPCRequest& request) " \"address\" (string) Subtract fee from this address\n" " ,...\n" " ]\n" - "\nResult:\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" "\nExamples:\n" @@ -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 setAddress; std::vector vecSend; @@ -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) " [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) {"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) 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) " 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) 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) {"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) 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[] = { "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"} }, diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index f5d9b33ac..a1b4eb106 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2724,7 +2724,10 @@ bool CWallet::CreateTransaction(const std::vector& 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) 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& { 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; +} diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 4f558adc7..e3715cdf3 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -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: * 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 } return true; } + +bool CalculateEstimateType(FeeEstimateMode mode, bool opt_in_rbf); + #endif // BITCOIN_WALLET_WALLET_H