Browse Source

Merge branch 'smartfee_wallet'

0.10
Gavin Andresen 11 years ago
parent
commit
21876d3831
No known key found for this signature in database
GPG Key ID: 7588242FBE38D3A8
  1. 20
      doc/release-notes.md
  2. 2
      src/core.cpp
  3. 9
      src/core.h
  4. 31
      src/init.cpp
  5. 33
      src/main.cpp
  6. 12
      src/main.h
  7. 2
      src/miner.cpp
  8. 60
      src/qt/coincontroldialog.cpp
  9. 3
      src/qt/coincontroldialog.h
  10. 3
      src/qt/guiutil.cpp
  11. 9
      src/qt/optionsdialog.cpp
  12. 2
      src/qt/paymentserver.cpp
  13. 2
      src/rpcmisc.cpp
  14. 2
      src/rpcnet.cpp
  15. 35
      src/txmempool.cpp
  16. 4
      src/txmempool.h
  17. 51
      src/wallet.cpp
  18. 6
      src/wallet.h

20
doc/release-notes.md

@ -1,6 +1,26 @@
(note: this is a temporary file, to be added-to by anybody, and moved to (note: this is a temporary file, to be added-to by anybody, and moved to
release-notes at release time) release-notes at release time)
Transaction fee changes
=======================
This release automatically estimates how high a transaction fee (or how
high a priority) transactions require to be confirmed quickly. The default
settings will create transactions that confirm quickly; see the new
'txconfirmtarget' setting to control the tradeoff between fees and
confirmation times.
Prior releases used hard-coded fees (and priorities), and would
sometimes create transactions that took a very long time to confirm.
New Command Line Options
========================
-txconfirmtarget=n : create transactions that have enough fees (or priority)
so they are likely to confirm within n blocks (default: 1). This setting
is over-ridden by the -paytxfee option.
New RPC methods New RPC methods
=============== ===============

2
src/core.cpp

@ -80,7 +80,7 @@ CFeeRate::CFeeRate(int64_t nFeePaid, size_t nSize)
nSatoshisPerK = 0; nSatoshisPerK = 0;
} }
int64_t CFeeRate::GetFee(size_t nSize) int64_t CFeeRate::GetFee(size_t nSize) const
{ {
return nSatoshisPerK*nSize / 1000; return nSatoshisPerK*nSize / 1000;
} }

9
src/core.h

@ -125,13 +125,14 @@ public:
CFeeRate(int64_t nFeePaid, size_t nSize); CFeeRate(int64_t nFeePaid, size_t nSize);
CFeeRate(const CFeeRate& other) { nSatoshisPerK = other.nSatoshisPerK; } CFeeRate(const CFeeRate& other) { nSatoshisPerK = other.nSatoshisPerK; }
int64_t GetFee(size_t size); // unit returned is satoshis int64_t GetFee(size_t size) const; // unit returned is satoshis
int64_t GetFeePerK() { return GetFee(1000); } // satoshis-per-1000-bytes int64_t GetFeePerK() const { return GetFee(1000); } // satoshis-per-1000-bytes
friend bool operator<(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK < b.nSatoshisPerK; } friend bool operator<(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK < b.nSatoshisPerK; }
friend bool operator>(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK > b.nSatoshisPerK; } friend bool operator>(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK > b.nSatoshisPerK; }
friend bool operator==(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK == b.nSatoshisPerK; } friend bool operator==(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK == b.nSatoshisPerK; }
friend bool operator<=(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK <= b.nSatoshisPerK; }
friend bool operator>=(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK >= b.nSatoshisPerK; }
std::string ToString() const; std::string ToString() const;
IMPLEMENT_SERIALIZE( READWRITE(nSatoshisPerK); ) IMPLEMENT_SERIALIZE( READWRITE(nSatoshisPerK); )
@ -216,8 +217,6 @@ private:
void UpdateHash() const; void UpdateHash() const;
public: public:
static CFeeRate minTxFee;
static CFeeRate minRelayTxFee;
static const int CURRENT_VERSION=1; static const int CURRENT_VERSION=1;
// The local variables are made const to prevent unintended modification // The local variables are made const to prevent unintended modification

31
src/init.cpp

