Skip to content

Commit b33d1f5

Browse files
committed
Use fee/priority estimates in wallet CreateTransaction
The wallet now uses the mempool fee estimator with a new command-line option: -txconfirmtarget (default: 1) instead of using hard-coded fees or priorities. A new bitcoind that hasn't seen enough transactions to estimate will fall back to the old hard-coded minimum priority or transaction fee. -paytxfee option overrides -txconfirmtarget. Relaying and mining code isn't changed. For Qt, the coin control dialog now uses priority estimates to label transaction priority (instead of hard-coded constants); unspent outputs were consistently labeled with a much higher priority than is justified by the free transactions actually being accepted into blocks. I did not implement any GUI for setting -txconfirmtarget; I would suggest getting rid of the "Pay transaction fee" GUI and replace it with either "target number of confirmations" or maybe a "faster confirmation <--> lower fee" slider or select box.
1 parent 29264a0 commit b33d1f5

File tree

9 files changed

+105
-47
lines changed

9 files changed

+105
-47
lines changed

doc/release-notes.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,26 @@
11
(note: this is a temporary file, to be added-to by anybody, and moved to
22
release-notes at release time)
33

4+
Transaction fee changes
5+
=======================
6+
7+
This release automatically estimates how high a transaction fee (or how
8+
high a priority) transactions require to be confirmed quickly. The default
9+
settings will create transactions that confirm quickly; see the new
10+
'txconfirmtarget' setting to control the tradeoff between fees and
11+
confirmation times.
12+
13+
Prior releases used hard-coded fees (and priorities), and would
14+
sometimes create transactions that took a very long time to confirm.
15+
16+
17+
New Command Line Options
18+
========================
19+
20+
-txconfirmtarget=n : create transactions that have enough fees (or priority)
21+
so they are likely to confirm within n blocks (default: 1). This setting
22+
is over-ridden by the -paytxfee option.
23+
424
New RPC methods
525
===============
626

src/core.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ class CFeeRate
131131
friend bool operator<(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK < b.nSatoshisPerK; }
132132
friend bool operator>(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK > b.nSatoshisPerK; }
133133
friend bool operator==(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK == b.nSatoshisPerK; }
134-
134+
friend bool operator<=(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK <= b.nSatoshisPerK; }
135+
friend bool operator>=(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK >= b.nSatoshisPerK; }
135136
std::string ToString() const;
136137

137138
IMPLEMENT_SERIALIZE( READWRITE(nSatoshisPerK); )

src/init.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ std::string HelpMessage(HelpMessageMode mode)
254254
strUsage += "\n" + _("Wallet options:") + "\n";
255255
strUsage += " -disablewallet " + _("Do not load the wallet and disable wallet RPC calls") + "\n";
256256
strUsage += " -paytxfee=<amt> " + strprintf(_("Fee (in BTC/kB) to add to transactions you send (default: %s)"), FormatMoney(payTxFee.GetFeePerK())) + "\n";
257+
strUsage += " -txconfirmtarget=<n> " + _("If paytxfee is not set, include enough fee so transactions are confirmed on average within n blocks (default: 1)") + "\n";
257258
strUsage += " -rescan " + _("Rescan the block chain for missing wallet transactions") + " " + _("on startup") + "\n";
258259
strUsage += " -salvagewallet " + _("Attempt to recover private keys from a corrupt wallet.dat") + " " + _("on startup") + "\n";
259260
strUsage += " -spendzeroconfchange " + _("Spend unconfirmed change when sending transactions (default: 1)") + "\n";
@@ -635,7 +636,13 @@ bool AppInit2(boost::thread_group& threadGroup)
635636
if (nFeePerK > nHighTransactionFeeWarning)
636637
InitWarning(_("Warning: -paytxfee is set very high! This is the transaction fee you will pay if you send a transaction."));
637638
payTxFee = CFeeRate(nFeePerK, 1000);
639+
if (payTxFee < CTransaction::minRelayTxFee)
640+
{
641+
return InitError(strprintf(_("Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)"),
642+
mapArgs["-paytxfee"], CTransaction::minRelayTxFee.ToString()));
643+
}
638644
}
645+
nTxConfirmTarget = GetArg("-txconfirmtarget", 1);
639646
bSpendZeroConfChange = GetArg("-spendzeroconfchange", true);
640647

641648
std::string strWalletFile = GetArg("-wallet", "wallet.dat");

src/main.cpp

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -858,7 +858,7 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state)
858858
return true;
859859
}
860860

