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
This commit is contained in:
commit
104f5f21dc
13 changed files with 157 additions and 28 deletions
|
@ -36,6 +36,20 @@ std::string StringForFeeReason(FeeReason reason) {
|
||||||
return reason_string->second;
|
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
|
* 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
|
* included in a block. We will lump transactions into a bucket according to their
|
||||||
|
|
|
@ -90,6 +90,15 @@ enum class FeeReason {
|
||||||
|
|
||||||
std::string StringForFeeReason(FeeReason reason);
|
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 */
|
/* Used to return detailed information about a feerate bucket */
|
||||||
struct EstimatorBucket
|
struct EstimatorBucket
|
||||||
{
|
{
|
||||||
|
@ -197,7 +206,7 @@ public:
|
||||||
* the closest target where one can be given. 'conservative' estimates are
|
* the closest target where one can be given. 'conservative' estimates are
|
||||||
* valid over longer time horizons also.
|
* 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
|
/** Return a specific fee estimate calculation with a given success
|
||||||
* threshold and time horizon, and optionally return detailed data about
|
* threshold and time horizon, and optionally return detailed data about
|
||||||
|
|
|
@ -490,6 +490,8 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
|
||||||
else nBytesInputs += 148;
|
else nBytesInputs += 148;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool conservative_estimate = CalculateEstimateType(FeeEstimateMode::UNSET, coinControl->signalRbf);
|
||||||
|
|
||||||
// calculation
|
// calculation
|
||||||
if (nQuantity > 0)
|
if (nQuantity > 0)
|
||||||
{
|
{
|
||||||
|
@ -510,7 +512,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
|
||||||
nBytes -= 34;
|
nBytes -= 34;
|
||||||
|
|
||||||
// Fee
|
// 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)
|
if (nPayAmount > 0)
|
||||||
{
|
{
|
||||||
|
@ -585,7 +587,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
|
||||||
if (payTxFee.GetFeePerK() > 0)
|
if (payTxFee.GetFeePerK() > 0)
|
||||||
dFeeVary = (double)std::max(CWallet::GetRequiredFee(1000), payTxFee.GetFeePerK()) / 1000;
|
dFeeVary = (double)std::max(CWallet::GetRequiredFee(1000), payTxFee.GetFeePerK()) / 1000;
|
||||||
else {
|
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);
|
QString toolTip4 = tr("Can vary +/- %1 satoshi(s) per input.").arg(dFeeVary);
|
||||||
|
|
||||||
|
|
|
@ -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(updateFeeSectionControls()));
|
||||||
connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateGlobalFeeVariables()));
|
connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateGlobalFeeVariables()));
|
||||||
connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels()));
|
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));
|
ui->customFee->setSingleStep(CWallet::GetRequiredFee(1000));
|
||||||
updateFeeSectionControls();
|
updateFeeSectionControls();
|
||||||
updateMinFeeLabel();
|
updateMinFeeLabel();
|
||||||
|
@ -652,7 +654,8 @@ void SendCoinsDialog::updateSmartFeeLabel()
|
||||||
|
|
||||||
int nBlocksToConfirm = ui->sliderSmartFee->maximum() - ui->sliderSmartFee->value() + 2;
|
int nBlocksToConfirm = ui->sliderSmartFee->maximum() - ui->sliderSmartFee->value() + 2;
|
||||||
FeeCalculation feeCalc;
|
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
|
if (feeRate <= CFeeRate(0)) // not enough data => minfee
|
||||||
{
|
{
|
||||||
ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(),
|
ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(),
|
||||||
|
@ -827,6 +830,7 @@ void SendCoinsDialog::coinControlUpdateLabels()
|
||||||
} else {
|
} else {
|
||||||
CoinControlDialog::coinControl->nConfirmTarget = model->getDefaultConfirmTarget();
|
CoinControlDialog::coinControl->nConfirmTarget = model->getDefaultConfirmTarget();
|
||||||
}
|
}
|
||||||
|
CoinControlDialog::coinControl->signalRbf = ui->optInRBF->isChecked();
|
||||||
|
|
||||||
for(int i = 0; i < ui->entries->count(); ++i)
|
for(int i = 0; i < ui->entries->count(); ++i)
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#include "keystore.h"
|
#include "keystore.h"
|
||||||
#include "validation.h"
|
#include "validation.h"
|
||||||
#include "net.h" // for g_connman
|
#include "net.h" // for g_connman
|
||||||
|
#include "policy/fees.h"
|
||||||
#include "policy/rbf.h"
|
#include "policy/rbf.h"
|
||||||
#include "sync.h"
|
#include "sync.h"
|
||||||
#include "ui_interface.h"
|
#include "ui_interface.h"
|
||||||
|
@ -667,7 +668,7 @@ bool WalletModel::bumpFee(uint256 hash)
|
||||||
std::unique_ptr<CFeeBumper> feeBump;
|
std::unique_ptr<CFeeBumper> feeBump;
|
||||||
{
|
{
|
||||||
LOCK2(cs_main, wallet->cs_wallet);
|
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)
|
if (feeBump->getResult() != BumpFeeResult::OK)
|
||||||
{
|
{
|
||||||
|
|
|
@ -37,6 +37,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
||||||
{ "getnetworkhashps", 1, "height" },
|
{ "getnetworkhashps", 1, "height" },
|
||||||
{ "sendtoaddress", 1, "amount" },
|
{ "sendtoaddress", 1, "amount" },
|
||||||
{ "sendtoaddress", 4, "subtractfeefromamount" },
|
{ "sendtoaddress", 4, "subtractfeefromamount" },
|
||||||
|
{ "sendtoaddress", 5 , "replaceable" },
|
||||||
|
{ "sendtoaddress", 6 , "conf_target" },
|
||||||
{ "settxfee", 0, "amount" },
|
{ "settxfee", 0, "amount" },
|
||||||
{ "getreceivedbyaddress", 1, "minconf" },
|
{ "getreceivedbyaddress", 1, "minconf" },
|
||||||
{ "getreceivedbyaccount", 1, "minconf" },
|
{ "getreceivedbyaccount", 1, "minconf" },
|
||||||
|
@ -69,6 +71,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
||||||
{ "sendmany", 1, "amounts" },
|
{ "sendmany", 1, "amounts" },
|
||||||
{ "sendmany", 2, "minconf" },
|
{ "sendmany", 2, "minconf" },
|
||||||
{ "sendmany", 4, "subtractfeefrom" },
|
{ "sendmany", 4, "subtractfeefrom" },
|
||||||
|
{ "sendmany", 5 , "replaceable" },
|
||||||
|
{ "sendmany", 6 , "conf_target" },
|
||||||
{ "addmultisigaddress", 0, "nrequired" },
|
{ "addmultisigaddress", 0, "nrequired" },
|
||||||
{ "addmultisigaddress", 1, "keys" },
|
{ "addmultisigaddress", 1, "keys" },
|
||||||
{ "createmultisig", 0, "nrequired" },
|
{ "createmultisig", 0, "nrequired" },
|
||||||
|
|
|
@ -184,8 +184,8 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
|
||||||
mpool.TrimToSize(1);
|
mpool.TrimToSize(1);
|
||||||
BOOST_CHECK(mpool.GetMinFee(1).GetFeePerK() > feeV[5]);
|
BOOST_CHECK(mpool.GetMinFee(1).GetFeePerK() > feeV[5]);
|
||||||
for (int i = 1; i < 10; i++) {
|
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, true).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() >= mpool.GetMinFee(1).GetFeePerK());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#define BITCOIN_WALLET_COINCONTROL_H
|
#define BITCOIN_WALLET_COINCONTROL_H
|
||||||
|
|
||||||
#include "policy/feerate.h"
|
#include "policy/feerate.h"
|
||||||
|
#include "policy/fees.h"
|
||||||
#include "primitives/transaction.h"
|
#include "primitives/transaction.h"
|
||||||
#include "wallet/wallet.h"
|
#include "wallet/wallet.h"
|
||||||
|
|
||||||
|
@ -26,6 +27,8 @@ public:
|
||||||
int nConfirmTarget;
|
int nConfirmTarget;
|
||||||
//! Signal BIP-125 replace by fee.
|
//! Signal BIP-125 replace by fee.
|
||||||
bool signalRbf;
|
bool signalRbf;
|
||||||
|
//! Fee estimation mode to control arguments to estimateSmartFee
|
||||||
|
FeeEstimateMode m_fee_mode;
|
||||||
|
|
||||||
CCoinControl()
|
CCoinControl()
|
||||||
{
|
{
|
||||||
|
@ -42,6 +45,7 @@ public:
|
||||||
fOverrideFeeRate = false;
|
fOverrideFeeRate = false;
|
||||||
nConfirmTarget = 0;
|
nConfirmTarget = 0;
|
||||||
signalRbf = fWalletRbf;
|
signalRbf = fWalletRbf;
|
||||||
|
m_fee_mode = FeeEstimateMode::UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HasSelected() const
|
bool HasSelected() const
|
||||||
|
|
|
@ -66,7 +66,7 @@ bool CFeeBumper::preconditionChecks(const CWallet *pWallet, const CWalletTx& wtx
|
||||||
return true;
|
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)),
|
txid(std::move(txidIn)),
|
||||||
nOldFee(0),
|
nOldFee(0),
|
||||||
|
@ -165,7 +165,8 @@ CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, int newConf
|
||||||
nNewFee = totalFee;
|
nNewFee = totalFee;
|
||||||
nNewFeeRate = CFeeRate(totalFee, maxNewTxSize);
|
nNewFeeRate = CFeeRate(totalFee, maxNewTxSize);
|
||||||
} else {
|
} 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);
|
nNewFeeRate = CFeeRate(nNewFee, maxNewTxSize);
|
||||||
|
|
||||||
// New fee rate must be at least old rate + minimum incremental relay rate
|
// New fee rate must be at least old rate + minimum incremental relay rate
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
class CWallet;
|
class CWallet;
|
||||||
class CWalletTx;
|
class CWalletTx;
|
||||||
class uint256;
|
class uint256;
|
||||||
|
enum class FeeEstimateMode;
|
||||||
|
|
||||||
enum class BumpFeeResult
|
enum class BumpFeeResult
|
||||||
{
|
{
|
||||||
|
@ -24,7 +25,7 @@ enum class BumpFeeResult
|
||||||
class CFeeBumper
|
class CFeeBumper
|
||||||
{
|
{
|
||||||
public:
|
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; }
|
BumpFeeResult getResult() const { return currentResult; }
|
||||||
const std::vector<std::string>& getErrors() const { return vErrors; }
|
const std::vector<std::string>& getErrors() const { return vErrors; }
|
||||||
CAmount getOldFee() const { return nOldFee; }
|
CAmount getOldFee() const { return nOldFee; }
|
||||||
|
|
|
@ -356,7 +356,7 @@ UniValue getaddressesbyaccount(const JSONRPCRequest& request)
|
||||||
return ret;
|
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();
|
CAmount curBalance = pwallet->GetBalance();
|
||||||
|
|
||||||
|
@ -382,7 +382,7 @@ static void SendMoney(CWallet * const pwallet, const CTxDestination &address, CA
|
||||||
int nChangePosRet = -1;
|
int nChangePosRet = -1;
|
||||||
CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount};
|
CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount};
|
||||||
vecSend.push_back(recipient);
|
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)
|
if (!fSubtractFeeFromAmount && nValue + nFeeRequired > curBalance)
|
||||||
strError = strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired));
|
strError = strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired));
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, strError);
|
throw JSONRPCError(RPC_WALLET_ERROR, strError);
|
||||||
|
@ -401,9 +401,9 @@ UniValue sendtoaddress(const JSONRPCRequest& request)
|
||||||
return NullUniValue;
|
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(
|
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"
|
"\nSend an amount to a given address.\n"
|
||||||
+ HelpRequiringPassphrase(pwallet) +
|
+ HelpRequiringPassphrase(pwallet) +
|
||||||
"\nArguments:\n"
|
"\nArguments:\n"
|
||||||
|
@ -416,6 +416,12 @@ UniValue sendtoaddress(const JSONRPCRequest& request)
|
||||||
" transaction, just kept in your wallet.\n"
|
" transaction, just kept in your wallet.\n"
|
||||||
"5. subtractfeefromamount (boolean, optional, default=false) The fee will be deducted from the amount being sent.\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"
|
" 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"
|
"\nResult:\n"
|
||||||
"\"txid\" (string) The transaction id.\n"
|
"\"txid\" (string) The transaction id.\n"
|
||||||
"\nExamples:\n"
|
"\nExamples:\n"
|
||||||
|
@ -444,12 +450,29 @@ UniValue sendtoaddress(const JSONRPCRequest& request)
|
||||||
wtx.mapValue["to"] = request.params[3].get_str();
|
wtx.mapValue["to"] = request.params[3].get_str();
|
||||||
|
|
||||||
bool fSubtractFeeFromAmount = false;
|
bool fSubtractFeeFromAmount = false;
|
||||||
if (request.params.size() > 4)
|
if (request.params.size() > 4 && !request.params[4].isNull()) {
|
||||||
fSubtractFeeFromAmount = request.params[4].get_bool();
|
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);
|
EnsureWalletIsUnlocked(pwallet);
|
||||||
|
|
||||||
SendMoney(pwallet, address.Get(), nAmount, fSubtractFeeFromAmount, wtx);
|
SendMoney(pwallet, address.Get(), nAmount, fSubtractFeeFromAmount, wtx, &coin_control);
|
||||||
|
|
||||||
return wtx.GetHash().GetHex();
|
return wtx.GetHash().GetHex();
|
||||||
}
|
}
|
||||||
|
@ -888,9 +911,9 @@ UniValue sendmany(const JSONRPCRequest& request)
|
||||||
return NullUniValue;
|
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(
|
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."
|
"\nSend multiple times. Amounts are double-precision floating point numbers."
|
||||||
+ HelpRequiringPassphrase(pwallet) + "\n"
|
+ HelpRequiringPassphrase(pwallet) + "\n"
|
||||||
"\nArguments:\n"
|
"\nArguments:\n"
|
||||||
|
@ -910,7 +933,13 @@ UniValue sendmany(const JSONRPCRequest& request)
|
||||||
" \"address\" (string) Subtract fee from this address\n"
|
" \"address\" (string) Subtract fee from this address\n"
|
||||||
" ,...\n"
|
" ,...\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"
|
"\"txid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n"
|
||||||
" the number of addresses.\n"
|
" the number of addresses.\n"
|
||||||
"\nExamples:\n"
|
"\nExamples:\n"
|
||||||
|
@ -942,9 +971,24 @@ UniValue sendmany(const JSONRPCRequest& request)
|
||||||
wtx.mapValue["comment"] = request.params[3].get_str();
|
wtx.mapValue["comment"] = request.params[3].get_str();
|
||||||
|
|
||||||
UniValue subtractFeeFromAmount(UniValue::VARR);
|
UniValue subtractFeeFromAmount(UniValue::VARR);
|
||||||
if (request.params.size() > 4)
|
if (request.params.size() > 4 && !request.params[4].isNull())
|
||||||
subtractFeeFromAmount = request.params[4].get_array();
|
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::set<CBitcoinAddress> setAddress;
|
||||||
std::vector<CRecipient> vecSend;
|
std::vector<CRecipient> vecSend;
|
||||||
|
|
||||||
|
@ -989,7 +1033,7 @@ UniValue sendmany(const JSONRPCRequest& request)
|
||||||
CAmount nFeeRequired = 0;
|
CAmount nFeeRequired = 0;
|
||||||
int nChangePosRet = -1;
|
int nChangePosRet = -1;
|
||||||
std::string strFailReason;
|
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)
|
if (!fCreated)
|
||||||
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason);
|
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason);
|
||||||
CValidationState state;
|
CValidationState state;
|
||||||
|
@ -2658,6 +2702,11 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
|
||||||
" [vout_index,...]\n"
|
" [vout_index,...]\n"
|
||||||
" \"replaceable\" (boolean, optional) Marks this transaction as BIP125 replaceable.\n"
|
" \"replaceable\" (boolean, optional) Marks this transaction as BIP125 replaceable.\n"
|
||||||
" Allows this transaction to be replaced by a transaction with higher fees\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"
|
" }\n"
|
||||||
" for backward compatibility: passing in a true instead of an object will result in {\"includeWatching\":true}\n"
|
" for backward compatibility: passing in a true instead of an object will result in {\"includeWatching\":true}\n"
|
||||||
"\nResult:\n"
|
"\nResult:\n"
|
||||||
|
@ -2710,6 +2759,8 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
|
||||||
{"feeRate", UniValueType()}, // will be checked below
|
{"feeRate", UniValueType()}, // will be checked below
|
||||||
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
|
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
|
||||||
{"replaceable", UniValueType(UniValue::VBOOL)},
|
{"replaceable", UniValueType(UniValue::VBOOL)},
|
||||||
|
{"conf_target", UniValueType(UniValue::VNUM)},
|
||||||
|
{"estimate_mode", UniValueType(UniValue::VSTR)},
|
||||||
},
|
},
|
||||||
true, true);
|
true, true);
|
||||||
|
|
||||||
|
@ -2746,6 +2797,14 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
|
||||||
if (options.exists("replaceable")) {
|
if (options.exists("replaceable")) {
|
||||||
coinControl.signalRbf = options["replaceable"].get_bool();
|
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"
|
" 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"
|
" still be replaceable in practice, for example if it has unconfirmed ancestors which\n"
|
||||||
" are replaceable).\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"
|
" }\n"
|
||||||
"\nResult:\n"
|
"\nResult:\n"
|
||||||
"{\n"
|
"{\n"
|
||||||
|
@ -2845,6 +2908,7 @@ UniValue bumpfee(const JSONRPCRequest& request)
|
||||||
int newConfirmTarget = nTxConfirmTarget;
|
int newConfirmTarget = nTxConfirmTarget;
|
||||||
CAmount totalFee = 0;
|
CAmount totalFee = 0;
|
||||||
bool replaceable = true;
|
bool replaceable = true;
|
||||||
|
FeeEstimateMode fee_mode = FeeEstimateMode::UNSET;
|
||||||
if (request.params.size() > 1) {
|
if (request.params.size() > 1) {
|
||||||
UniValue options = request.params[1];
|
UniValue options = request.params[1];
|
||||||
RPCTypeCheckObj(options,
|
RPCTypeCheckObj(options,
|
||||||
|
@ -2852,6 +2916,7 @@ UniValue bumpfee(const JSONRPCRequest& request)
|
||||||
{"confTarget", UniValueType(UniValue::VNUM)},
|
{"confTarget", UniValueType(UniValue::VNUM)},
|
||||||
{"totalFee", UniValueType(UniValue::VNUM)},
|
{"totalFee", UniValueType(UniValue::VNUM)},
|
||||||
{"replaceable", UniValueType(UniValue::VBOOL)},
|
{"replaceable", UniValueType(UniValue::VBOOL)},
|
||||||
|
{"estimate_mode", UniValueType(UniValue::VSTR)},
|
||||||
},
|
},
|
||||||
true, true);
|
true, true);
|
||||||
|
|
||||||
|
@ -2876,12 +2941,17 @@ UniValue bumpfee(const JSONRPCRequest& request)
|
||||||
if (options.exists("replaceable")) {
|
if (options.exists("replaceable")) {
|
||||||
replaceable = options["replaceable"].get_bool();
|
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);
|
LOCK2(cs_main, pwallet->cs_wallet);
|
||||||
EnsureWalletIsUnlocked(pwallet);
|
EnsureWalletIsUnlocked(pwallet);
|
||||||
|
|
||||||
CFeeBumper feeBump(pwallet, hash, newConfirmTarget, ignoreGlobalPayTxFee, totalFee, replaceable);
|
CFeeBumper feeBump(pwallet, hash, newConfirmTarget, ignoreGlobalPayTxFee, totalFee, replaceable, fee_mode);
|
||||||
BumpFeeResult res = feeBump.getResult();
|
BumpFeeResult res = feeBump.getResult();
|
||||||
if (res != BumpFeeResult::OK)
|
if (res != BumpFeeResult::OK)
|
||||||
{
|
{
|
||||||
|
@ -3023,8 +3093,8 @@ static const CRPCCommand commands[] =
|
||||||
{ "wallet", "lockunspent", &lockunspent, true, {"unlock","transactions"} },
|
{ "wallet", "lockunspent", &lockunspent, true, {"unlock","transactions"} },
|
||||||
{ "wallet", "move", &movecmd, false, {"fromaccount","toaccount","amount","minconf","comment"} },
|
{ "wallet", "move", &movecmd, false, {"fromaccount","toaccount","amount","minconf","comment"} },
|
||||||
{ "wallet", "sendfrom", &sendfrom, false, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} },
|
{ "wallet", "sendfrom", &sendfrom, false, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} },
|
||||||
{ "wallet", "sendmany", &sendmany, false, {"fromaccount","amounts","minconf","comment","subtractfeefrom"} },
|
{ "wallet", "sendmany", &sendmany, false, {"fromaccount","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode"} },
|
||||||
{ "wallet", "sendtoaddress", &sendtoaddress, false, {"address","amount","comment","comment_to","subtractfeefromamount"} },
|
{ "wallet", "sendtoaddress", &sendtoaddress, false, {"address","amount","comment","comment_to","subtractfeefromamount","replaceable","conf_target","estimate_mode"} },
|
||||||
{ "wallet", "setaccount", &setaccount, true, {"address","account"} },
|
{ "wallet", "setaccount", &setaccount, true, {"address","account"} },
|
||||||
{ "wallet", "settxfee", &settxfee, true, {"amount"} },
|
{ "wallet", "settxfee", &settxfee, true, {"amount"} },
|
||||||
{ "wallet", "signmessage", &signmessage, true, {"address","message"} },
|
{ "wallet", "signmessage", &signmessage, true, {"address","message"} },
|
||||||
|
|
|
@ -2724,7 +2724,10 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
|
||||||
if (coinControl && coinControl->nConfirmTarget > 0)
|
if (coinControl && coinControl->nConfirmTarget > 0)
|
||||||
currentConfirmationTarget = coinControl->nConfirmTarget;
|
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)
|
if (coinControl && coinControl->fOverrideFeeRate)
|
||||||
nFeeNeeded = coinControl->nFeeRate.GetFee(nBytes);
|
nFeeNeeded = coinControl->nFeeRate.GetFee(nBytes);
|
||||||
|
|
||||||
|
@ -2905,13 +2908,13 @@ CAmount CWallet::GetRequiredFee(unsigned int nTxBytes)
|
||||||
return std::max(minTxFee.GetFee(nTxBytes), ::minRelayTxFee.GetFee(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
|
// payTxFee is the user-set global for desired feerate
|
||||||
CAmount nFeeNeeded = payTxFee.GetFee(nTxBytes);
|
CAmount nFeeNeeded = payTxFee.GetFee(nTxBytes);
|
||||||
// User didn't set: use -txconfirmtarget to estimate...
|
// User didn't set: use -txconfirmtarget to estimate...
|
||||||
if (nFeeNeeded == 0 || ignoreGlobalPayTxFee) {
|
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
|
// ... unless we don't have enough mempool data for estimatefee, then use fallbackFee
|
||||||
if (nFeeNeeded == 0) {
|
if (nFeeNeeded == 0) {
|
||||||
nFeeNeeded = fallbackFee.GetFee(nTxBytes);
|
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);
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -80,6 +80,7 @@ class CTxMemPool;
|
||||||
class CBlockPolicyEstimator;
|
class CBlockPolicyEstimator;
|
||||||
class CWalletTx;
|
class CWalletTx;
|
||||||
struct FeeCalculation;
|
struct FeeCalculation;
|
||||||
|
enum class FeeEstimateMode;
|
||||||
|
|
||||||
/** (client) version numbers for particular wallet features */
|
/** (client) version numbers for particular wallet features */
|
||||||
enum WalletFeature
|
enum WalletFeature
|
||||||
|
@ -963,7 +964,7 @@ public:
|
||||||
* Estimate the minimum fee considering user set parameters
|
* Estimate the minimum fee considering user set parameters
|
||||||
* and the required fee
|
* 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
|
* Return the minimum required fee taking into account the
|
||||||
* floating relay fee and user set minimum transaction fee
|
* floating relay fee and user set minimum transaction fee
|
||||||
|
@ -1211,4 +1212,7 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const ContainerType &coins
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CalculateEstimateType(FeeEstimateMode mode, bool opt_in_rbf);
|
||||||
|
|
||||||
#endif // BITCOIN_WALLET_WALLET_H
|
#endif // BITCOIN_WALLET_WALLET_H
|
||||||
|
|
Loading…
Reference in a new issue