@ -253,14 +253,16 @@ std::string HelpMessage(HelpMessageMode mode)
#ifdef ENABLE_WALLET #ifdef ENABLE_WALLET
strUsage += "\n" + _("Wallet options:") + "\n"; strUsage += "\n" + _("Wallet options:") + "\n";
strUsage += " -disablewallet " + _("Do not load the wallet and disable wallet RPC calls") + "\n"; strUsage += " -disablewallet " + _("Do not load the wallet and disable wallet RPC calls") + "\n";
strUsage += " -mintxfee=<amt> " + strprintf(_("Fees (in BTC/Kb) smaller than this are considered zero fee for transaction creation (default: %s)"), FormatMoney(CWallet::minTxFee.GetFeePerK())) + "\n";
strUsage += " -paytxfee=<amt> " + strprintf(_("Fee (in BTC/kB) to add to transactions you send (default: %s)"), FormatMoney(payTxFee.GetFeePerK())) + "\n"; strUsage += " -paytxfee=<amt> " + strprintf(_("Fee (in BTC/kB) to add to transactions you send (default: %s)"), FormatMoney(payTxFee.GetFeePerK())) + "\n";
strUsage += " -rescan " + _("Rescan the block chain for missing wallet transactions") + " " + _("on startup") + "\n"; strUsage += " -rescan " + _("Rescan the block chain for missing wallet transactions") + " " + _("on startup") + "\n";
strUsage += " -respendnotify=<cmd> " + _("Execute command when a network tx respends wallet tx input (%s=respend TxID, %t=wallet TxID)") + "\n";
strUsage += " -salvagewallet " + _("Attempt to recover private keys from a corrupt wallet.dat") + " " + _("on startup") + "\n"; strUsage += " -salvagewallet " + _("Attempt to recover private keys from a corrupt wallet.dat") + " " + _("on startup") + "\n";
strUsage += " -spendzeroconfchange " + _("Spend unconfirmed change when sending transactions (default: 1)") + "\n"; strUsage += " -spendzeroconfchange " + _("Spend unconfirmed change when sending transactions (default: 1)") + "\n";
strUsage += " -txconfirmtarget=<n> " + _("If paytxfee is not set, include enough fee so transactions are confirmed on average within n blocks (default: 1)") + "\n";
strUsage += " -upgradewallet " + _("Upgrade wallet to latest format") + " " + _("on startup") + "\n"; strUsage += " -upgradewallet " + _("Upgrade wallet to latest format") + " " + _("on startup") + "\n";
strUsage += " -wallet=<file> " + _("Specify wallet file (within data directory)") + " " + _("(default: wallet.dat)") + "\n"; strUsage += " -wallet=<file> " + _("Specify wallet file (within data directory)") + " " + _("(default: wallet.dat)") + "\n";
strUsage += " -walletnotify=<cmd> " + _("Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)") + "\n"; strUsage += " -walletnotify=<cmd> " + _("Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)") + "\n";
strUsage += " -respendnotify=<cmd> " + _("Execute command when a network tx respends wallet tx input (%s=respend TxID, %t=wallet TxID)") + "\n";
strUsage += " -zapwallettxes=<mode> " + _("Delete all wallet transactions and only recover those part of the blockchain through -rescan on startup") + "\n"; strUsage += " -zapwallettxes=<mode> " + _("Delete all wallet transactions and only recover those part of the blockchain through -rescan on startup") + "\n";
strUsage += " " + _("(default: 1, 1 = keep tx meta data e.g. account owner and payment request information, 2 = drop tx meta data)") + "\n"; strUsage += " " + _("(default: 1, 1 = keep tx meta data e.g. account owner and payment request information, 2 = drop tx meta data)") + "\n";
#endif #endif
@ -294,8 +296,7 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += " -limitfreerelay=<n> " + _("Continuously rate-limit free transactions to <n>*1000 bytes per minute (default:15)") + "\n"; strUsage += " -limitfreerelay=<n> " + _("Continuously rate-limit free transactions to <n>*1000 bytes per minute (default:15)") + "\n";
strUsage += " -maxsigcachesize=<n> " + _("Limit size of signature cache to <n> entries (default: 50000)") + "\n"; strUsage += " -maxsigcachesize=<n> " + _("Limit size of signature cache to <n> entries (default: 50000)") + "\n";
} }
strUsage += " -mintxfee=<amt> " + strprintf(_("Fees (in BTC/Kb) smaller than this are considered zero fee for transaction creation (default: %s)"), FormatMoney(CTransaction::minTxFee.GetFeePerK())) + "\n"; strUsage += " -minrelaytxfee=<amt> " + strprintf(_("Fees (in BTC/Kb) smaller than this are considered zero fee for relaying (default: %s)"), FormatMoney(::minRelayTxFee.GetFeePerK())) + "\n";
strUsage += " -minrelaytxfee=<amt> " + strprintf(_("Fees (in BTC/Kb) smaller than this are considered zero fee for relaying (default: %s)"), FormatMoney(CTransaction::minRelayTxFee.GetFeePerK())) + "\n";
strUsage += " -printtoconsole " + _("Send trace/debug info to console instead of debug.log file") + "\n"; strUsage += " -printtoconsole " + _("Send trace/debug info to console instead of debug.log file") + "\n";
if (GetBoolArg("-help-debug", false)) if (GetBoolArg("-help-debug", false))
{ {
@ -609,24 +610,24 @@ bool AppInit2(boost::thread_group& threadGroup)
// a transaction spammer can cheaply fill blocks using // a transaction spammer can cheaply fill blocks using
// 1-satoshi-fee transactions. It should be set above the real // 1-satoshi-fee transactions. It should be set above the real
// cost to you of processing a transaction. // cost to you of processing a transaction.
if (mapArgs.count("-mintxfee"))
{
int64_t n = 0;
if (ParseMoney(mapArgs["-mintxfee"], n) && n > 0)
CTransaction::minTxFee = CFeeRate(n);
else
return InitError(strprintf(_("Invalid amount for -mintxfee=<amount>: '%s'"), mapArgs["-mintxfee"]));
}
if (mapArgs.count("-minrelaytxfee")) if (mapArgs.count("-minrelaytxfee"))
{ {
int64_t n = 0; int64_t n = 0;
if (ParseMoney(mapArgs["-minrelaytxfee"], n) && n > 0) if (ParseMoney(mapArgs["-minrelaytxfee"], n) && n > 0)
CTransaction::minRelayTxFee = CFeeRate(n); ::minRelayTxFee = CFeeRate(n);
else else
return InitError(strprintf(_("Invalid amount for -minrelaytxfee=<amount>: '%s'"), mapArgs["-minrelaytxfee"])); return InitError(strprintf(_("Invalid amount for -minrelaytxfee=<amount>: '%s'"), mapArgs["-minrelaytxfee"]));
} }
#ifdef ENABLE_WALLET #ifdef ENABLE_WALLET
if (mapArgs.count("-mintxfee"))
{
int64_t n = 0;
if (ParseMoney(mapArgs["-mintxfee"], n) && n > 0)
CWallet::minTxFee = CFeeRate(n);
else
return InitError(strprintf(_("Invalid amount for -mintxfee=<amount>: '%s'"), mapArgs["-mintxfee"]));
}
if (mapArgs.count("-paytxfee")) if (mapArgs.count("-paytxfee"))
{ {
int64_t nFeePerK = 0; int64_t nFeePerK = 0;
@ -635,7 +636,13 @@ bool AppInit2(boost::thread_group& threadGroup)
if (nFeePerK > nHighTransactionFeeWarning) if (nFeePerK > nHighTransactionFeeWarning)
InitWarning(_("Warning: -paytxfee is set very high! This is the transaction fee you will pay if you send a transaction.")); InitWarning(_("Warning: -paytxfee is set very high! This is the transaction fee you will pay if you send a transaction."));
payTxFee = CFeeRate(nFeePerK, 1000); payTxFee = CFeeRate(nFeePerK, 1000);
if (payTxFee < ::minRelayTxFee)
{
return InitError(strprintf(_("Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)"),
mapArgs["-paytxfee"], ::minRelayTxFee.ToString()));
}
} }
nTxConfirmTarget = GetArg("-txconfirmtarget", 1);
bSpendZeroConfChange = GetArg("-spendzeroconfchange", true); bSpendZeroConfChange = GetArg("-spendzeroconfchange", true);
std::string strWalletFile = GetArg("-wallet", "wallet.dat"); std::string strWalletFile = GetArg("-wallet", "wallet.dat");

33
src/main.cpp

@ -38,8 +38,6 @@ using namespace boost;
CCriticalSection cs_main; CCriticalSection cs_main;
CTxMemPool mempool;
map<uint256, CBlockIndex*> mapBlockIndex; map<uint256, CBlockIndex*> mapBlockIndex;
CChain chainActive; CChain chainActive;
int64_t nTimeBestReceived = 0; int64_t nTimeBestReceived = 0;
@ -50,10 +48,10 @@ bool fBenchmark = false;
bool fTxIndex = false; bool fTxIndex = false;
unsigned int nCoinCacheSize = 5000; unsigned int nCoinCacheSize = 5000;
/** Fees smaller than this (in satoshi) are considered zero fee (for transaction creation) */
CFeeRate CTransaction::minTxFee = CFeeRate(10000); // Override with -mintxfee
/** Fees smaller than this (in satoshi) are considered zero fee (for relaying and mining) */ /** Fees smaller than this (in satoshi) are considered zero fee (for relaying and mining) */
CFeeRate CTransaction::minRelayTxFee = CFeeRate(1000); CFeeRate minRelayTxFee = CFeeRate(1000);
CTxMemPool mempool(::minRelayTxFee);
struct COrphanBlock { struct COrphanBlock {
uint256 hashBlock; uint256 hashBlock;
@ -617,7 +615,7 @@ bool IsStandardTx(const CTransaction& tx, string& reason)
} }
if (whichType == TX_NULL_DATA) if (whichType == TX_NULL_DATA)
nDataOut++; nDataOut++;
else if (txout.IsDust(CTransaction::minRelayTxFee)) { else if (txout.IsDust(::minRelayTxFee)) {
reason = "dust"; reason = "dust";
return false; return false;
} }
@ -858,7 +856,7 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state)
return true; return true;
} }
int64_t GetMinFee(const CTransaction& tx, unsigned int nBytes, bool fAllowFree, enum GetMinFee_mode mode) int64_t GetMinRelayFee(const CTransaction& tx, unsigned int nBytes, bool fAllowFree)
{ {
{ {
LOCK(mempool.cs); LOCK(mempool.cs);
@ -870,10 +868,7 @@ int64_t GetMinFee(const CTransaction& tx, unsigned int nBytes, bool fAllowFree,
return 0; return 0;
} }
// Base fee is either minTxFee or minRelayTxFee int64_t nMinFee = ::minRelayTxFee.GetFee(nBytes);
CFeeRate baseFeeRate = (mode == GMF_RELAY) ? tx.minRelayTxFee : tx.minTxFee;
int64_t nMinFee = baseFeeRate.GetFee(nBytes);
if (fAllowFree) if (fAllowFree)
{ {
@ -881,9 +876,7 @@ int64_t GetMinFee(const CTransaction& tx, unsigned int nBytes, bool fAllowFree,
// * If we are relaying we allow transactions up to DEFAULT_BLOCK_PRIORITY_SIZE - 1000 // * If we are relaying we allow transactions up to DEFAULT_BLOCK_PRIORITY_SIZE - 1000
// to be considered to fall into this category. We don't want to encourage sending // to be considered to fall into this category. We don't want to encourage sending
// multiple transactions instead of one big transaction to avoid fees. // multiple transactions instead of one big transaction to avoid fees.
// * If we are creating a transaction we allow transactions up to 1,000 bytes if (nBytes < (DEFAULT_BLOCK_PRIORITY_SIZE - 1000))
// to be considered safe and assume they can likely make it into this section.
if (nBytes < (mode == GMF_SEND ? 1000 : (DEFAULT_BLOCK_PRIORITY_SIZE - 1000)))
nMinFee = 0; nMinFee = 0;
} }
@ -1005,7 +998,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
unsigned int nSize = entry.GetTxSize(); unsigned int nSize = entry.GetTxSize();
// Don't accept it if it can't get into a block // Don't accept it if it can't get into a block
int64_t txMinFee = GetMinFee(tx, nSize, true, GMF_RELAY); int64_t txMinFee = GetMinRelayFee(tx, nSize, true);
if (fLimitFree && nFees < txMinFee) if (fLimitFree && nFees < txMinFee)
return state.DoS(0, error("AcceptToMemoryPool : not enough fees %s, %d < %d", return state.DoS(0, error("AcceptToMemoryPool : not enough fees %s, %d < %d",
hash.ToString(), nFees, txMinFee), hash.ToString(), nFees, txMinFee),
@ -1014,7 +1007,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
// Continuously rate-limit free (really, very-low-fee)transactions // Continuously rate-limit free (really, very-low-fee)transactions
// This mitigates 'penny-flooding' -- sending thousands of free transactions just to // This mitigates 'penny-flooding' -- sending thousands of free transactions just to
// be annoying or make others' transactions take longer to confirm. // be annoying or make others' transactions take longer to confirm.
if (fLimitFree && nFees < CTransaction::minRelayTxFee.GetFee(nSize)) if (fLimitFree && nFees < ::minRelayTxFee.GetFee(nSize))
{ {
static double dFreeCount; static double dFreeCount;
static int64_t nLastFreeTime; static int64_t nLastFreeTime;
@ -1027,10 +1020,10 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
LogPrint("mempool", "Rate limit dFreeCount: %g => %g\n", dFreeCount, dFreeCount+nSize); LogPrint("mempool", "Rate limit dFreeCount: %g => %g\n", dFreeCount, dFreeCount+nSize);
} }
if (fRejectInsaneFee && nFees > CTransaction::minRelayTxFee.GetFee(nSize) * 10000) if (fRejectInsaneFee && nFees > ::minRelayTxFee.GetFee(nSize) * 10000)
return error("AcceptToMemoryPool: : insane fees %s, %d > %d", return error("AcceptToMemoryPool: : insane fees %s, %d > %d",
hash.ToString(), hash.ToString(),
nFees, CTransaction::minRelayTxFee.GetFee(nSize) * 10000); nFees, ::minRelayTxFee.GetFee(nSize) * 10000);
// Check against previous transactions // Check against previous transactions
// This is done last to help prevent CPU exhaustion denial-of-service attacks. // This is done last to help prevent CPU exhaustion denial-of-service attacks.
@ -1134,10 +1127,10 @@ int CMerkleTx::GetBlocksToMaturity() const
} }
bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree) bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree, bool fRejectInsaneFee)
{ {
CValidationState state; CValidationState state;
return ::AcceptToMemoryPool(mempool, state, *this, fLimitFree, NULL); return ::AcceptToMemoryPool(mempool, state, *this, fLimitFree, NULL, fRejectInsaneFee);
} }

12
src/main.h

@ -93,6 +93,7 @@ extern bool fBenchmark;
extern int nScriptCheckThreads; extern int nScriptCheckThreads;
extern bool fTxIndex; extern bool fTxIndex;
extern unsigned int nCoinCacheSize; extern unsigned int nCoinCacheSize;
extern CFeeRate minRelayTxFee;
// Minimum disk space required - used in CheckDiskSpace() // Minimum disk space required - used in CheckDiskSpace()
static const uint64_t nMinDiskSpace = 52428800; static const uint64_t nMinDiskSpace = 52428800;
@ -245,14 +246,7 @@ struct CDiskTxPos : public CDiskBlockPos
}; };
int64_t GetMinRelayFee(const CTransaction& tx, unsigned int nBytes, bool fAllowFree);
enum GetMinFee_mode
{
GMF_RELAY,
GMF_SEND,
};
int64_t GetMinFee(const CTransaction& tx, unsigned int nBytes, bool fAllowFree, enum GetMinFee_mode mode);
// //
// Check transaction inputs, and make sure any // Check transaction inputs, and make sure any
@ -459,7 +453,7 @@ public:
int GetDepthInMainChain() const { CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet); } int GetDepthInMainChain() const { CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet); }
bool IsInMainChain() const { CBlockIndex *pindexRet; return GetDepthInMainChainINTERNAL(pindexRet) > 0; } bool IsInMainChain() const { CBlockIndex *pindexRet; return GetDepthInMainChainINTERNAL(pindexRet) > 0; }
int GetBlocksToMaturity() const; int GetBlocksToMaturity() const;
bool AcceptToMemoryPool(bool fLimitFree=true); bool AcceptToMemoryPool(bool fLimitFree=true, bool fRejectInsaneFee=true);
}; };