861-
int64_t GetMinFee(const CTransaction& tx, unsigned int nBytes, bool fAllowFree, enum GetMinFee_mode mode)
861+
int64_t GetMinRelayFee(const CTransaction& tx, unsigned int nBytes, bool fAllowFree)
862862
{
863863
{
864864
LOCK(mempool.cs);
@@ -870,20 +870,15 @@ int64_t GetMinFee(const CTransaction& tx, unsigned int nBytes, bool fAllowFree,
870870
return 0;
871871
}
872872

873-
// Base fee is either minTxFee or minRelayTxFee
874-
CFeeRate baseFeeRate = (mode == GMF_RELAY) ? tx.minRelayTxFee : tx.minTxFee;
875-
876-
int64_t nMinFee = baseFeeRate.GetFee(nBytes);
873+
int64_t nMinFee = tx.minRelayTxFee.GetFee(nBytes);
877874

878875
if (fAllowFree)
879876
{
880877
// There is a free transaction area in blocks created by most miners,
881878
// * If we are relaying we allow transactions up to DEFAULT_BLOCK_PRIORITY_SIZE - 1000
882879
// to be considered to fall into this category. We don't want to encourage sending
883880
// multiple transactions instead of one big transaction to avoid fees.
884-
// * If we are creating a transaction we allow transactions up to 1,000 bytes
885-
// to be considered safe and assume they can likely make it into this section.
886-
if (nBytes < (mode == GMF_SEND ? 1000 : (DEFAULT_BLOCK_PRIORITY_SIZE - 1000)))
881+
if (nBytes < (DEFAULT_BLOCK_PRIORITY_SIZE - 1000))
887882
nMinFee = 0;
888883
}
889884

@@ -1005,7 +1000,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
10051000
unsigned int nSize = entry.GetTxSize();
10061001

10071002
// Don't accept it if it can't get into a block
1008-
int64_t txMinFee = GetMinFee(tx, nSize, true, GMF_RELAY);
1003+
int64_t txMinFee = GetMinRelayFee(tx, nSize, true);
10091004
if (fLimitFree && nFees < txMinFee)
10101005
return state.DoS(0, error("AcceptToMemoryPool : not enough fees %s, %d < %d",
10111006
hash.ToString(), nFees, txMinFee),

src/main.h

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -245,14 +245,7 @@ struct CDiskTxPos : public CDiskBlockPos
245245
};
246246

247247

248-
249-
enum GetMinFee_mode
250-
{
251-
GMF_RELAY,
252-
GMF_SEND,
253-
};
254-
255-
int64_t GetMinFee(const CTransaction& tx, unsigned int nBytes, bool fAllowFree, enum GetMinFee_mode mode);
248+
int64_t GetMinRelayFee(const CTransaction& tx, unsigned int nBytes, bool fAllowFree);
256249

257250
//
258251
// Check transaction inputs, and make sure any

src/qt/coincontroldialog.cpp

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
#include "main.h"
1717
#include "wallet.h"
1818

19+
#include <boost/assign/list_of.hpp> // for 'map_list_of()'
20+
1921
#include <QApplication>
2022
#include <QCheckBox>
2123
#include <QCursor>
@@ -400,23 +402,24 @@ void CoinControlDialog::viewItemChanged(QTreeWidgetItem* item, int column)
400402
}
401403

402404
// return human readable label for priority number
403-
QString CoinControlDialog::getPriorityLabel(double dPriority)
405+
QString CoinControlDialog::getPriorityLabel(const CTxMemPool& pool, double dPriority)
404406
{
405-
if (AllowFree(dPriority)) // at least medium
407+
// confirmations -> textual description
408+
typedef std::map<unsigned int, QString> PriorityDescription;
409+
static PriorityDescription priorityDescriptions = boost::assign::map_list_of
410+
(1, tr("highest"))(2, tr("higher"))(3, tr("high"))
411+
(5, tr("medium-high"))(6, tr("medium"))
412+
(10, tr("low-medium"))(15, tr("low"))
413+
(20, tr("lower"));
414+
415+
BOOST_FOREACH(const PriorityDescription::value_type& i, priorityDescriptions)
406416
{
407-
if (AllowFree(dPriority / 1000000)) return tr("highest");
408-
else if (AllowFree(dPriority / 100000)) return tr("higher");
409-
else if (AllowFree(dPriority / 10000)) return tr("high");
410-
else if (AllowFree(dPriority / 1000)) return tr("medium-high");
411-
else return tr("medium");
412-
}
413-
else
414-
{
415-
if (AllowFree(dPriority * 10)) return tr("low-medium");
416-
else if (AllowFree(dPriority * 100)) return tr("low");
417-
else if (AllowFree(dPriority * 1000)) return tr("lower");
418-
else return tr("lowest");
417+
double p = mempool.estimatePriority(i.first);
418+
if (p > 0 && dPriority >= p) return i.second;
419419
}
420+
// Note: if mempool hasn't accumulated enough history (estimatePriority
421+
// returns -1) we're conservative and classify as "lowest"
422+
return tr("lowest");
420423
}
421424

422425
// shows count of locked unspent outputs
@@ -518,15 +521,20 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
518521

519522
// Priority
520523
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)
521-
sPriorityLabel = CoinControlDialog::getPriorityLabel(dPriority);
524+
sPriorityLabel = CoinControlDialog::getPriorityLabel(mempool, dPriority);
522525

