Merge #10284: Always log debug information for fee calculation in CreateTransaction
1bebfc8
Output Fee Estimation Calculations in CreateTransaction (Alex Morcos)
Tree-SHA512: e25a27f7acbbc3a666d5d85da2554c5aaec4c923ee2fdbcfc532c29c6fbdec3c9e0d6ae6044543ecc339e7bd81df09c8d228e0b53a2c5c2dae0f1098c9453272
This commit is contained in:
commit
c2ab38bdd5
7 changed files with 134 additions and 44 deletions
|
@ -16,6 +16,26 @@
|
||||||
|
|
||||||
static constexpr double INF_FEERATE = 1e99;
|
static constexpr double INF_FEERATE = 1e99;
|
||||||
|
|
||||||
|
std::string StringForFeeReason(FeeReason reason) {
|
||||||
|
static const std::map<FeeReason, std::string> fee_reason_strings = {
|
||||||
|
{FeeReason::NONE, "None"},
|
||||||
|
{FeeReason::HALF_ESTIMATE, "Half Target 60% Threshold"},
|
||||||
|
{FeeReason::FULL_ESTIMATE, "Target 85% Threshold"},
|
||||||
|
{FeeReason::DOUBLE_ESTIMATE, "Double Target 95% Threshold"},
|
||||||
|
{FeeReason::CONSERVATIVE, "Conservative Double Target longer horizon"},
|
||||||
|
{FeeReason::MEMPOOL_MIN, "Mempool Min Fee"},
|
||||||
|
{FeeReason::PAYTXFEE, "PayTxFee set"},
|
||||||
|
{FeeReason::FALLBACK, "Fallback fee"},
|
||||||
|
{FeeReason::REQUIRED, "Minimum Required Fee"},
|
||||||
|
{FeeReason::MAXTXFEE, "MaxTxFee limit"}
|
||||||
|
};
|
||||||
|
auto reason_string = fee_reason_strings.find(reason);
|
||||||
|
|
||||||
|
if (reason_string == fee_reason_strings.end()) return "Unknown";
|
||||||
|
|
||||||
|
return reason_string->second;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
|
@ -698,31 +718,36 @@ unsigned int CBlockPolicyEstimator::MaxUsableEstimate() const
|
||||||
* time horizon which tracks confirmations up to the desired target. If
|
* time horizon which tracks confirmations up to the desired target. If
|
||||||
* checkShorterHorizon is requested, also allow short time horizon estimates
|
* checkShorterHorizon is requested, also allow short time horizon estimates
|
||||||
* for a lower target to reduce the given answer */
|
* for a lower target to reduce the given answer */
|
||||||
double CBlockPolicyEstimator::estimateCombinedFee(unsigned int confTarget, double successThreshold, bool checkShorterHorizon) const
|
double CBlockPolicyEstimator::estimateCombinedFee(unsigned int confTarget, double successThreshold, bool checkShorterHorizon, EstimationResult *result) const
|
||||||
{
|
{
|
||||||
double estimate = -1;
|
double estimate = -1;
|
||||||
if (confTarget >= 1 && confTarget <= longStats->GetMaxConfirms()) {
|
if (confTarget >= 1 && confTarget <= longStats->GetMaxConfirms()) {
|
||||||
// Find estimate from shortest time horizon possible
|
// Find estimate from shortest time horizon possible
|
||||||
if (confTarget <= shortStats->GetMaxConfirms()) { // short horizon
|
if (confTarget <= shortStats->GetMaxConfirms()) { // short horizon
|
||||||
estimate = shortStats->EstimateMedianVal(confTarget, SUFFICIENT_TXS_SHORT, successThreshold, true, nBestSeenHeight);
|
estimate = shortStats->EstimateMedianVal(confTarget, SUFFICIENT_TXS_SHORT, successThreshold, true, nBestSeenHeight, result);
|
||||||
}
|
}
|
||||||
else if (confTarget <= feeStats->GetMaxConfirms()) { // medium horizon
|
else if (confTarget <= feeStats->GetMaxConfirms()) { // medium horizon
|
||||||
estimate = feeStats->EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, successThreshold, true, nBestSeenHeight);
|
estimate = feeStats->EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, successThreshold, true, nBestSeenHeight, result);
|
||||||
}
|
}
|
||||||
else { // long horizon
|
else { // long horizon
|
||||||
estimate = longStats->EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, successThreshold, true, nBestSeenHeight);
|
estimate = longStats->EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, successThreshold, true, nBestSeenHeight, result);
|
||||||
}
|
}
|
||||||
if (checkShorterHorizon) {
|
if (checkShorterHorizon) {
|
||||||
|
EstimationResult tempResult;
|
||||||
// If a lower confTarget from a more recent horizon returns a lower answer use it.
|
// If a lower confTarget from a more recent horizon returns a lower answer use it.
|
||||||
if (confTarget > feeStats->GetMaxConfirms()) {
|
if (confTarget > feeStats->GetMaxConfirms()) {
|
||||||
double medMax = feeStats->EstimateMedianVal(feeStats->GetMaxConfirms(), SUFFICIENT_FEETXS, successThreshold, true, nBestSeenHeight);
|
double medMax = feeStats->EstimateMedianVal(feeStats->GetMaxConfirms(), SUFFICIENT_FEETXS, successThreshold, true, nBestSeenHeight, &tempResult);
|
||||||
if (medMax > 0 && (estimate == -1 || medMax < estimate))
|
if (medMax > 0 && (estimate == -1 || medMax < estimate)) {
|
||||||
estimate = medMax;
|
estimate = medMax;
|
||||||
|
if (result) *result = tempResult;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (confTarget > shortStats->GetMaxConfirms()) {
|
if (confTarget > shortStats->GetMaxConfirms()) {
|
||||||
double shortMax = shortStats->EstimateMedianVal(shortStats->GetMaxConfirms(), SUFFICIENT_TXS_SHORT, successThreshold, true, nBestSeenHeight);
|
double shortMax = shortStats->EstimateMedianVal(shortStats->GetMaxConfirms(), SUFFICIENT_TXS_SHORT, successThreshold, true, nBestSeenHeight, &tempResult);
|
||||||
if (shortMax > 0 && (estimate == -1 || shortMax < estimate))
|
if (shortMax > 0 && (estimate == -1 || shortMax < estimate)) {
|
||||||
estimate = shortMax;
|
estimate = shortMax;
|
||||||
|
if (result) *result = tempResult;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -732,16 +757,18 @@ double CBlockPolicyEstimator::estimateCombinedFee(unsigned int confTarget, doubl
|
||||||
/** Ensure that for a conservative estimate, the DOUBLE_SUCCESS_PCT is also met
|
/** Ensure that for a conservative estimate, the DOUBLE_SUCCESS_PCT is also met
|
||||||
* at 2 * target for any longer time horizons.
|
* at 2 * target for any longer time horizons.
|
||||||
*/
|
*/
|
||||||
double CBlockPolicyEstimator::estimateConservativeFee(unsigned int doubleTarget) const
|
double CBlockPolicyEstimator::estimateConservativeFee(unsigned int doubleTarget, EstimationResult *result) const
|
||||||
{
|
{
|
||||||
double estimate = -1;
|
double estimate = -1;
|
||||||
|
EstimationResult tempResult;
|
||||||
if (doubleTarget <= shortStats->GetMaxConfirms()) {
|
if (doubleTarget <= shortStats->GetMaxConfirms()) {
|
||||||
estimate = feeStats->EstimateMedianVal(doubleTarget, SUFFICIENT_FEETXS, DOUBLE_SUCCESS_PCT, true, nBestSeenHeight);
|
estimate = feeStats->EstimateMedianVal(doubleTarget, SUFFICIENT_FEETXS, DOUBLE_SUCCESS_PCT, true, nBestSeenHeight, result);
|
||||||
}
|
}
|
||||||
if (doubleTarget <= feeStats->GetMaxConfirms()) {
|
if (doubleTarget <= feeStats->GetMaxConfirms()) {
|
||||||
double longEstimate = longStats->EstimateMedianVal(doubleTarget, SUFFICIENT_FEETXS, DOUBLE_SUCCESS_PCT, true, nBestSeenHeight);
|
double longEstimate = longStats->EstimateMedianVal(doubleTarget, SUFFICIENT_FEETXS, DOUBLE_SUCCESS_PCT, true, nBestSeenHeight, &tempResult);
|
||||||
if (longEstimate > estimate) {
|
if (longEstimate > estimate) {
|
||||||
estimate = longEstimate;
|
estimate = longEstimate;
|
||||||
|
if (result) *result = tempResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return estimate;
|
return estimate;
|
||||||
|
@ -754,12 +781,15 @@ double CBlockPolicyEstimator::estimateConservativeFee(unsigned int doubleTarget)
|
||||||
* estimates, however, required the 95% threshold at 2 * target be met for any
|
* estimates, however, required the 95% threshold at 2 * target be met for any
|
||||||
* longer time horizons also.
|
* longer time horizons also.
|
||||||
*/
|
*/
|
||||||
CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, int *answerFoundAtTarget, const CTxMemPool& pool, bool conservative) const
|
CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, FeeCalculation *feeCalc, const CTxMemPool& pool, bool conservative) const
|
||||||
{
|
{
|
||||||
if (answerFoundAtTarget)
|
if (feeCalc) {
|
||||||
*answerFoundAtTarget = confTarget;
|
feeCalc->desiredTarget = confTarget;
|
||||||
|
feeCalc->returnedTarget = confTarget;
|
||||||
|
}
|
||||||
|
|
||||||
double median = -1;
|
double median = -1;
|
||||||
|
EstimationResult tempResult;
|
||||||
{
|
{
|
||||||
LOCK(cs_feeEstimator);
|
LOCK(cs_feeEstimator);
|
||||||
|
|
||||||
|
@ -780,7 +810,6 @@ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, int *answerFoun
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(confTarget > 0); //estimateCombinedFee and estimateConservativeFee take unsigned ints
|
assert(confTarget > 0); //estimateCombinedFee and estimateConservativeFee take unsigned ints
|
||||||
|
|
||||||
/** true is passed to estimateCombined fee for target/2 and target so
|
/** true is passed to estimateCombined fee for target/2 and target so
|
||||||
* that we check the max confirms for shorter time horizons as well.
|
* that we check the max confirms for shorter time horizons as well.
|
||||||
* This is necessary to preserve monotonically increasing estimates.
|
* This is necessary to preserve monotonically increasing estimates.
|
||||||
|
@ -791,32 +820,49 @@ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, int *answerFoun
|
||||||
* the purpose of conservative estimates is not to let short term
|
* the purpose of conservative estimates is not to let short term
|
||||||
* fluctuations lower our estimates by too much.
|
* fluctuations lower our estimates by too much.
|
||||||
*/
|
*/
|
||||||
double halfEst = estimateCombinedFee(confTarget/2, HALF_SUCCESS_PCT, true);
|
double halfEst = estimateCombinedFee(confTarget/2, HALF_SUCCESS_PCT, true, &tempResult);
|
||||||
double actualEst = estimateCombinedFee(confTarget, SUCCESS_PCT, true);
|
if (feeCalc) {
|
||||||
double doubleEst = estimateCombinedFee(2 * confTarget, DOUBLE_SUCCESS_PCT, !conservative);
|
feeCalc->est = tempResult;
|
||||||
|
feeCalc->reason = FeeReason::HALF_ESTIMATE;
|
||||||
|
}
|
||||||
median = halfEst;
|
median = halfEst;
|
||||||
|
double actualEst = estimateCombinedFee(confTarget, SUCCESS_PCT, true, &tempResult);
|
||||||
if (actualEst > median) {
|
if (actualEst > median) {
|
||||||
median = actualEst;
|
median = actualEst;
|
||||||
|
if (feeCalc) {
|
||||||
|
feeCalc->est = tempResult;
|
||||||
|
feeCalc->reason = FeeReason::FULL_ESTIMATE;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
double doubleEst = estimateCombinedFee(2 * confTarget, DOUBLE_SUCCESS_PCT, !conservative, &tempResult);
|
||||||
if (doubleEst > median) {
|
if (doubleEst > median) {
|
||||||
median = doubleEst;
|
median = doubleEst;
|
||||||
|
if (feeCalc) {
|
||||||
|
feeCalc->est = tempResult;
|
||||||
|
feeCalc->reason = FeeReason::DOUBLE_ESTIMATE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (conservative || median == -1) {
|
if (conservative || median == -1) {
|
||||||
double consEst = estimateConservativeFee(2 * confTarget);
|
double consEst = estimateConservativeFee(2 * confTarget, &tempResult);
|
||||||
if (consEst > median) {
|
if (consEst > median) {
|
||||||
median = consEst;
|
median = consEst;
|
||||||
|
if (feeCalc) {
|
||||||
|
feeCalc->est = tempResult;
|
||||||
|
feeCalc->reason = FeeReason::CONSERVATIVE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // Must unlock cs_feeEstimator before taking mempool locks
|
} // Must unlock cs_feeEstimator before taking mempool locks
|
||||||
|
|
||||||
if (answerFoundAtTarget)
|
if (feeCalc) feeCalc->returnedTarget = confTarget;
|
||||||
*answerFoundAtTarget = confTarget;
|
|
||||||
|
|
||||||
// If mempool is limiting txs , return at least the min feerate from the mempool
|
// If mempool is limiting txs , return at least the min feerate from the mempool
|
||||||
CAmount minPoolFee = pool.GetMinFee(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFeePerK();
|
CAmount minPoolFee = pool.GetMinFee(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFeePerK();
|
||||||
if (minPoolFee > 0 && minPoolFee > median)
|
if (minPoolFee > 0 && minPoolFee > median) {
|
||||||
|
if (feeCalc) feeCalc->reason = FeeReason::MEMPOOL_MIN;
|
||||||
return CFeeRate(minPoolFee);
|
return CFeeRate(minPoolFee);
|
||||||
|
}
|
||||||
|
|
||||||
if (median < 0)
|
if (median < 0)
|
||||||
return CFeeRate(0);
|
return CFeeRate(0);
|
||||||
|
|
|
@ -74,6 +74,22 @@ enum FeeEstimateHorizon {
|
||||||
LONG_HALFLIFE = 2
|
LONG_HALFLIFE = 2
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Enumeration of reason for returned fee estimate */
|
||||||
|
enum class FeeReason {
|
||||||
|
NONE,
|
||||||
|
HALF_ESTIMATE,
|
||||||
|
FULL_ESTIMATE,
|
||||||
|
DOUBLE_ESTIMATE,
|
||||||
|
CONSERVATIVE,
|
||||||
|
MEMPOOL_MIN,
|
||||||
|
PAYTXFEE,
|
||||||
|
FALLBACK,
|
||||||
|
REQUIRED,
|
||||||
|
MAXTXFEE,
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string StringForFeeReason(FeeReason reason);
|
||||||
|
|
||||||
/* Used to return detailed information about a feerate bucket */
|
/* Used to return detailed information about a feerate bucket */
|
||||||
struct EstimatorBucket
|
struct EstimatorBucket
|
||||||
{
|
{
|
||||||
|
@ -90,8 +106,16 @@ struct EstimationResult
|
||||||
{
|
{
|
||||||
EstimatorBucket pass;
|
EstimatorBucket pass;
|
||||||
EstimatorBucket fail;
|
EstimatorBucket fail;
|
||||||
double decay;
|
double decay = 0;
|
||||||
unsigned int scale;
|
unsigned int scale = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FeeCalculation
|
||||||
|
{
|
||||||
|
EstimationResult est;
|
||||||
|
FeeReason reason = FeeReason::NONE;
|
||||||
|
int desiredTarget = 0;
|
||||||
|
int returnedTarget = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -173,7 +197,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, int *answerFoundAtTarget, const CTxMemPool& pool, bool conservative = true) const;
|
CFeeRate estimateSmartFee(int confTarget, FeeCalculation *feeCalc, const CTxMemPool& pool, bool conservative = true) 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
|
||||||
|
@ -223,9 +247,9 @@ private:
|
||||||
bool processBlockTx(unsigned int nBlockHeight, const CTxMemPoolEntry* entry);
|
bool processBlockTx(unsigned int nBlockHeight, const CTxMemPoolEntry* entry);
|
||||||
|
|
||||||
/** Helper for estimateSmartFee */
|
/** Helper for estimateSmartFee */
|
||||||
double estimateCombinedFee(unsigned int confTarget, double successThreshold, bool checkShorterHorizon) const;
|
double estimateCombinedFee(unsigned int confTarget, double successThreshold, bool checkShorterHorizon, EstimationResult *result) const;
|
||||||
/** Helper for estimateSmartFee */
|
/** Helper for estimateSmartFee */
|
||||||
double estimateConservativeFee(unsigned int doubleTarget) const;
|
double estimateConservativeFee(unsigned int doubleTarget, EstimationResult *result) const;
|
||||||
/** Number of blocks of data recorded while fee estimates have been running */
|
/** Number of blocks of data recorded while fee estimates have been running */
|
||||||
unsigned int BlockSpan() const;
|
unsigned int BlockSpan() const;
|
||||||
/** Number of blocks of recorded fee estimate data represented in saved data file */
|
/** Number of blocks of recorded fee estimate data represented in saved data file */
|
||||||
|
|
|
@ -651,8 +651,8 @@ void SendCoinsDialog::updateSmartFeeLabel()
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int nBlocksToConfirm = ui->sliderSmartFee->maximum() - ui->sliderSmartFee->value() + 2;
|
int nBlocksToConfirm = ui->sliderSmartFee->maximum() - ui->sliderSmartFee->value() + 2;
|
||||||
int estimateFoundAtBlocks = nBlocksToConfirm;
|
FeeCalculation feeCalc;
|
||||||
CFeeRate feeRate = ::feeEstimator.estimateSmartFee(nBlocksToConfirm, &estimateFoundAtBlocks, ::mempool);
|
CFeeRate feeRate = ::feeEstimator.estimateSmartFee(nBlocksToConfirm, &feeCalc, ::mempool);
|
||||||
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(),
|
||||||
|
@ -670,7 +670,7 @@ void SendCoinsDialog::updateSmartFeeLabel()
|
||||||
ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(),
|
ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(),
|
||||||
std::max(feeRate.GetFeePerK(), CWallet::GetRequiredFee(1000))) + "/kB");
|
std::max(feeRate.GetFeePerK(), CWallet::GetRequiredFee(1000))) + "/kB");
|
||||||
ui->labelSmartFee2->hide();
|
ui->labelSmartFee2->hide();
|
||||||
ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", estimateFoundAtBlocks));
|
ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", feeCalc.returnedTarget));
|
||||||
ui->fallbackFeeWarningLabel->setVisible(false);
|
ui->fallbackFeeWarningLabel->setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -867,10 +867,10 @@ UniValue estimatesmartfee(const JSONRPCRequest& request)
|
||||||
}
|
}
|
||||||
|
|
||||||
UniValue result(UniValue::VOBJ);
|
UniValue result(UniValue::VOBJ);
|
||||||
int answerFound;
|
FeeCalculation feeCalc;
|
||||||
CFeeRate feeRate = ::feeEstimator.estimateSmartFee(nBlocks, &answerFound, ::mempool, conservative);
|
CFeeRate feeRate = ::feeEstimator.estimateSmartFee(nBlocks, &feeCalc, ::mempool, conservative);
|
||||||
result.push_back(Pair("feerate", feeRate == CFeeRate(0) ? -1.0 : ValueFromAmount(feeRate.GetFeePerK())));
|
result.push_back(Pair("feerate", feeRate == CFeeRate(0) ? -1.0 : ValueFromAmount(feeRate.GetFeePerK())));
|
||||||
result.push_back(Pair("blocks", answerFound));
|
result.push_back(Pair("blocks", feeCalc.returnedTarget));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -165,7 +165,7 @@ 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, ignoreGlobalPayTxFee);
|
nNewFee = CWallet::GetMinimumFee(maxNewTxSize, newConfirmTarget, mempool, ::feeEstimator, nullptr, ignoreGlobalPayTxFee);
|
||||||
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
|
||||||
|
|
|
@ -2534,7 +2534,8 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
|
||||||
|
|
||||||
assert(txNew.nLockTime <= (unsigned int)chainActive.Height());
|
assert(txNew.nLockTime <= (unsigned int)chainActive.Height());
|
||||||
assert(txNew.nLockTime < LOCKTIME_THRESHOLD);
|
assert(txNew.nLockTime < LOCKTIME_THRESHOLD);
|
||||||
|
FeeCalculation feeCalc;
|
||||||
|
unsigned int nBytes;
|
||||||
{
|
{
|
||||||
std::set<CInputCoin> setCoins;
|
std::set<CInputCoin> setCoins;
|
||||||
LOCK2(cs_main, cs_wallet);
|
LOCK2(cs_main, cs_wallet);
|
||||||
|
@ -2706,7 +2707,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int nBytes = GetVirtualTransactionSize(txNew);
|
nBytes = GetVirtualTransactionSize(txNew);
|
||||||
|
|
||||||
CTransaction txNewConst(txNew);
|
CTransaction txNewConst(txNew);
|
||||||
|
|
||||||
|
@ -2721,7 +2722,7 @@ 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);
|
CAmount nFeeNeeded = GetMinimumFee(nBytes, currentConfirmationTarget, ::mempool, ::feeEstimator, &feeCalc);
|
||||||
if (coinControl && coinControl->fOverrideFeeRate)
|
if (coinControl && coinControl->fOverrideFeeRate)
|
||||||
nFeeNeeded = coinControl->nFeeRate.GetFee(nBytes);
|
nFeeNeeded = coinControl->nFeeRate.GetFee(nBytes);
|
||||||
|
|
||||||
|
@ -2818,6 +2819,15 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LogPrintf("Fee Calculation: Fee:%d Bytes:%u Tgt:%d (requested %d) Reason:\"%s\" Decay %.5f: Estimation: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n",
|
||||||
|
nFeeRet, nBytes, feeCalc.returnedTarget, feeCalc.desiredTarget, StringForFeeReason(feeCalc.reason), feeCalc.est.decay,
|
||||||
|
feeCalc.est.pass.start, feeCalc.est.pass.end,
|
||||||
|
100 * feeCalc.est.pass.withinTarget / (feeCalc.est.pass.totalConfirmed + feeCalc.est.pass.inMempool + feeCalc.est.pass.leftMempool),
|
||||||
|
feeCalc.est.pass.withinTarget, feeCalc.est.pass.totalConfirmed, feeCalc.est.pass.inMempool, feeCalc.est.pass.leftMempool,
|
||||||
|
feeCalc.est.fail.start, feeCalc.est.fail.end,
|
||||||
|
100 * feeCalc.est.fail.withinTarget / (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool),
|
||||||
|
feeCalc.est.fail.withinTarget, feeCalc.est.fail.totalConfirmed, feeCalc.est.fail.inMempool, feeCalc.est.fail.leftMempool);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2893,23 +2903,32 @@ 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, bool ignoreGlobalPayTxFee)
|
CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation *feeCalc, bool ignoreGlobalPayTxFee)
|
||||||
{
|
{
|
||||||
// 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) {
|
||||||
int estimateFoundTarget = nConfirmTarget;
|
nFeeNeeded = estimator.estimateSmartFee(nConfirmTarget, feeCalc, pool, true).GetFee(nTxBytes);
|
||||||
nFeeNeeded = estimator.estimateSmartFee(nConfirmTarget, &estimateFoundTarget, pool).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);
|
||||||
|
if (feeCalc) feeCalc->reason = FeeReason::FALLBACK;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (feeCalc) feeCalc->reason = FeeReason::PAYTXFEE;
|
||||||
}
|
}
|
||||||
// prevent user from paying a fee below minRelayTxFee or minTxFee
|
// prevent user from paying a fee below minRelayTxFee or minTxFee
|
||||||
nFeeNeeded = std::max(nFeeNeeded, GetRequiredFee(nTxBytes));
|
CAmount requiredFee = GetRequiredFee(nTxBytes);
|
||||||
|
if (requiredFee > nFeeNeeded) {
|
||||||
|
nFeeNeeded = requiredFee;
|
||||||
|
if (feeCalc) feeCalc->reason = FeeReason::REQUIRED;
|
||||||
|
}
|
||||||
// But always obey the maximum
|
// But always obey the maximum
|
||||||
if (nFeeNeeded > maxTxFee)
|
if (nFeeNeeded > maxTxFee) {
|
||||||
nFeeNeeded = maxTxFee;
|
nFeeNeeded = maxTxFee;
|
||||||
|
if (feeCalc) feeCalc->reason = FeeReason::MAXTXFEE;
|
||||||
|
}
|
||||||
return nFeeNeeded;
|
return nFeeNeeded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,7 @@ class CScheduler;
|
||||||
class CTxMemPool;
|
class CTxMemPool;
|
||||||
class CBlockPolicyEstimator;
|
class CBlockPolicyEstimator;
|
||||||
class CWalletTx;
|
class CWalletTx;
|
||||||
|
class FeeCalculation;
|
||||||
|
|
||||||
/** (client) version numbers for particular wallet features */
|
/** (client) version numbers for particular wallet features */
|
||||||
enum WalletFeature
|
enum WalletFeature
|
||||||
|
@ -959,7 +960,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, bool ignoreGlobalPayTxFee = false);
|
static CAmount GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation *feeCalc = nullptr, bool ignoreGlobalPayTxFee = false);
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
|
|
Loading…
Reference in a new issue