2
src/miner.cpp

@ -236,7 +236,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
double dPriorityDelta = 0; double dPriorityDelta = 0;
int64_t nFeeDelta = 0; int64_t nFeeDelta = 0;
mempool.ApplyDeltas(hash, dPriorityDelta, nFeeDelta); mempool.ApplyDeltas(hash, dPriorityDelta, nFeeDelta);
if (fSortedByFee && (dPriorityDelta <= 0) && (nFeeDelta <= 0) && (feeRate < CTransaction::minRelayTxFee) && (nBlockSize + nTxSize >= nBlockMinSize)) if (fSortedByFee && (dPriorityDelta <= 0) && (nFeeDelta <= 0) && (feeRate < ::minRelayTxFee) && (nBlockSize + nTxSize >= nBlockMinSize))
continue; continue;
// Prioritise by fee once past the priority size or we run out of high-priority // Prioritise by fee once past the priority size or we run out of high-priority

60
src/qt/coincontroldialog.cpp

@ -16,6 +16,8 @@
#include "main.h" #include "main.h"
#include "wallet.h" #include "wallet.h"
#include <boost/assign/list_of.hpp> // for 'map_list_of()'
#include <QApplication> #include <QApplication>
#include <QCheckBox> #include <QCheckBox>
#include <QCursor> #include <QCursor>
@ -400,23 +402,24 @@ void CoinControlDialog::viewItemChanged(QTreeWidgetItem* item, int column)
} }
// return human readable label for priority number // return human readable label for priority number
QString CoinControlDialog::getPriorityLabel(double dPriority) QString CoinControlDialog::getPriorityLabel(const CTxMemPool& pool, double dPriority)
{ {
if (AllowFree(dPriority)) // at least medium // confirmations -> textual description
typedef std::map<unsigned int, QString> PriorityDescription;
const static PriorityDescription priorityDescriptions = boost::assign::map_list_of
(1, tr("highest"))(2, tr("higher"))(3, tr("high"))
(5, tr("medium-high"))(6, tr("medium"))
(10, tr("low-medium"))(15, tr("low"))
(20, tr("lower"));
BOOST_FOREACH(const PriorityDescription::value_type& i, priorityDescriptions)
{ {
if (AllowFree(dPriority / 1000000)) return tr("highest"); double p = mempool.estimatePriority(i.first);
else if (AllowFree(dPriority / 100000)) return tr("higher"); if (p > 0 && dPriority >= p) return i.second;
else if (AllowFree(dPriority / 10000)) return tr("high");
else if (AllowFree(dPriority / 1000)) return tr("medium-high");
else return tr("medium");
}
else
{
if (AllowFree(dPriority * 10)) return tr("low-medium");
else if (AllowFree(dPriority * 100)) return tr("low");
else if (AllowFree(dPriority * 1000)) return tr("lower");
else return tr("lowest");
} }
// Note: if mempool hasn't accumulated enough history (estimatePriority
// returns -1) we're conservative and classify as "lowest"
return tr("lowest");
} }
// shows count of locked unspent outputs // shows count of locked unspent outputs
@ -449,7 +452,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
{ {
CTxOut txout(amount, (CScript)vector<unsigned char>(24, 0)); CTxOut txout(amount, (CScript)vector<unsigned char>(24, 0));
txDummy.vout.push_back(txout); txDummy.vout.push_back(txout);
if (txout.IsDust(CTransaction::minRelayTxFee)) if (txout.IsDust(::minRelayTxFee))
fDust = true; fDust = true;
} }
} }
@ -518,15 +521,20 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
// Priority // Priority
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) 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)
sPriorityLabel = CoinControlDialog::getPriorityLabel(dPriority); sPriorityLabel = CoinControlDialog::getPriorityLabel(mempool, dPriority);
// Fee // Fee
int64_t nFee = payTxFee.GetFee(max((unsigned int)1000, nBytes)); int64_t nFee = payTxFee.GetFee(max((unsigned int)1000, nBytes));
// Min Fee // Min Fee
int64_t nMinFee = GetMinFee(txDummy, nBytes, AllowFree(dPriority), GMF_SEND); nPayFee = CWallet::GetMinimumFee(nBytes, nTxConfirmTarget, mempool);
double dPriorityNeeded = mempool.estimatePriority(nTxConfirmTarget);
if (dPriorityNeeded <= 0) // Not enough mempool history: never send free
dPriorityNeeded = std::numeric_limits<double>::max();
nPayFee = max(nFee, nMinFee); if (nBytes <= MAX_FREE_TRANSACTION_CREATE_SIZE && dPriority >= dPriorityNeeded)
nPayFee = 0;
if (nPayAmount > 0) if (nPayAmount > 0)
{ {
@ -536,7 +544,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
if (nChange > 0 && nChange < CENT) if (nChange > 0 && nChange < CENT)
{ {
CTxOut txout(nChange, (CScript)vector<unsigned char>(24, 0)); CTxOut txout(nChange, (CScript)vector<unsigned char>(24, 0));
if (txout.IsDust(CTransaction::minRelayTxFee)) if (txout.IsDust(::minRelayTxFee))
{ {
nPayFee += nChange; nPayFee += nChange;
nChange = 0; nChange = 0;
@ -591,23 +599,23 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
} }
// turn labels "red" // turn labels "red"
l5->setStyleSheet((nBytes >= 1000) ? "color:red;" : ""); // Bytes >= 1000 l5->setStyleSheet((nBytes >= MAX_FREE_TRANSACTION_CREATE_SIZE) ? "color:red;" : "");// Bytes >= 1000
l6->setStyleSheet((dPriority > 0 && !AllowFree(dPriority)) ? "color:red;" : ""); // Priority < "medium" l6->setStyleSheet((dPriority > 0 && !AllowFree(dPriority)) ? "color:red;" : ""); // Priority < "medium"
l7->setStyleSheet((fDust) ? "color:red;" : ""); // Dust = "yes" l7->setStyleSheet((fDust) ? "color:red;" : ""); // Dust = "yes"
// tool tips // tool tips
QString toolTip1 = tr("This label turns red, if the transaction size is greater than 1000 bytes.") + "<br /><br />"; QString toolTip1 = tr("This label turns red, if the transaction size is greater than 1000 bytes.") + "<br /><br />";
toolTip1 += tr("This means a fee of at least %1 per kB is required.").arg(BitcoinUnits::formatWithUnit(nDisplayUnit, CTransaction::minTxFee.GetFeePerK())) + "<br /><br />"; toolTip1 += tr("This means a fee of at least %1 per kB is required.").arg(BitcoinUnits::formatWithUnit(nDisplayUnit, CWallet::minTxFee.GetFeePerK())) + "<br /><br />";
toolTip1 += tr("Can vary +/- 1 byte per input."); toolTip1 += tr("Can vary +/- 1 byte per input.");
QString toolTip2 = tr("Transactions with higher priority are more likely to get included into a block.") + "<br /><br />"; QString toolTip2 = tr("Transactions with higher priority are more likely to get included into a block.") + "<br /><br />";
toolTip2 += tr("This label turns red, if the priority is smaller than \"medium\".") + "<br /><br />"; toolTip2 += tr("This label turns red, if the priority is smaller than \"medium\".") + "<br /><br />";
toolTip2 += tr("This means a fee of at least %1 per kB is required.").arg(BitcoinUnits::formatWithUnit(nDisplayUnit, CTransaction::minTxFee.GetFeePerK())); toolTip2 += tr("This means a fee of at least %1 per kB is required.").arg(BitcoinUnits::formatWithUnit(nDisplayUnit, CWallet::minTxFee.GetFeePerK()));
QString toolTip3 = tr("This label turns red, if any recipient receives an amount smaller than %1.").arg(BitcoinUnits::formatWithUnit(nDisplayUnit, CTransaction::minRelayTxFee.GetFee(546))); QString toolTip3 = tr("This label turns red, if any recipient receives an amount smaller than %1.").arg(BitcoinUnits::formatWithUnit(nDisplayUnit, ::minRelayTxFee.GetFee(546)));
// how many satoshis the estimated fee can vary per byte we guess wrong // how many satoshis the estimated fee can vary per byte we guess wrong
double dFeeVary = (double)std::max(CTransaction::minTxFee.GetFeePerK(), payTxFee.GetFeePerK()) / 1000; double dFeeVary = (double)std::max(CWallet::minTxFee.GetFeePerK(), payTxFee.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);
l3->setToolTip(toolTip4); l3->setToolTip(toolTip4);
@ -732,7 +740,7 @@ void CoinControlDialog::updateView()
// priority // priority
double dPriority = ((double)out.tx->vout[out.i].nValue / (nInputSize + 78)) * (out.nDepth+1); // 78 = 2 * 34 + 10 double dPriority = ((double)out.tx->vout[out.i].nValue / (nInputSize + 78)) * (out.nDepth+1); // 78 = 2 * 34 + 10
itemOutput->setText(COLUMN_PRIORITY, CoinControlDialog::getPriorityLabel(dPriority)); itemOutput->setText(COLUMN_PRIORITY, CoinControlDialog::getPriorityLabel(mempool, dPriority));
itemOutput->setText(COLUMN_PRIORITY_INT64, strPad(QString::number((int64_t)dPriority), 20, " ")); itemOutput->setText(COLUMN_PRIORITY_INT64, strPad(QString::number((int64_t)dPriority), 20, " "));
dPrioritySum += (double)out.tx->vout[out.i].nValue * (out.nDepth+1); dPrioritySum += (double)out.tx->vout[out.i].nValue * (out.nDepth+1);
nInputSum += nInputSize; nInputSum += nInputSize;
@ -765,7 +773,7 @@ void CoinControlDialog::updateView()
itemWalletAddress->setText(COLUMN_CHECKBOX, "(" + QString::number(nChildren) + ")"); itemWalletAddress->setText(COLUMN_CHECKBOX, "(" + QString::number(nChildren) + ")");
itemWalletAddress->setText(COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, nSum)); itemWalletAddress->setText(COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, nSum));
itemWalletAddress->setText(COLUMN_AMOUNT_INT64, strPad(QString::number(nSum), 15, " ")); itemWalletAddress->setText(COLUMN_AMOUNT_INT64, strPad(QString::number(nSum), 15, " "));
itemWalletAddress->setText(COLUMN_PRIORITY, CoinControlDialog::getPriorityLabel(dPrioritySum)); itemWalletAddress->setText(COLUMN_PRIORITY, CoinControlDialog::getPriorityLabel(mempool, dPrioritySum));
itemWalletAddress->setText(COLUMN_PRIORITY_INT64, strPad(QString::number((int64_t)dPrioritySum), 20, " ")); itemWalletAddress->setText(COLUMN_PRIORITY_INT64, strPad(QString::number((int64_t)dPrioritySum), 20, " "));
} }
} }