523526
// Fee
524527
int64_t nFee = payTxFee.GetFee(max((unsigned int)1000, nBytes));
525528

526529
// Min Fee
527-
int64_t nMinFee = GetMinFee(txDummy, nBytes, AllowFree(dPriority), GMF_SEND);
530+
nPayFee = CWallet::GetMinimumFee(nBytes, nTxConfirmTarget, mempool);
531+
532+
double dPriorityNeeded = mempool.estimatePriority(nTxConfirmTarget);
533+
if (dPriorityNeeded <= 0) // Not enough mempool history: never send free
534+
dPriorityNeeded = std::numeric_limits<double>::max();
528535

529-
nPayFee = max(nFee, nMinFee);
536+
if (nBytes <= MAX_FREE_TRANSACTION_CREATE_SIZE && dPriority >= dPriorityNeeded)
537+
nPayFee = 0;
530538

531539
if (nPayAmount > 0)
532540
{
@@ -591,7 +599,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
591599
}
592600

593601
// turn labels "red"
594-
l5->setStyleSheet((nBytes >= 1000) ? "color:red;" : ""); // Bytes >= 1000
602+
l5->setStyleSheet((nBytes >= MAX_FREE_TRANSACTION_CREATE_SIZE) ? "color:red;" : "");// Bytes >= 1000
595603
l6->setStyleSheet((dPriority > 0 && !AllowFree(dPriority)) ? "color:red;" : ""); // Priority < "medium"
596604
l7->setStyleSheet((fDust) ? "color:red;" : ""); // Dust = "yes"
597605

@@ -732,7 +740,7 @@ void CoinControlDialog::updateView()
732740

733741
// priority
734742
double dPriority = ((double)out.tx->vout[out.i].nValue / (nInputSize + 78)) * (out.nDepth+1); // 78 = 2 * 34 + 10
735-
itemOutput->setText(COLUMN_PRIORITY, CoinControlDialog::getPriorityLabel(dPriority));
743+
itemOutput->setText(COLUMN_PRIORITY, CoinControlDialog::getPriorityLabel(mempool, dPriority));
736744
itemOutput->setText(COLUMN_PRIORITY_INT64, strPad(QString::number((int64_t)dPriority), 20, " "));
737745
dPrioritySum += (double)out.tx->vout[out.i].nValue * (out.nDepth+1);
738746
nInputSum += nInputSize;
@@ -765,7 +773,7 @@ void CoinControlDialog::updateView()
765773
itemWalletAddress->setText(COLUMN_CHECKBOX, "(" + QString::number(nChildren) + ")");
766774
itemWalletAddress->setText(COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, nSum));
767775
itemWalletAddress->setText(COLUMN_AMOUNT_INT64, strPad(QString::number(nSum), 15, " "));
768-
itemWalletAddress->setText(COLUMN_PRIORITY, CoinControlDialog::getPriorityLabel(dPrioritySum));
776+
itemWalletAddress->setText(COLUMN_PRIORITY, CoinControlDialog::getPriorityLabel(mempool, dPrioritySum));
769777
itemWalletAddress->setText(COLUMN_PRIORITY_INT64, strPad(QString::number((int64_t)dPrioritySum), 20, " "));
770778
}
771779
}

src/qt/coincontroldialog.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ namespace Ui {
1919
}
2020
class WalletModel;
2121
class CCoinControl;
22+
class CTxMemPool;
2223

2324
class CoinControlDialog : public QDialog
2425
{
@@ -32,7 +33,7 @@ class CoinControlDialog : public QDialog
3233

3334
// static because also called from sendcoinsdialog
3435
static void updateLabels(WalletModel*, QDialog*);
35-
static QString getPriorityLabel(double);
36+
static QString getPriorityLabel(const CTxMemPool& pool, double);
3637

3738
static QList<qint64> payAmounts;
3839
static CCoinControl *coinControl;

src/wallet.cpp

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ using namespace std;
1818

1919
// Settings
2020
CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE);
21+
unsigned int nTxConfirmTarget = 1;
2122
bool bSpendZeroConfChange = true;
2223

