Skip to content

Commit e92377f

Browse files
committed
Merge pull request #6134
e304432 Pass reference to estimateSmartFee and cleanup whitespace (Suhas Daftuar) 56106a3 Expose RPC calls for estimatesmart functions (Alex Morcos) e93a236 add estimateSmartFee to the unit test (Alex Morcos) 6303051 EstimateSmart functions consider mempool min fee (Alex Morcos) f22ac4a Increase success threshold for fee estimation to 95% (Alex Morcos) 4fe2823 Change wallet and GUI code to use new smart fee estimation calls. (Alex Morcos) 22eca7d Add smart fee estimation functions (Alex Morcos)
2 parents 05d5918 + e304432 commit e92377f

16 files changed

+261
-66
lines changed

qa/rpc-tests/smartfees.py

+30-22
Original file line numberDiff line numberDiff line change
@@ -120,15 +120,26 @@ def check_estimates(node, fees_seen, max_invalid, print_estimates = True):
120120
last_e = e
121121
valid_estimate = False
122122
invalid_estimates = 0
123-
for e in all_estimates:
123+
for i,e in enumerate(all_estimates): # estimate is for i+1
124124
if e >= 0:
125125
valid_estimate = True
126+
# estimatesmartfee should return the same result
127+
assert_equal(node.estimatesmartfee(i+1)["feerate"], e)
128+
126129
else:
127130
invalid_estimates += 1
128-
# Once we're at a high enough confirmation count that we can give an estimate
129-
# We should have estimates for all higher confirmation counts
130-
if valid_estimate and e < 0:
131-
raise AssertionError("Invalid estimate appears at higher confirm count than valid estimate")
131+
132+
# estimatesmartfee should still be valid
133+
approx_estimate = node.estimatesmartfee(i+1)["feerate"]
134+
answer_found = node.estimatesmartfee(i+1)["blocks"]
135+
assert(approx_estimate > 0)
136+
assert(answer_found > i+1)
137+
138+
# Once we're at a high enough confirmation count that we can give an estimate
139+
# We should have estimates for all higher confirmation counts
140+
if valid_estimate:
141+
raise AssertionError("Invalid estimate appears at higher confirm count than valid estimate")
142+
132143
# Check on the expected number of different confirmation counts
133144
# that we might not have valid estimates for
134145
if invalid_estimates > max_invalid:
@@ -184,13 +195,13 @@ def setup_network(self):
184195
# NOTE: the CreateNewBlock code starts counting block size at 1,000 bytes,
185196
# (17k is room enough for 110 or so transactions)
186197
self.nodes.append(start_node(1, self.options.tmpdir,
187-
["-blockprioritysize=1500", "-blockmaxsize=18000",
198+
["-blockprioritysize=1500", "-blockmaxsize=17000",
188199
"-maxorphantx=1000", "-relaypriority=0", "-debug=estimatefee"]))
189200
connect_nodes(self.nodes[1], 0)
190201

191202
# Node2 is a stingy miner, that
192-
# produces too small blocks (room for only 70 or so transactions)
193-
node2args = ["-blockprioritysize=0", "-blockmaxsize=12000", "-maxorphantx=1000", "-relaypriority=0"]
203+
# produces too small blocks (room for only 55 or so transactions)
204+
node2args = ["-blockprioritysize=0", "-blockmaxsize=8000", "-maxorphantx=1000", "-relaypriority=0"]
194205

195206
self.nodes.append(start_node(2, self.options.tmpdir, node2args))
196207
connect_nodes(self.nodes[0], 2)
@@ -229,22 +240,19 @@ def run_test(self):
229240
self.fees_per_kb = []
230241
self.memutxo = []
231242
self.confutxo = self.txouts # Start with the set of confirmed txouts after splitting
232-
print("Checking estimates for 1/2/3/6/15/25 blocks")
233-
print("Creating transactions and mining them with a huge block size")
234-
# Create transactions and mine 20 big blocks with node 0 such that the mempool is always emptied
235-
self.transact_and_mine(30, self.nodes[0])
236-
check_estimates(self.nodes[1], self.fees_per_kb, 1)
243+
print("Will output estimates for 1/2/3/6/15/25 blocks")
237244

238-
print("Creating transactions and mining them with a block size that can't keep up")
239-
# Create transactions and mine 30 small blocks with node 2, but create txs faster than we can mine
240-
self.transact_and_mine(20, self.nodes[2])
241-
check_estimates(self.nodes[1], self.fees_per_kb, 3)
245+
for i in xrange(2):
246+
print("Creating transactions and mining them with a block size that can't keep up")
247+
# Create transactions and mine 10 small blocks with node 2, but create txs faster than we can mine
248+
self.transact_and_mine(10, self.nodes[2])
249+
check_estimates(self.nodes[1], self.fees_per_kb, 14)
242250

243-
print("Creating transactions and mining them at a block size that is just big enough")
244-
# Generate transactions while mining 40 more blocks, this time with node1
245-
# which mines blocks with capacity just above the rate that transactions are being created
246-
self.transact_and_mine(40, self.nodes[1])
247-
check_estimates(self.nodes[1], self.fees_per_kb, 2)
251+
print("Creating transactions and mining them at a block size that is just big enough")
252+
# Generate transactions while mining 10 more blocks, this time with node1
253+
# which mines blocks with capacity just above the rate that transactions are being created
254+
self.transact_and_mine(10, self.nodes[1])
255+
check_estimates(self.nodes[1], self.fees_per_kb, 2)
248256

249257
# Finish by mining a normal-sized block:
250258
while len(self.nodes[1].getrawmempool()) > 0:

src/main.h

-2
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@ static const unsigned int DEFAULT_ANCESTOR_SIZE_LIMIT = 101;
5656
static const unsigned int DEFAULT_DESCENDANT_LIMIT = 25;
5757
/** Default for -limitdescendantsize, maximum kilobytes of in-mempool descendants */
5858
static const unsigned int DEFAULT_DESCENDANT_SIZE_LIMIT = 101;
59-
/** Default for -maxmempool, maximum megabytes of mempool memory usage */
60-
static const unsigned int DEFAULT_MAX_MEMPOOL_SIZE = 300;
6159
/** Default for -mempoolexpiry, expiration time for mempool transactions in hours */
6260
static const unsigned int DEFAULT_MEMPOOL_EXPIRY = 72;
6361
/** The maximum size of a blk?????.dat file (since 0.8) */

src/policy/fees.cpp

+52
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
55

66
#include "policy/fees.h"
7+
#include "policy/policy.h"
78

89
#include "amount.h"
910
#include "primitives/transaction.h"
@@ -504,6 +505,33 @@ CFeeRate CBlockPolicyEstimator::estimateFee(int confTarget)
504505
return CFeeRate(median);
505506
}
506507