3
src/qt/coincontroldialog.h

@ -19,6 +19,7 @@ namespace Ui {
} }
class WalletModel; class WalletModel;
class CCoinControl; class CCoinControl;
class CTxMemPool;
class CoinControlDialog : public QDialog class CoinControlDialog : public QDialog
{ {
@ -32,7 +33,7 @@ public:
// static because also called from sendcoinsdialog // static because also called from sendcoinsdialog
static void updateLabels(WalletModel*, QDialog*); static void updateLabels(WalletModel*, QDialog*);
static QString getPriorityLabel(double); static QString getPriorityLabel(const CTxMemPool& pool, double);
static QList<qint64> payAmounts; static QList<qint64> payAmounts;
static CCoinControl *coinControl; static CCoinControl *coinControl;

3
src/qt/guiutil.cpp

@ -11,6 +11,7 @@
#include "core.h" #include "core.h"
#include "init.h" #include "init.h"
#include "main.h"
#include "protocol.h" #include "protocol.h"
#include "util.h" #include "util.h"
@ -212,7 +213,7 @@ bool isDust(const QString& address, qint64 amount)
CTxDestination dest = CBitcoinAddress(address.toStdString()).Get(); CTxDestination dest = CBitcoinAddress(address.toStdString()).Get();
CScript script; script.SetDestination(dest); CScript script; script.SetDestination(dest);
CTxOut txOut(amount, script); CTxOut txOut(amount, script);
return txOut.IsDust(CTransaction::minRelayTxFee); return txOut.IsDust(::minRelayTxFee);
} }
QString HtmlEscape(const QString& str, bool fMultiLine) QString HtmlEscape(const QString& str, bool fMultiLine)