2324
//////////////////////////////////////////////////////////////////////////////
@@ -1273,6 +1274,7 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend,
12731274
return false;
12741275
}
12751276

1277+
wtxNew.fTimeReceivedIsTxTime = true;
12761278
wtxNew.BindWallet(this);
12771279
CMutableTransaction txNew;
12781280

@@ -1393,19 +1395,31 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend,
13931395
}
13941396
dPriority = wtxNew.ComputePriority(dPriority, nBytes);
13951397

1396-
// Check that enough fee is included
1397-
int64_t nPayFee = payTxFee.GetFee(nBytes);
1398-
bool fAllowFree = AllowFree(dPriority);
1399-
int64_t nMinFee = GetMinFee(wtxNew, nBytes, fAllowFree, GMF_SEND);
1400-
if (nFeeRet < max(nPayFee, nMinFee))
1398+
int64_t nFeeNeeded = GetMinimumFee(nBytes, nTxConfirmTarget, mempool);
1399+
1400+
if (nFeeRet >= nFeeNeeded)
1401+
break; // Done, enough fee included.
1402+
1403+
// Too big to send for free? Include more fee and try again:
1404+
if (nBytes > MAX_FREE_TRANSACTION_CREATE_SIZE)
14011405
{
1402-
nFeeRet = max(nPayFee, nMinFee);
1406+
nFeeRet = nFeeNeeded;
14031407
continue;
14041408
}
14051409

1406-
wtxNew.fTimeReceivedIsTxTime = true;
1410+
// Not enough fee: enough priority?
1411+
double dPriorityNeeded = mempool.estimatePriority(nTxConfirmTarget);
1412+
// Not enough mempool history to estimate: use hard-coded AllowFree.
1413+
if (dPriorityNeeded <= 0 && AllowFree(dPriority))
1414+
break;
14071415

1408-
break;
1416+
// Small enough, and priority high enough, to send for free
1417+
if (dPriority >= dPriorityNeeded)
1418+
break;
1419+
1420+
// Include more fee and try again.
1421+
nFeeRet = nFeeNeeded;
1422+
continue;
14091423
}
14101424
}
14111425
}
@@ -1513,6 +1527,20 @@ string CWallet::SendMoneyToDestination(const CTxDestination& address, int64_t nV
15131527
return SendMoney(scriptPubKey, nValue, wtxNew);
15141528
}
15151529

1530+
int64_t CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool)
1531+
{
1532+
// payTxFee is user-set "I want to pay this much"
1533+
int64_t nFeeNeeded = payTxFee.GetFee(nTxBytes);
1534+
// User didn't set: use -txconfirmtarget to estimate...
1535+
if (nFeeNeeded == 0)
1536+
nFeeNeeded = pool.estimateFee(nConfirmTarget).GetFee(nTxBytes);
1537+
// ... unless we don't have enough mempool data, in which case fall
1538+
// back to a hard-coded fee
1539+
if (nFeeNeeded == 0)
1540+
nFeeNeeded = CTransaction::minTxFee.GetFee(nTxBytes);
1541+
return nFeeNeeded;
1542+
}
1543+
15161544

15171545

15181546

src/wallet.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,15 @@
2525

2626
// Settings
2727
extern CFeeRate payTxFee;
28+
extern unsigned int nTxConfirmTarget;
2829
extern bool bSpendZeroConfChange;
2930

3031
// -paytxfee default
3132
static const int64_t DEFAULT_TRANSACTION_FEE = 0;
3233
// -paytxfee will warn if called with a higher fee than this amount (in satoshis) per KB
3334
static const int nHighTransactionFeeWarning = 0.01 * COIN;
35+
// Largest (in bytes) free transaction we're willing to create
36+
static const unsigned int MAX_FREE_TRANSACTION_CREATE_SIZE = 1000;
3437

3538
class CAccountingEntry;
3639
class CCoinControl;
@@ -265,6 +268,8 @@ class CWallet : public CCryptoKeyStore, public CWalletInterface
265268
std::string SendMoney(CScript scriptPubKey, int64_t nValue, CWalletTx& wtxNew);
266269
std::string SendMoneyToDestination(const CTxDestination &address, int64_t nValue, CWalletTx& wtxNew);
267270

271+
static int64_t GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool);
272+
268273
bool NewKeyPool();
269274
bool TopUpKeyPool(unsigned int kpSize = 0);
270275
void ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool);

0 commit comments

Comments
 (0)