508+
CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, int *answerFoundAtTarget, const CTxMemPool& pool)
509+
{
510+
if (answerFoundAtTarget)
511+
*answerFoundAtTarget = confTarget;
512+
// Return failure if trying to analyze a target we're not tracking
513+
if (confTarget <= 0 || (unsigned int)confTarget > feeStats.GetMaxConfirms())
514+
return CFeeRate(0);
515+
516+
double median = -1;
517+
while (median < 0 && (unsigned int)confTarget <= feeStats.GetMaxConfirms()) {
518+
median = feeStats.EstimateMedianVal(confTarget++, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT, true, nBestSeenHeight);
519+
}
520+
521+
if (answerFoundAtTarget)
522+
*answerFoundAtTarget = confTarget - 1;
523+
524+
// If mempool is limiting txs , return at least the min fee from the mempool
525+
CAmount minPoolFee = pool.GetMinFee(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFeePerK();
526+
if (minPoolFee > 0 && minPoolFee > median)
527+
return CFeeRate(minPoolFee);
528+
529+
if (median < 0)
530+
return CFeeRate(0);
531+
532+
return CFeeRate(median);
533+
}
534+
507535
double CBlockPolicyEstimator::estimatePriority(int confTarget)
508536
{
509537
// Return failure if trying to analyze a target we're not tracking
@@ -513,6 +541,30 @@ double CBlockPolicyEstimator::estimatePriority(int confTarget)
513541
return priStats.EstimateMedianVal(confTarget, SUFFICIENT_PRITXS, MIN_SUCCESS_PCT, true, nBestSeenHeight);
514542
}
515543

544+
double CBlockPolicyEstimator::estimateSmartPriority(int confTarget, int *answerFoundAtTarget, const CTxMemPool& pool)
545+
{
546+
if (answerFoundAtTarget)
547+
*answerFoundAtTarget = confTarget;
548+
// Return failure if trying to analyze a target we're not tracking
549+
if (confTarget <= 0 || (unsigned int)confTarget > priStats.GetMaxConfirms())
550+
return -1;
551+
552+
// If mempool is limiting txs, no priority txs are allowed
553+
CAmount minPoolFee = pool.GetMinFee(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFeePerK();
554+
if (minPoolFee > 0)
555+
return INF_PRIORITY;
556+
557+
double median = -1;
558+
while (median < 0 && (unsigned int)confTarget <= priStats.GetMaxConfirms()) {
559+
median = priStats.EstimateMedianVal(confTarget++, SUFFICIENT_PRITXS, MIN_SUCCESS_PCT, true, nBestSeenHeight);
560+
}
561+
562+
if (answerFoundAtTarget)
563+
*answerFoundAtTarget = confTarget - 1;
564+
565+
return median;
566+
}
567+
516568
void CBlockPolicyEstimator::Write(CAutoFile& fileout)
517569
{
518570
fileout << nBestSeenHeight;

src/policy/fees.h

+15-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
class CAutoFile;
1616
class CFeeRate;
1717
class CTxMemPoolEntry;
18+
class CTxMemPool;
1819

1920
/** \class CBlockPolicyEstimator
2021
* The BlockPolicyEstimator is used for estimating the fee or priority needed
@@ -182,8 +183,8 @@ static const unsigned int MAX_BLOCK_CONFIRMS = 25;
182183
/** Decay of .998 is a half-life of 346 blocks or about 2.4 days */
183184
static const double DEFAULT_DECAY = .998;
184185

185-
/** Require greater than 85% of X fee transactions to be confirmed within Y blocks for X to be big enough */
186-
static const double MIN_SUCCESS_PCT = .85;
186+
/** Require greater than 95% of X fee transactions to be confirmed within Y blocks for X to be big enough */
187+
static const double MIN_SUCCESS_PCT = .95;
187188
static const double UNLIKELY_PCT = .5;
188189

189190
/** Require an avg of 1 tx in the combined fee bucket per block to have stat significance */
@@ -242,9 +243,21 @@ class CBlockPolicyEstimator
242243
/** Return a fee estimate */
243244
CFeeRate estimateFee(int confTarget);
244245

246+
/** Estimate fee rate needed to get be included in a block within
247+
* confTarget blocks. If no answer can be given at confTarget, return an
248+
* estimate at the lowest target where one can be given.
249+
*/
250+
CFeeRate estimateSmartFee(int confTarget, int *answerFoundAtTarget, const CTxMemPool& pool);
251+
245252
/** Return a priority estimate */
246253
double estimatePriority(int confTarget);
247254

255+
/** Estimate priority needed to get be included in a block within
256+
* confTarget blocks. If no answer can be given at confTarget, return an
257+
* estimate at the lowest target where one can be given.
258+
*/
259+
double estimateSmartPriority(int confTarget, int *answerFoundAtTarget, const CTxMemPool& pool);
260+
248261
/** Write estimation data to a file */
249262
void Write(CAutoFile& fileout);
250263

src/policy/policy.h

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ static const unsigned int MAX_STANDARD_TX_SIZE = 100000;
2525
static const unsigned int MAX_P2SH_SIGOPS = 15;
2626
/** The maximum number of sigops we're willing to relay/mine in a single tx */
2727
static const unsigned int MAX_STANDARD_TX_SIGOPS = MAX_BLOCK_SIGOPS/5;
28+
/** Default for -maxmempool, maximum megabytes of mempool memory usage */
29+
static const unsigned int DEFAULT_MAX_MEMPOOL_SIZE = 300;
2830
/**
2931
* Standard script verification flags that standard transactions will comply
3032
* with. However scripts violating these flags may still be present in valid

src/qt/coincontroldialog.cpp

+7-8
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
538538
nBytes = nBytesInputs + ((CoinControlDialog::payAmounts.size() > 0 ? CoinControlDialog::payAmounts.size() + 1 : 2) * 34) + 10; // always assume +1 output for change here
539539

540540
// Priority
541-
double mempoolEstimatePriority = mempool.estimatePriority(nTxConfirmTarget);
541+
double mempoolEstimatePriority = mempool.estimateSmartPriority(nTxConfirmTarget);
542542
dPriority = dPriorityInputs / (nBytes - nBytesInputs + (nQuantityUncompressed * 29)); // 29 = 180 - 151 (uncompressed public keys are over the limit. max 151 bytes of the input are ignored for priority)
543543
sPriorityLabel = CoinControlDialog::getPriorityLabel(dPriority, mempoolEstimatePriority);
544544

@@ -550,10 +550,8 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
550550
// Fee
551551
nPayFee = CWallet::GetMinimumFee(nBytes, nTxConfirmTarget, mempool);
552552

553-
// Allow free?
554-
double dPriorityNeeded = mempoolEstimatePriority;
555-
if (dPriorityNeeded <= 0)
556-
dPriorityNeeded = AllowFreeThreshold(); // not enough data, back to hard-coded
553+
// Allow free? (require at least hard-coded threshold and default to that if no estimate)
554+
double dPriorityNeeded = std::max(mempoolEstimatePriority, AllowFreeThreshold());
557555
fAllowFree = (dPriority >= dPriorityNeeded);
558556

559557
if (fSendFreeTransactions)
@@ -649,8 +647,9 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
649647
double dFeeVary;
650648
if (payTxFee.GetFeePerK() > 0)
651649
dFeeVary = (double)std::max(CWallet::GetRequiredFee(1000), payTxFee.GetFeePerK()) / 1000;
652-
else
653-
dFeeVary = (double)std::max(CWallet::GetRequiredFee(1000), mempool.estimateFee(nTxConfirmTarget).GetFeePerK()) / 1000;
650+
else {
651+
dFeeVary = (double)std::max(CWallet::GetRequiredFee(1000), mempool.estimateSmartFee(nTxConfirmTarget).GetFeePerK()) / 1000;
652+
}
654653
QString toolTip4 = tr("Can vary +/- %1 satoshi(s) per input.").arg(dFeeVary);
655654

656655
l3->setToolTip(toolTip4);
@@ -686,7 +685,7 @@ void CoinControlDialog::updateView()
686685
QFlags<Qt::ItemFlag> flgTristate = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsTristate;
687686

688687
int nDisplayUnit = model->getOptionsModel()->getDisplayUnit();
689-
double mempoolEstimatePriority = mempool.estimatePriority(nTxConfirmTarget);
688+
double mempoolEstimatePriority = mempool.estimateSmartPriority(nTxConfirmTarget);
690689

691690
std::map<QString, std::vector<COutput> > mapCoins;
692691
model->listCoins(mapCoins);

src/qt/sendcoinsdialog.cpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,8 @@ void SendCoinsDialog::updateSmartFeeLabel()
633633
return;
634634

635635
int nBlocksToConfirm = defaultConfirmTarget - ui->sliderSmartFee->value();
636-
CFeeRate feeRate = mempool.estimateFee(nBlocksToConfirm);
636+
int estimateFoundAtBlocks = nBlocksToConfirm;
637+
CFeeRate feeRate = mempool.estimateSmartFee(nBlocksToConfirm, &estimateFoundAtBlocks);
637638
if (feeRate <= CFeeRate(0)) // not enough data => minfee
638639
{
639640
ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), CWallet::GetRequiredFee(1000)) + "/kB");
@@ -644,7 +645,7 @@ void SendCoinsDialog::updateSmartFeeLabel()
644645
{
645646
ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), feeRate.GetFeePerK()) + "/kB");
646647
ui->labelSmartFee2->hide();
647-
ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", nBlocksToConfirm));
648+
ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", estimateFoundAtBlocks));
648649
}
649650

650651
updateFeeMinimizedLabel();

src/rpcblockchain.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "coins.h"
1111
#include "consensus/validation.h"
1212
#include "main.h"
13+
#include "policy/policy.h"
1314
#include "primitives/transaction.h"
1415
#include "rpcserver.h"
1516
#include "streams.h"

src/rpcclient.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
9696
{ "getrawmempool", 0 },
9797
{ "estimatefee", 0 },
9898
{ "estimatepriority", 0 },
99+
{ "estimatesmartfee", 0 },
100+
{ "estimatesmartpriority", 0 },
99101
{ "prioritisetransaction", 1 },
100102
{ "prioritisetransaction", 2 },
101103
{ "setban", 2 },

src/rpcmining.cpp

+72
Original file line numberDiff line numberDiff line change
@@ -726,3 +726,75 @@ UniValue estimatepriority(const UniValue& params, bool fHelp)
726726

727727
return mempool.estimatePriority(nBlocks);
728728
}
729+
730+
UniValue estimatesmartfee(const UniValue& params, bool fHelp)
731+
{
732+
if (fHelp || params.size() != 1)
733+
throw runtime_error(
734+
"estimatesmartfee nblocks\n"
735+
"\nWARNING: This interface is unstable and may disappear or change!\n"
736+
"\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n"
737+
"confirmation within nblocks blocks if possible and return the number of blocks\n"
738+
"for which the estimate is valid.\n"
739+
"\nArguments:\n"
740+
"1. nblocks (numeric)\n"
741+
"\nResult:\n"
742+
"{\n"
743+
" \"feerate\" : x.x, (numeric) estimate fee-per-kilobyte (in BTC)\n"
744+
" \"blocks\" : n (numeric) block number where estimate was found\n"
745+
"}\n"
746+
"\n"
747+
"A negative value is returned if not enough transactions and blocks\n"
748+
"have been observed to make an estimate for any number of blocks.\n"
749+
"However it will not return a value below the mempool reject fee.\n"
750+
"\nExample:\n"
751+
+ HelpExampleCli("estimatesmartfee", "6")
752+
);
753+
754+
RPCTypeCheck(params, boost::assign::list_of(UniValue::VNUM));
755+
756+
int nBlocks = params[0].get_int();
757+
758+
UniValue result(UniValue::VOBJ);
759+
int answerFound;
760+
CFeeRate feeRate = mempool.estimateSmartFee(nBlocks, &answerFound);
761+
result.push_back(Pair("feerate", feeRate == CFeeRate(0) ? -1.0 : ValueFromAmount(feeRate.GetFeePerK())));
762+
result.push_back(Pair("blocks", answerFound));
763+
return result;
764+
}
765+
766+
UniValue estimatesmartpriority(const UniValue& params, bool fHelp)
767+
{
768+
if (fHelp || params.size() != 1)
769+
throw runtime_error(
770+
"estimatesmartpriority nblocks\n"
771+
"\nWARNING: This interface is unstable and may disappear or change!\n"
772+
"\nEstimates the approximate priority a zero-fee transaction needs to begin\n"
773+
"confirmation within nblocks blocks if possible and return the number of blocks\n"
774+
"for which the estimate is valid.\n"
775+
"\nArguments:\n"
776+
"1. nblocks (numeric)\n"
777+
"\nResult:\n"
778+
"{\n"
779+
" \"priority\" : x.x, (numeric) estimated priority\n"
780+
" \"blocks\" : n (numeric) block number where estimate was found\n"
781+
"}\n"
782+
"\n"
783+
"A negative value is returned if not enough transactions and blocks\n"
784+
"have been observed to make an estimate for any number of blocks.\n"
785+
"However if the mempool reject fee is set it will return 1e9 * MAX_MONEY.\n"
786+
"\nExample:\n"
787+
+ HelpExampleCli("estimatesmartpriority", "6")
788+
);
789+
790+
RPCTypeCheck(params, boost::assign::list_of(UniValue::VNUM));
791+
792+
int nBlocks = params[0].get_int();
793+
794+
UniValue result(UniValue::VOBJ);
795+
int answerFound;
796+
double priority = mempool.estimateSmartPriority(nBlocks, &answerFound);
797+
result.push_back(Pair("priority", priority));
798+
result.push_back(Pair("blocks", answerFound));
799+
return result;
800+
}

src/rpcserver.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,8 @@ static const CRPCCommand vRPCCommands[] =
319319
{ "util", "verifymessage", &verifymessage, true },
320320
{ "util", "estimatefee", &estimatefee, true },
321321
{ "util", "estimatepriority", &estimatepriority, true },
322+
{ "util", "estimatesmartfee", &estimatesmartfee, true },
323+
{ "util", "estimatesmartpriority", &estimatesmartpriority, true },
322324

323325
/* Not shown in help */
324326
{ "hidden", "invalidateblock", &invalidateblock, true },

src/rpcserver.h

+2
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ extern UniValue getblocktemplate(const UniValue& params, bool fHelp);
193193
extern UniValue submitblock(const UniValue& params, bool fHelp);
194194
extern UniValue estimatefee(const UniValue& params, bool fHelp);
195195
extern UniValue estimatepriority(const UniValue& params, bool fHelp);
196+
extern UniValue estimatesmartfee(const UniValue& params, bool fHelp);
197+
extern UniValue estimatesmartpriority(const UniValue& params, bool fHelp);
196198

197199
extern UniValue getnewaddress(const UniValue& params, bool fHelp); // in rpcwallet.cpp
198200
extern UniValue getaccountaddress(const UniValue& params, bool fHelp);

0 commit comments

Comments
 (0)