9
src/qt/optionsdialog.cpp

@ -14,7 +14,10 @@
#include "monitoreddatamapper.h" #include "monitoreddatamapper.h"
#include "optionsmodel.h" #include "optionsmodel.h"
#include "main.h" // for CTransaction::minTxFee and MAX_SCRIPTCHECK_THREADS #include "main.h" // for MAX_SCRIPTCHECK_THREADS
#ifdef ENABLE_WALLET
#include "wallet.h" // for CWallet::minTxFee
#endif
#include "netbase.h" #include "netbase.h"
#include "txdb.h" // for -dbcache defaults #include "txdb.h" // for -dbcache defaults
@ -101,7 +104,9 @@ OptionsDialog::OptionsDialog(QWidget *parent) :
#endif #endif
ui->unit->setModel(new BitcoinUnits(this)); ui->unit->setModel(new BitcoinUnits(this));
ui->transactionFee->setSingleStep(CTransaction::minTxFee.GetFeePerK()); #ifdef ENABLE_WALLET
ui->transactionFee->setSingleStep(CWallet::minTxFee.GetFeePerK());
#endif
/* Widget-to-option mapper */ /* Widget-to-option mapper */
mapper = new MonitoredDataMapper(this); mapper = new MonitoredDataMapper(this);

2
src/qt/paymentserver.cpp

@ -551,7 +551,7 @@ bool PaymentServer::processPaymentRequest(PaymentRequestPlus& request, SendCoins
// Extract and check amounts // Extract and check amounts
CTxOut txOut(sendingTo.second, sendingTo.first); CTxOut txOut(sendingTo.second, sendingTo.first);
if (txOut.IsDust(CTransaction::minRelayTxFee)) { if (txOut.IsDust(::minRelayTxFee)) {
emit message(tr("Payment request error"), tr("Requested payment amount of %1 is too small (considered dust).") emit message(tr("Payment request error"), tr("Requested payment amount of %1 is too small (considered dust).")
.arg(BitcoinUnits::formatWithUnit(optionsModel->getDisplayUnit(), sendingTo.second)), .arg(BitcoinUnits::formatWithUnit(optionsModel->getDisplayUnit(), sendingTo.second)),
CClientUIInterface::MSG_ERROR); CClientUIInterface::MSG_ERROR);

2
src/rpcmisc.cpp

@ -84,7 +84,7 @@ Value getinfo(const Array& params, bool fHelp)
obj.push_back(Pair("unlocked_until", nWalletUnlockTime)); obj.push_back(Pair("unlocked_until", nWalletUnlockTime));
obj.push_back(Pair("paytxfee", ValueFromAmount(payTxFee.GetFeePerK()))); obj.push_back(Pair("paytxfee", ValueFromAmount(payTxFee.GetFeePerK())));
#endif #endif
obj.push_back(Pair("relayfee", ValueFromAmount(CTransaction::minRelayTxFee.GetFeePerK()))); obj.push_back(Pair("relayfee", ValueFromAmount(::minRelayTxFee.GetFeePerK())));
obj.push_back(Pair("errors", GetWarnings("statusbar"))); obj.push_back(Pair("errors", GetWarnings("statusbar")));
return obj; return obj;
} }

2
src/rpcnet.cpp

@ -372,7 +372,7 @@ Value getnetworkinfo(const Array& params, bool fHelp)
obj.push_back(Pair("timeoffset", GetTimeOffset())); obj.push_back(Pair("timeoffset", GetTimeOffset()));
obj.push_back(Pair("connections", (int)vNodes.size())); obj.push_back(Pair("connections", (int)vNodes.size()));
obj.push_back(Pair("proxy", (proxy.first.IsValid() ? proxy.first.ToStringIPPort() : string()))); obj.push_back(Pair("proxy", (proxy.first.IsValid() ? proxy.first.ToStringIPPort() : string())));
obj.push_back(Pair("relayfee", ValueFromAmount(CTransaction::minRelayTxFee.GetFeePerK()))); obj.push_back(Pair("relayfee", ValueFromAmount(::minRelayTxFee.GetFeePerK())));
Array localAddresses; Array localAddresses;
{ {
LOCK(cs_mapLocalHost); LOCK(cs_mapLocalHost);

35
src/txmempool.cpp

@ -80,13 +80,13 @@ public:
// Used as belt-and-suspenders check when reading to detect // Used as belt-and-suspenders check when reading to detect
// file corruption // file corruption
bool AreSane(const std::vector<CFeeRate>& vecFee) bool AreSane(const std::vector<CFeeRate>& vecFee, const CFeeRate& minRelayFee)
{ {
BOOST_FOREACH(CFeeRate fee, vecFee) BOOST_FOREACH(CFeeRate fee, vecFee)
{ {
if (fee < CFeeRate(0)) if (fee < CFeeRate(0))
return false; return false;
if (fee.GetFee(1000) > CTransaction::minRelayTxFee.GetFee(1000) * 10000) if (fee.GetFeePerK() > minRelayFee.GetFeePerK() * 10000)
return false; return false;
} }
return true; return true;
@ -109,10 +109,10 @@ public:
fileout << vecPriority; fileout << vecPriority;
} }
void Read(CAutoFile& filein) { void Read(CAutoFile& filein, const CFeeRate& minRelayFee) {
std::vector<CFeeRate> vecFee; std::vector<CFeeRate> vecFee;
filein >> vecFee; filein >> vecFee;
if (AreSane(vecFee)) if (AreSane(vecFee, minRelayFee))
feeSamples.insert(feeSamples.end(), vecFee.begin(), vecFee.end()); feeSamples.insert(feeSamples.end(), vecFee.begin(), vecFee.end());
else else
throw runtime_error("Corrupt fee value in estimates file."); throw runtime_error("Corrupt fee value in estimates file.");
@ -141,7 +141,7 @@ private:
// nBlocksAgo is 0 based, i.e. transactions that confirmed in the highest seen block are // nBlocksAgo is 0 based, i.e. transactions that confirmed in the highest seen block are
// nBlocksAgo == 0, transactions in the block before that are nBlocksAgo == 1 etc. // nBlocksAgo == 0, transactions in the block before that are nBlocksAgo == 1 etc.
void seenTxConfirm(CFeeRate feeRate, double dPriority, int nBlocksAgo) void seenTxConfirm(const CFeeRate& feeRate, const CFeeRate& minRelayFee, double dPriority, int nBlocksAgo)
{ {
// Last entry records "everything else". // Last entry records "everything else".
int nBlocksTruncated = min(nBlocksAgo, (int) history.size() - 1); int nBlocksTruncated = min(nBlocksAgo, (int) history.size() - 1);
@ -149,7 +149,7 @@ private:
// We need to guess why the transaction was included in a block-- either // We need to guess why the transaction was included in a block-- either
// because it is high-priority or because it has sufficient fees. // because it is high-priority or because it has sufficient fees.
bool sufficientFee = (feeRate > CTransaction::minRelayTxFee); bool sufficientFee = (feeRate > minRelayFee);
bool sufficientPriority = AllowFree(dPriority); bool sufficientPriority = AllowFree(dPriority);
const char* assignedTo = "unassigned"; const char* assignedTo = "unassigned";
if (sufficientFee && !sufficientPriority) if (sufficientFee && !sufficientPriority)
@ -177,7 +177,7 @@ public:
history.resize(nEntries); history.resize(nEntries);
} }
void seenBlock(const std::vector<CTxMemPoolEntry>& entries, int nBlockHeight) void seenBlock(const std::vector<CTxMemPoolEntry>& entries, int nBlockHeight, const CFeeRate minRelayFee)
{ {
if (nBlockHeight <= nBestSeenHeight) if (nBlockHeight <= nBestSeenHeight)
{ {
@ -222,7 +222,7 @@ public:
// Fees are stored and reported as BTC-per-kb: // Fees are stored and reported as BTC-per-kb:
CFeeRate feeRate(entry->GetFee(), entry->GetTxSize()); CFeeRate feeRate(entry->GetFee(), entry->GetTxSize());
double dPriority = entry->GetPriority(entry->GetHeight()); // Want priority when it went IN double dPriority = entry->GetPriority(entry->GetHeight()); // Want priority when it went IN
seenTxConfirm(feeRate, dPriority, i); seenTxConfirm(feeRate, minRelayFee, dPriority, i);
} }
} }
for (size_t i = 0; i < history.size(); i++) { for (size_t i = 0; i < history.size(); i++) {
@ -251,8 +251,13 @@ public:
std::sort(sortedFeeSamples.begin(), sortedFeeSamples.end(), std::sort(sortedFeeSamples.begin(), sortedFeeSamples.end(),
std::greater<CFeeRate>()); std::greater<CFeeRate>());
} }
if (sortedFeeSamples.size() == 0) if (sortedFeeSamples.size() < 11)
{
// Eleven is Gavin's Favorite Number
// ... but we also take a maximum of 10 samples per block so eleven means
// we're getting samples from at least two different blocks
return CFeeRate(0); return CFeeRate(0);
}
int nBucketSize = history.at(nBlocksToConfirm).FeeSamples(); int nBucketSize = history.at(nBlocksToConfirm).FeeSamples();
@ -281,7 +286,7 @@ public:
std::sort(sortedPrioritySamples.begin(), sortedPrioritySamples.end(), std::sort(sortedPrioritySamples.begin(), sortedPrioritySamples.end(),
std::greater<double>()); std::greater<double>());
} }
if (sortedPrioritySamples.size() == 0) if (sortedPrioritySamples.size() < 11)
return -1.0; return -1.0;
int nBucketSize = history.at(nBlocksToConfirm).PrioritySamples(); int nBucketSize = history.at(nBlocksToConfirm).PrioritySamples();
@ -308,7 +313,7 @@ public:
} }
} }
void Read(CAutoFile& filein) void Read(CAutoFile& filein, const CFeeRate& minRelayFee)
{ {
filein >> nBestSeenHeight; filein >> nBestSeenHeight;
size_t numEntries; size_t numEntries;
@ -317,14 +322,14 @@ public:
for (size_t i = 0; i < numEntries; i++) for (size_t i = 0; i < numEntries; i++)
{ {
CBlockAverage entry; CBlockAverage entry;
entry.Read(filein); entry.Read(filein, minRelayFee);
history.push_back(entry); history.push_back(entry);
} }
} }
}; };
CTxMemPool::CTxMemPool() CTxMemPool::CTxMemPool(const CFeeRate& _minRelayFee) : minRelayFee(_minRelayFee)
{ {
// Sanity checks off by default for performance, because otherwise // Sanity checks off by default for performance, because otherwise
// accepting transactions becomes O(N^2) where N is the number // accepting transactions becomes O(N^2) where N is the number
@ -440,7 +445,7 @@ void CTxMemPool::removeForBlock(const std::vector<CTransaction>& vtx, unsigned i
if (mapTx.count(hash)) if (mapTx.count(hash))
entries.push_back(mapTx[hash]); entries.push_back(mapTx[hash]);
} }
minerPolicyEstimator->seenBlock(entries, nBlockHeight); minerPolicyEstimator->seenBlock(entries, nBlockHeight, minRelayFee);
BOOST_FOREACH(const CTransaction& tx, vtx) BOOST_FOREACH(const CTransaction& tx, vtx)
{ {
std::list<CTransaction> dummy; std::list<CTransaction> dummy;
@ -555,7 +560,7 @@ CTxMemPool::ReadFeeEstimates(CAutoFile& filein)
return error("CTxMemPool::ReadFeeEstimates() : up-version (%d) fee estimate file", nVersionRequired); return error("CTxMemPool::ReadFeeEstimates() : up-version (%d) fee estimate file", nVersionRequired);
LOCK(cs); LOCK(cs);
minerPolicyEstimator->Read(filein); minerPolicyEstimator->Read(filein, minRelayFee);
} }
catch (std::exception &e) { catch (std::exception &e) {
LogPrintf("CTxMemPool::ReadFeeEstimates() : unable to read policy estimator data (non-fatal)"); LogPrintf("CTxMemPool::ReadFeeEstimates() : unable to read policy estimator data (non-fatal)");

4
src/txmempool.h

@ -67,13 +67,15 @@ private:
unsigned int nTransactionsUpdated; unsigned int nTransactionsUpdated;
CMinerPolicyEstimator* minerPolicyEstimator; CMinerPolicyEstimator* minerPolicyEstimator;
CFeeRate minRelayFee; // Passed to constructor to avoid dependency on main
public: public:
mutable CCriticalSection cs; mutable CCriticalSection cs;
std::map<uint256, CTxMemPoolEntry> mapTx; std::map<uint256, CTxMemPoolEntry> mapTx;
std::map<COutPoint, CInPoint> mapNextTx; std::map<COutPoint, CInPoint> mapNextTx;
std::map<uint256, std::pair<double, int64_t> > mapDeltas; std::map<uint256, std::pair<double, int64_t> > mapDeltas;
CTxMemPool(); CTxMemPool(const CFeeRate& _minRelayFee);
~CTxMemPool(); ~CTxMemPool();
/* /*

51
src/wallet.cpp

@ -18,8 +18,12 @@ using namespace std;
// Settings // Settings
CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE); CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE);
unsigned int nTxConfirmTarget = 1;
bool bSpendZeroConfChange = true; bool bSpendZeroConfChange = true;
/** Fees smaller than this (in satoshi) are considered zero fee (for transaction creation) */
CFeeRate CWallet::minTxFee = CFeeRate(10000); // Override with -mintxfee
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
// //
// mapWallet // mapWallet
@ -1273,6 +1277,7 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend,
return false; return false;
} }
wtxNew.fTimeReceivedIsTxTime = true;
wtxNew.BindWallet(this); wtxNew.BindWallet(this);
CMutableTransaction txNew; CMutableTransaction txNew;
@ -1292,7 +1297,7 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend,
BOOST_FOREACH (const PAIRTYPE(CScript, int64_t)& s, vecSend) BOOST_FOREACH (const PAIRTYPE(CScript, int64_t)& s, vecSend)
{ {
CTxOut txout(s.second, s.first); CTxOut txout(s.second, s.first);
if (txout.IsDust(CTransaction::minRelayTxFee)) if (txout.IsDust(::minRelayTxFee))
{ {
strFailReason = _("Transaction amount too small"); strFailReason = _("Transaction amount too small");
return false; return false;
@ -1353,7 +1358,7 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend,
// Never create dust outputs; if we would, just // Never create dust outputs; if we would, just
// add the dust to the fee. // add the dust to the fee.
if (newTxOut.IsDust(CTransaction::minRelayTxFee)) if (newTxOut.IsDust(::minRelayTxFee))
{ {
nFeeRet += nChange; nFeeRet += nChange;
reservekey.ReturnKey(); reservekey.ReturnKey();
@ -1393,19 +1398,31 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend,
} }
dPriority = wtxNew.ComputePriority(dPriority, nBytes); dPriority = wtxNew.ComputePriority(dPriority, nBytes);
// Check that enough fee is included int64_t nFeeNeeded = GetMinimumFee(nBytes, nTxConfirmTarget, mempool);
int64_t nPayFee = payTxFee.GetFee(nBytes);
bool fAllowFree = AllowFree(dPriority); if (nFeeRet >= nFeeNeeded)
int64_t nMinFee = GetMinFee(wtxNew, nBytes, fAllowFree, GMF_SEND); break; // Done, enough fee included.
if (nFeeRet < max(nPayFee, nMinFee))
// Too big to send for free? Include more fee and try again:
if (nBytes > MAX_FREE_TRANSACTION_CREATE_SIZE)
{ {
nFeeRet = max(nPayFee, nMinFee); nFeeRet = nFeeNeeded;
continue; continue;
} }
wtxNew.fTimeReceivedIsTxTime = true; // Not enough fee: enough priority?
double dPriorityNeeded = mempool.estimatePriority(nTxConfirmTarget);
// Not enough mempool history to estimate: use hard-coded AllowFree.
if (dPriorityNeeded <= 0 && AllowFree(dPriority))
break;
// Small enough, and priority high enough, to send for free
if (dPriority >= dPriorityNeeded)
break;
break; // Include more fee and try again.
nFeeRet = nFeeNeeded;
continue;
} }
} }
} }
@ -1513,6 +1530,20 @@ string CWallet::SendMoneyToDestination(const CTxDestination& address, int64_t nV
return SendMoney(scriptPubKey, nValue, wtxNew); return SendMoney(scriptPubKey, nValue, wtxNew);
} }
int64_t CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool)
{
// payTxFee is user-set "I want to pay this much"
int64_t nFeeNeeded = payTxFee.GetFee(nTxBytes);
// User didn't set: use -txconfirmtarget to estimate...
if (nFeeNeeded == 0)
nFeeNeeded = pool.estimateFee(nConfirmTarget).GetFee(nTxBytes);
// ... unless we don't have enough mempool data, in which case fall
// back to a hard-coded fee
if (nFeeNeeded == 0)
nFeeNeeded = minTxFee.GetFee(nTxBytes);
return nFeeNeeded;
}

6
src/wallet.h

@ -25,12 +25,15 @@
// Settings // Settings
extern CFeeRate payTxFee; extern CFeeRate payTxFee;
extern unsigned int nTxConfirmTarget;
extern bool bSpendZeroConfChange; extern bool bSpendZeroConfChange;
// -paytxfee default // -paytxfee default
static const int64_t DEFAULT_TRANSACTION_FEE = 0; static const int64_t DEFAULT_TRANSACTION_FEE = 0;
// -paytxfee will warn if called with a higher fee than this amount (in satoshis) per KB // -paytxfee will warn if called with a higher fee than this amount (in satoshis) per KB
static const int nHighTransactionFeeWarning = 0.01 * COIN; static const int nHighTransactionFeeWarning = 0.01 * COIN;
// Largest (in bytes) free transaction we're willing to create
static const unsigned int MAX_FREE_TRANSACTION_CREATE_SIZE = 1000;
class CAccountingEntry; class CAccountingEntry;
class CCoinControl; class CCoinControl;
@ -265,6 +268,9 @@ public:
std::string SendMoney(CScript scriptPubKey, int64_t nValue, CWalletTx& wtxNew); std::string SendMoney(CScript scriptPubKey, int64_t nValue, CWalletTx& wtxNew);
std::string SendMoneyToDestination(const CTxDestination &address, int64_t nValue, CWalletTx& wtxNew); std::string SendMoneyToDestination(const CTxDestination &address, int64_t nValue, CWalletTx& wtxNew);
static CFeeRate minTxFee;
static int64_t GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool);
bool NewKeyPool(); bool NewKeyPool();
bool TopUpKeyPool(unsigned int kpSize = 0); bool TopUpKeyPool(unsigned int kpSize = 0);
void ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool); void ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool);

Loading…
Cancel
Save