diff options
Diffstat (limited to 'src')
34 files changed, 515 insertions, 211 deletions
diff --git a/src/core.cpp b/src/core.cpp index ca28624529..47f3b2a015 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -80,7 +80,7 @@ CFeeRate::CFeeRate(int64_t nFeePaid, size_t nSize) nSatoshisPerK = 0; } -int64_t CFeeRate::GetFee(size_t nSize) +int64_t CFeeRate::GetFee(size_t nSize) const { return nSatoshisPerK*nSize / 1000; } diff --git a/src/core.h b/src/core.h index 8606831575..0387336c98 100644 --- a/src/core.h +++ b/src/core.h @@ -125,13 +125,14 @@ public: CFeeRate(int64_t nFeePaid, size_t nSize); CFeeRate(const CFeeRate& other) { nSatoshisPerK = other.nSatoshisPerK; } - int64_t GetFee(size_t size); // unit returned is satoshis - int64_t GetFeePerK() { return GetFee(1000); } // satoshis-per-1000-bytes + int64_t GetFee(size_t size) const; // unit returned is satoshis + 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; } std::string ToString() const; IMPLEMENT_SERIALIZE( READWRITE(nSatoshisPerK); ) @@ -216,8 +217,6 @@ private: void UpdateHash() const; public: - static CFeeRate minTxFee; - static CFeeRate minRelayTxFee; static const int CURRENT_VERSION=1; // The local variables are made const to prevent unintended modification diff --git a/src/init.cpp b/src/init.cpp index da13218a94..c514facbbe 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -253,14 +253,16 @@ std::string HelpMessage(HelpMessageMode mode) #ifdef ENABLE_WALLET strUsage += "\n" + _("Wallet options:") + "\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 += " -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 += " -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 += " -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 += " -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 += " " + _("(default: 1, 1 = keep tx meta data e.g. account owner and payment request information, 2 = drop tx meta data)") + "\n"; #endif @@ -288,14 +290,14 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += " -gen " + _("Generate coins (default: 0)") + "\n"; strUsage += " -genproclimit=<n> " + _("Set the processor limit for when generation is on (-1 = unlimited, default: -1)") + "\n"; strUsage += " -help-debug " + _("Show all debugging options (usage: --help -help-debug)") + "\n"; + strUsage += " -logips " + _("Include IP addresses in debug output (default: 0)") + "\n"; strUsage += " -logtimestamps " + _("Prepend debug output with timestamp (default: 1)") + "\n"; if (GetBoolArg("-help-debug", false)) { 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 += " -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(CTransaction::minRelayTxFee.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 += " -printtoconsole " + _("Send trace/debug info to console instead of debug.log file") + "\n"; if (GetBoolArg("-help-debug", false)) { @@ -323,7 +325,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += " -rpcuser=<user> " + _("Username for JSON-RPC connections") + "\n"; strUsage += " -rpcpassword=<pw> " + _("Password for JSON-RPC connections") + "\n"; strUsage += " -rpcport=<port> " + _("Listen for JSON-RPC connections on <port> (default: 8332 or testnet: 18332)") + "\n"; - strUsage += " -rpcallowip=<ip> " + _("Allow JSON-RPC connections from specified IP address. This option can be specified multiple times") + "\n"; + strUsage += " -rpcallowip=<ip> " + _("Allow JSON-RPC connections from specified source. Valid for <ip> are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24). This option can be specified multiple times") + "\n"; strUsage += " -rpcthreads=<n> " + _("Set the number of threads to service RPC calls (default: 4)") + "\n"; strUsage += "\n" + _("RPC SSL options: (see the Bitcoin Wiki for SSL setup instructions)") + "\n"; @@ -585,6 +587,7 @@ bool AppInit2(boost::thread_group& threadGroup) fServer = GetBoolArg("-server", false); fPrintToConsole = GetBoolArg("-printtoconsole", false); fLogTimestamps = GetBoolArg("-logtimestamps", true); + fLogIPs = GetBoolArg("-logips", false); setvbuf(stdout, NULL, _IOLBF, 0); #ifdef ENABLE_WALLET bool fDisableWallet = GetBoolArg("-disablewallet", false); @@ -609,24 +612,24 @@ bool AppInit2(boost::thread_group& threadGroup) // a transaction spammer can cheaply fill blocks using // 1-satoshi-fee transactions. It should be set above the real // 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")) { int64_t n = 0; if (ParseMoney(mapArgs["-minrelaytxfee"], n) && n > 0) - CTransaction::minRelayTxFee = CFeeRate(n); + ::minRelayTxFee = CFeeRate(n); else return InitError(strprintf(_("Invalid amount for -minrelaytxfee=<amount>: '%s'"), mapArgs["-minrelaytxfee"])); } #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")) { int64_t nFeePerK = 0; @@ -635,7 +638,13 @@ bool AppInit2(boost::thread_group& threadGroup) if (nFeePerK > nHighTransactionFeeWarning) InitWarning(_("Warning: -paytxfee is set very high! This is the transaction fee you will pay if you send a transaction.")); 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); std::string strWalletFile = GetArg("-wallet", "wallet.dat"); @@ -1176,7 +1185,7 @@ bool AppInit2(boost::thread_group& threadGroup) LogPrintf("mapAddressBook.size() = %u\n", pwalletMain ? pwalletMain->mapAddressBook.size() : 0); #endif - RegisterInternalSignals(); + InitRespendFilter(); StartNode(threadGroup); if (fServer) StartRPCThreads(); diff --git a/src/main.cpp b/src/main.cpp index 04d9523e26..e08b794184 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -38,8 +38,6 @@ using namespace boost; CCriticalSection cs_main; -CTxMemPool mempool; - map<uint256, CBlockIndex*> mapBlockIndex; CChain chainActive; int64_t nTimeBestReceived = 0; @@ -50,10 +48,10 @@ bool fBenchmark = false; bool fTxIndex = false; 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) */ -CFeeRate CTransaction::minRelayTxFee = CFeeRate(1000); +CFeeRate minRelayTxFee = CFeeRate(1000); + +CTxMemPool mempool(::minRelayTxFee); struct COrphanBlock { uint256 hashBlock; @@ -125,9 +123,14 @@ namespace { } // anon namespace -// Forward reference functions defined here: +// Bloom filter to limit respend relays to one static const unsigned int MAX_DOUBLESPEND_BLOOM = 1000; -static void RelayDoubleSpend(const COutPoint& outPoint, const CTransaction& doubleSpend, bool fInBlock, CBloomFilter& filter); +static CBloomFilter doubleSpendFilter; +void InitRespendFilter() { + seed_insecure_rand(); + doubleSpendFilter = CBloomFilter(MAX_DOUBLESPEND_BLOOM, 0.01, insecure_rand(), BLOOM_UPDATE_NONE); +} + ////////////////////////////////////////////////////////////////////////////// // @@ -151,24 +154,10 @@ struct CMainSignals { boost::signals2::signal<void (const uint256 &)> Inventory; // Tells listeners to broadcast their data. boost::signals2::signal<void ()> Broadcast; - // Notifies listeners of detection of a double-spent transaction. Arguments are outpoint that is - // double-spent, first transaction seen, double-spend transaction, and whether the second double-spend - // transaction was first seen in a block. - // Note: only notifies if the previous transaction is in the memory pool; if previous transction was in a block, - // then the double-spend simply fails when we try to lookup the inputs in the current UTXO set. - boost::signals2::signal<void (const COutPoint&, const CTransaction&, bool)> DetectedDoubleSpend; } g_signals; } // anon namespace -void RegisterInternalSignals() { - static CBloomFilter doubleSpendFilter; - seed_insecure_rand(); - doubleSpendFilter = CBloomFilter(MAX_DOUBLESPEND_BLOOM, 0.01, insecure_rand(), BLOOM_UPDATE_NONE); - - g_signals.DetectedDoubleSpend.connect(boost::bind(RelayDoubleSpend, _1, _2, _3, doubleSpendFilter)); -} - void RegisterWallet(CWalletInterface* pwalletIn) { g_signals.SyncTransaction.connect(boost::bind(&CWalletInterface::SyncTransaction, pwalletIn, _1, _2)); @@ -617,7 +606,7 @@ bool IsStandardTx(const CTransaction& tx, string& reason) } if (whichType == TX_NULL_DATA) nDataOut++; - else if (txout.IsDust(CTransaction::minRelayTxFee)) { + else if (txout.IsDust(::minRelayTxFee)) { reason = "dust"; return false; } @@ -858,7 +847,7 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state) 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); @@ -870,10 +859,7 @@ int64_t GetMinFee(const CTransaction& tx, unsigned int nBytes, bool fAllowFree, return 0; } - // Base fee is either minTxFee or minRelayTxFee - CFeeRate baseFeeRate = (mode == GMF_RELAY) ? tx.minRelayTxFee : tx.minTxFee; - - int64_t nMinFee = baseFeeRate.GetFee(nBytes); + int64_t nMinFee = ::minRelayTxFee.GetFee(nBytes); if (fAllowFree) { @@ -881,9 +867,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 // 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. - // * If we are creating a transaction we allow transactions up to 1,000 bytes - // 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))) + if (nBytes < (DEFAULT_BLOCK_PRIORITY_SIZE - 1000)) nMinFee = 0; } @@ -908,6 +892,45 @@ bool RateLimitExceeded(double& dCount, int64_t& nLastTime, int64_t nLimit, unsig return false; } +static bool RelayableRespend(const COutPoint& outPoint, const CTransaction& doubleSpend, bool fInBlock, CBloomFilter& filter) +{ + // Relaying double-spend attempts to our peers lets them detect when + // somebody might be trying to cheat them. However, blindly relaying + // every double-spend across the entire network gives attackers + // a denial-of-service attack: just generate a stream of double-spends + // re-spending the same (limited) set of outpoints owned by the attacker. + // So, we use a bloom filter and only relay (at most) the first double + // spend for each outpoint. False-positives ("we have already relayed") + // are OK, because if the peer doesn't hear about the double-spend + // from us they are very likely to hear about it from another peer, since + // each peer uses a different, randomized bloom filter. + + if (fInBlock || filter.contains(outPoint)) return false; + + // Apply an independent rate limit to double-spend relays + static double dRespendCount; + static int64_t nLastRespendTime; + static int64_t nRespendLimit = GetArg("-limitrespendrelay", 100); + unsigned int nSize = ::GetSerializeSize(doubleSpend, SER_NETWORK, PROTOCOL_VERSION); + + if (RateLimitExceeded(dRespendCount, nLastRespendTime, nRespendLimit, nSize)) + { + LogPrint("mempool", "Double-spend relay rejected by rate limiter\n"); + return false; + } + + LogPrint("mempool", "Rate limit dRespendCount: %g => %g\n", dRespendCount, dRespendCount+nSize); + + // Clear the filter on average every MAX_DOUBLE_SPEND_BLOOM + // insertions + if (insecure_rand()%MAX_DOUBLESPEND_BLOOM == 0) + filter.clear(); + + filter.insert(outPoint); + + return true; +} + bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree, bool* pfMissingInputs, bool fRejectInsaneFee) { @@ -936,6 +959,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa return false; // Check for conflicts with in-memory transactions + bool relayableRespend = false; { LOCK(pool.cs); // protect pool.mapNextTx for (unsigned int i = 0; i < tx.vin.size(); i++) @@ -944,8 +968,9 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa // Does tx conflict with a member of the pool, and is it not equivalent to that member? if (pool.mapNextTx.count(outpoint) && !tx.IsEquivalentTo(*pool.mapNextTx[outpoint].ptx)) { - g_signals.DetectedDoubleSpend(outpoint, tx, false); - return false; + relayableRespend = RelayableRespend(outpoint, tx, false, doubleSpendFilter); + if (!relayableRespend) + return false; } } } @@ -1005,7 +1030,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa unsigned int nSize = entry.GetTxSize(); // 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) return state.DoS(0, error("AcceptToMemoryPool : not enough fees %s, %d < %d", hash.ToString(), nFees, txMinFee), @@ -1014,7 +1039,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa // Continuously rate-limit free (really, very-low-fee)transactions // This mitigates 'penny-flooding' -- sending thousands of free transactions just to // 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 int64_t nLastFreeTime; @@ -1027,10 +1052,10 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa 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", hash.ToString(), - nFees, CTransaction::minRelayTxFee.GetFee(nSize) * 10000); + nFees, ::minRelayTxFee.GetFee(nSize) * 10000); // Check against previous transactions // This is done last to help prevent CPU exhaustion denial-of-service attacks. @@ -1038,55 +1063,21 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa { return error("AcceptToMemoryPool: : ConnectInputs failed %s", hash.ToString()); } - // Store transaction in memory - pool.addUnchecked(hash, entry); - } - - g_signals.SyncTransaction(tx, NULL); - - return true; -} - -static void RelayDoubleSpend(const COutPoint& outPoint, const CTransaction& doubleSpend, bool fInBlock, CBloomFilter& filter) -{ - // Relaying double-spend attempts to our peers lets them detect when - // somebody might be trying to cheat them. However, blindly relaying - // every double-spend across the entire network gives attackers - // a denial-of-service attack: just generate a stream of double-spends - // re-spending the same (limited) set of outpoints owned by the attacker. - // So, we use a bloom filter and only relay (at most) the first double - // spend for each outpoint. False-positives ("we have already relayed") - // are OK, because if the peer doesn't hear about the double-spend - // from us they are very likely to hear about it from another peer, since - // each peer uses a different, randomized bloom filter. - - if (fInBlock || filter.contains(outPoint)) return; - - // Apply an independent rate limit to double-spend relays - static double dRespendCount; - static int64_t nLastRespendTime; - static int64_t nRespendLimit = GetArg("-limitrespendrelay", 100); - unsigned int nSize = ::GetSerializeSize(doubleSpend, SER_NETWORK, PROTOCOL_VERSION); - if (RateLimitExceeded(dRespendCount, nLastRespendTime, nRespendLimit, nSize)) - { - LogPrint("mempool", "Double-spend relay rejected by rate limiter\n"); - return; + if (relayableRespend) + { + RelayTransaction(tx); + } + else + { + // Store transaction in memory + pool.addUnchecked(hash, entry); + } } - LogPrint("mempool", "Rate limit dRespendCount: %g => %g\n", dRespendCount, dRespendCount+nSize); - - // Clear the filter on average every MAX_DOUBLE_SPEND_BLOOM - // insertions - if (insecure_rand()%MAX_DOUBLESPEND_BLOOM == 0) - filter.clear(); - - filter.insert(outPoint); - - RelayTransaction(doubleSpend); + g_signals.SyncTransaction(tx, NULL); - // Share conflict with wallet - g_signals.SyncTransaction(doubleSpend, NULL); + return !relayableRespend; } @@ -1134,10 +1125,10 @@ int CMerkleTx::GetBlocksToMaturity() const } -bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree) +bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree, bool fRejectInsaneFee) { CValidationState state; - return ::AcceptToMemoryPool(mempool, state, *this, fLimitFree, NULL); + return ::AcceptToMemoryPool(mempool, state, *this, fLimitFree, NULL, fRejectInsaneFee); } @@ -3544,10 +3535,10 @@ void static ProcessGetData(CNode* pfrom) } } -bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) +bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, int64_t nTimeReceived) { RandAddSeedPerfmon(); - LogPrint("net", "received: %s (%u bytes)\n", strCommand, vRecv.size()); + LogPrint("net", "received: %s (%u bytes) peer=%d\n", strCommand, vRecv.size(), pfrom->id); if (mapArgs.count("-dropmessagestest") && GetRand(atoi(mapArgs["-dropmessagestest"])) == 0) { LogPrintf("dropmessagestest DROPPING RECV MESSAGE\n"); @@ -3579,7 +3570,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) if (pfrom->nVersion < MIN_PEER_PROTO_VERSION) { // disconnect from peers older than this proto version - LogPrintf("partner %s using obsolete version %i; disconnecting\n", pfrom->addr.ToString(), pfrom->nVersion); + LogPrintf("peer=%d using obsolete version %i; disconnecting\n", pfrom->id, pfrom->nVersion); pfrom->PushMessage("reject", strCommand, REJECT_OBSOLETE, strprintf("Version must be %d or greater", MIN_PEER_PROTO_VERSION)); pfrom->fDisconnect = true; @@ -3660,7 +3651,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) pfrom->fSuccessfullyConnected = true; - LogPrintf("receive version message: %s: version %d, blocks=%d, us=%s, them=%s, peer=%s\n", pfrom->cleanSubVer, pfrom->nVersion, pfrom->nStartingHeight, addrMe.ToString(), addrFrom.ToString(), pfrom->addr.ToString()); + LogPrintf("receive version message: %s: version %d, blocks=%d, us=%s, peer=%d\n", pfrom->cleanSubVer, pfrom->nVersion, pfrom->nStartingHeight, addrMe.ToString(), pfrom->id); AddTimeData(pfrom->addr, nTime); } @@ -3767,7 +3758,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) pfrom->AddInventoryKnown(inv); bool fAlreadyHave = AlreadyHave(inv); - LogPrint("net", " got inventory: %s %s\n", inv.ToString(), fAlreadyHave ? "have" : "new"); + LogPrint("net", "got inv: %s %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom->id); if (!fAlreadyHave) { if (!fImporting && !fReindex) { @@ -3800,10 +3791,10 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) } if (fDebug || (vInv.size() != 1)) - LogPrint("net", "received getdata (%u invsz)\n", vInv.size()); + LogPrint("net", "received getdata (%u invsz) peer=%d\n", vInv.size(), pfrom->id); if ((fDebug && vInv.size() > 0) || (vInv.size() == 1)) - LogPrint("net", "received getdata for: %s\n", vInv[0].ToString()); + LogPrint("net", "received getdata for: %s peer=%d\n", vInv[0].ToString(), pfrom->id); pfrom->vRecvGetData.insert(pfrom->vRecvGetData.end(), vInv.begin(), vInv.end()); ProcessGetData(pfrom); @@ -3825,7 +3816,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) if (pindex) pindex = chainActive.Next(pindex); int nLimit = 500; - LogPrint("net", "getblocks %d to %s limit %d\n", (pindex ? pindex->nHeight : -1), hashStop==uint256(0) ? "end" : hashStop.ToString(), nLimit); + LogPrint("net", "getblocks %d to %s limit %d from peer=%d\n", (pindex ? pindex->nHeight : -1), hashStop==uint256(0) ? "end" : hashStop.ToString(), nLimit, pfrom->id); for (; pindex; pindex = chainActive.Next(pindex)) { if (pindex->GetBlockHash() == hashStop) @@ -3908,8 +3899,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) vEraseQueue.push_back(inv.hash); - LogPrint("mempool", "AcceptToMemoryPool: %s %s : accepted %s (poolsz %u)\n", - pfrom->addr.ToString(), pfrom->cleanSubVer, + LogPrint("mempool", "AcceptToMemoryPool: peer=%d %s : accepted %s (poolsz %u)\n", + pfrom->id, pfrom->cleanSubVer, tx.GetHash().ToString(), mempool.mapTx.size()); @@ -3962,8 +3953,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) int nDoS = 0; if (state.IsInvalid(nDoS)) { - LogPrint("mempool", "%s from %s %s was not accepted into the memory pool: %s\n", tx.GetHash().ToString(), - pfrom->addr.ToString(), pfrom->cleanSubVer, + LogPrint("mempool", "%s from peer=%d %s was not accepted into the memory pool: %s\n", tx.GetHash().ToString(), + pfrom->id, pfrom->cleanSubVer, state.GetRejectReason()); pfrom->PushMessage("reject", strCommand, state.GetRejectCode(), state.GetRejectReason(), inv.hash); @@ -3978,7 +3969,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) CBlock block; vRecv >> block; - LogPrint("net", "received block %s\n", block.GetHash().ToString()); + LogPrint("net", "received block %s peer=%d\n", block.GetHash().ToString(), pfrom->id); // block.print(); CInv inv(MSG_BLOCK, block.GetHash()); @@ -4054,7 +4045,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) else if (strCommand == "pong") { - int64_t pingUsecEnd = GetTimeMicros(); + int64_t pingUsecEnd = nTimeReceived; uint64_t nonce = 0; size_t nAvail = vRecv.in_avail(); bool bPingFinished = false; @@ -4095,8 +4086,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) } if (!(sProblem.empty())) { - LogPrint("net", "pong %s %s: %s, %x expected, %x received, %u bytes\n", - pfrom->addr.ToString(), + LogPrint("net", "pong peer=%d %s: %s, %x expected, %x received, %u bytes\n", + pfrom->id, pfrom->cleanSubVer, sProblem, pfrom->nPingNonceSent, @@ -4305,7 +4296,7 @@ bool ProcessMessages(CNode* pfrom) bool fRet = false; try { - fRet = ProcessMessage(pfrom, strCommand, vRecv); + fRet = ProcessMessage(pfrom, strCommand, vRecv, msg.nTime); boost::this_thread::interruption_point(); } catch (std::ios_base::failure& e) @@ -4336,7 +4327,7 @@ bool ProcessMessages(CNode* pfrom) } if (!fRet) - LogPrintf("ProcessMessage(%s, %u bytes) FAILED\n", strCommand, nMessageSize); + LogPrintf("ProcessMessage(%s, %u bytes) FAILED peer=%d\n", strCommand, nMessageSize, pfrom->id); break; } @@ -4540,7 +4531,7 @@ bool SendMessages(CNode* pto, bool fSendTrickle) uint256 hash = state.vBlocksToDownload.front(); vGetData.push_back(CInv(MSG_BLOCK, hash)); MarkBlockAsInFlight(pto->GetId(), hash); - LogPrint("net", "Requesting block %s from %s\n", hash.ToString(), state.name); + LogPrint("net", "Requesting block %s peer=%d\n", hash.ToString(), pto->id); if (vGetData.size() >= 1000) { pto->PushMessage("getdata", vGetData); @@ -4557,7 +4548,7 @@ bool SendMessages(CNode* pto, bool fSendTrickle) if (!AlreadyHave(inv)) { if (fDebug) - LogPrint("net", "sending getdata: %s\n", inv.ToString()); + LogPrint("net", "Requesting %s peer=%d\n", inv.ToString(), pto->id); vGetData.push_back(inv); if (vGetData.size() >= 1000) { diff --git a/src/main.h b/src/main.h index cc7dea75a1..f6bac889be 100644 --- a/src/main.h +++ b/src/main.h @@ -93,6 +93,7 @@ extern bool fBenchmark; extern int nScriptCheckThreads; extern bool fTxIndex; extern unsigned int nCoinCacheSize; +extern CFeeRate minRelayTxFee; // Minimum disk space required - used in CheckDiskSpace() static const uint64_t nMinDiskSpace = 52428800; @@ -108,8 +109,8 @@ struct CNodeStateStats; struct CBlockTemplate; -/** Set up internal signal handlers **/ -void RegisterInternalSignals(); +/** Initialize respend bloom filter **/ +void InitRespendFilter(); /** Register a wallet to receive updates from core */ void RegisterWallet(CWalletInterface* pwalletIn); @@ -245,14 +246,7 @@ struct CDiskTxPos : public CDiskBlockPos }; - -enum GetMinFee_mode -{ - GMF_RELAY, - GMF_SEND, -}; - -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); // // Check transaction inputs, and make sure any @@ -459,7 +453,7 @@ public: int GetDepthInMainChain() const { CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet); } bool IsInMainChain() const { CBlockIndex *pindexRet; return GetDepthInMainChainINTERNAL(pindexRet) > 0; } int GetBlocksToMaturity() const; - bool AcceptToMemoryPool(bool fLimitFree=true); + bool AcceptToMemoryPool(bool fLimitFree=true, bool fRejectInsaneFee=true); }; diff --git a/src/miner.cpp b/src/miner.cpp index 69e53756e0..17918a1280 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -236,7 +236,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn) double dPriorityDelta = 0; int64_t nFeeDelta = 0; 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; // Prioritise by fee once past the priority size or we run out of high-priority diff --git a/src/net.cpp b/src/net.cpp index 934c45ca4c..6a660dc9bd 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -491,8 +491,6 @@ CNode* ConnectNode(CAddress addrConnect, const char *pszDest) { addrman.Attempt(addrConnect); - LogPrint("net", "connected %s\n", pszDest ? pszDest : addrConnect.ToString()); - // Set to non-blocking #ifdef WIN32 u_long nOne = 1; @@ -513,6 +511,7 @@ CNode* ConnectNode(CAddress addrConnect, const char *pszDest) } pnode->nTimeConnected = GetTime(); + return pnode; } @@ -524,7 +523,7 @@ void CNode::CloseSocketDisconnect() fDisconnect = true; if (hSocket != INVALID_SOCKET) { - LogPrint("net", "disconnecting node %s\n", addrName); + LogPrint("net", "disconnecting peer=%d\n", id); closesocket(hSocket); hSocket = INVALID_SOCKET; } @@ -548,7 +547,10 @@ void CNode::PushVersion() CAddress addrYou = (addr.IsRoutable() && !IsProxy(addr) ? addr : CAddress(CService("0.0.0.0",0))); CAddress addrMe = GetLocalAddress(&addr); RAND_bytes((unsigned char*)&nLocalHostNonce, sizeof(nLocalHostNonce)); - LogPrint("net", "send version message: version %d, blocks=%d, us=%s, them=%s, peer=%s\n", PROTOCOL_VERSION, nBestHeight, addrMe.ToString(), addrYou.ToString(), addr.ToString()); + if (fLogIPs) + LogPrint("net", "send version message: version %d, blocks=%d, us=%s, them=%s, peer=%d\n", PROTOCOL_VERSION, nBestHeight, addrMe.ToString(), addrYou.ToString(), id); + else + LogPrint("net", "send version message: version %d, blocks=%d, us=%s, peer=%d\n", PROTOCOL_VERSION, nBestHeight, addrMe.ToString(), id); PushMessage("version", PROTOCOL_VERSION, nLocalServices, nTime, addrYou, addrMe, nLocalHostNonce, FormatSubVersion(CLIENT_NAME, CLIENT_VERSION, std::vector<string>()), nBestHeight, true); } @@ -653,6 +655,9 @@ bool CNode::ReceiveMsgBytes(const char *pch, unsigned int nBytes) pch += handled; nBytes -= handled; + + if (msg.complete()) + msg.nTime = GetTimeMicros(); } return true; @@ -950,7 +955,6 @@ void ThreadSocketHandler() } else { - LogPrint("net", "accepted connection %s\n", addr.ToString()); CNode* pnode = new CNode(hSocket, addr, "", true); pnode->AddRef(); @@ -1040,7 +1044,7 @@ void ThreadSocketHandler() { if (pnode->nLastRecv == 0 || pnode->nLastSend == 0) { - LogPrint("net", "socket no message in first 60 seconds, %d %d\n", pnode->nLastRecv != 0, pnode->nLastSend != 0); + LogPrint("net", "socket no message in first 60 seconds, %d %d from %d\n", pnode->nLastRecv != 0, pnode->nLastSend != 0, pnode->id); pnode->fDisconnect = true; } else if (nTime - pnode->nLastSend > TIMEOUT_INTERVAL) @@ -173,11 +173,14 @@ public: CDataStream vRecv; // received message data unsigned int nDataPos; + int64_t nTime; // time (in microseconds) of message receipt. + CNetMessage(int nTypeIn, int nVersionIn) : hdrbuf(nTypeIn, nVersionIn), vRecv(nTypeIn, nVersionIn) { hdrbuf.resize(24); in_data = false; nHdrPos = 0; nDataPos = 0; + nTime = 0; } bool complete() const @@ -330,6 +333,11 @@ public: id = nLastNodeId++; } + if (fLogIPs) + LogPrint("net", "Added connection to %s peer=%d\n", addrName, id); + else + LogPrint("net", "Added connection peer=%d\n", id); + // Be shy and don't send version until we hear if (hSocket != INVALID_SOCKET && !fInbound) PushVersion(); @@ -446,7 +454,7 @@ public: nRequestTime = it->second; else nRequestTime = 0; - LogPrint("net", "askfor %s %d (%s)\n", inv.ToString(), nRequestTime, DateTimeStrFormat("%H:%M:%S", nRequestTime/1000000).c_str()); + LogPrint("net", "askfor %s %d (%s) peer=%d\n", inv.ToString(), nRequestTime, DateTimeStrFormat("%H:%M:%S", nRequestTime/1000000).c_str(), id); // Make sure not to reuse time indexes to keep things in the same order int64_t nNow = GetTimeMicros() - 1000000; @@ -514,7 +522,7 @@ public: assert(ssSend.size () >= CMessageHeader::CHECKSUM_OFFSET + sizeof(nChecksum)); memcpy((char*)&ssSend[CMessageHeader::CHECKSUM_OFFSET], &nChecksum, sizeof(nChecksum)); - LogPrint("net", "(%d bytes)\n", nSize); + LogPrint("net", "(%d bytes) peer=%d\n", nSize, id); std::deque<CSerializeData>::iterator it = vSendMsg.insert(vSendMsg.end(), CSerializeData()); ssSend.GetAndClear(*it); diff --git a/src/netbase.cpp b/src/netbase.cpp index 3c50174e75..f47b0f3563 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -3,6 +3,18 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#ifdef HAVE_CONFIG_H +#include "bitcoin-config.h" +#endif + +#ifdef HAVE_INET_PTON +#include <arpa/inet.h> +#endif + +#ifdef HAVE_GETADDRINFO_A +#include <netdb.h> +#endif + #include "netbase.h" #include "hash.h" @@ -71,9 +83,30 @@ bool static LookupIntern(const char *pszName, std::vector<CNetAddr>& vIP, unsign } } +#ifdef HAVE_GETADDRINFO_A + struct in_addr ipv4_addr; +#ifdef HAVE_INET_PTON + if (inet_pton(AF_INET, pszName, &ipv4_addr) > 0) { + vIP.push_back(CNetAddr(ipv4_addr)); + return true; + } + + struct in6_addr ipv6_addr; + if (inet_pton(AF_INET6, pszName, &ipv6_addr) > 0) { + vIP.push_back(CNetAddr(ipv6_addr)); + return true; + } +#else + ipv4_addr.s_addr = inet_addr(pszName); + if (ipv4_addr.s_addr != INADDR_NONE) { + vIP.push_back(CNetAddr(ipv4_addr)); + return true; + } +#endif +#endif + struct addrinfo aiHint; memset(&aiHint, 0, sizeof(struct addrinfo)); - aiHint.ai_socktype = SOCK_STREAM; aiHint.ai_protocol = IPPROTO_TCP; aiHint.ai_family = AF_UNSPEC; @@ -82,8 +115,33 @@ bool static LookupIntern(const char *pszName, std::vector<CNetAddr>& vIP, unsign #else aiHint.ai_flags = fAllowLookup ? AI_ADDRCONFIG : AI_NUMERICHOST; #endif + struct addrinfo *aiRes = NULL; +#ifdef HAVE_GETADDRINFO_A + struct gaicb gcb, *query = &gcb; + memset(query, 0, sizeof(struct gaicb)); + gcb.ar_name = pszName; + gcb.ar_request = &aiHint; + int nErr = getaddrinfo_a(GAI_NOWAIT, &query, 1, NULL); + if (nErr) + return false; + + do { + // Should set the timeout limit to a resonable value to avoid + // generating unnecessary checking call during the polling loop, + // while it can still response to stop request quick enough. + // 2 seconds looks fine in our situation. + struct timespec ts = { 2, 0 }; + gai_suspend(&query, 1, &ts); + boost::this_thread::interruption_point(); + + nErr = gai_error(query); + if (0 == nErr) + aiRes = query->ar_result; + } while (nErr == EAI_INPROGRESS); +#else int nErr = getaddrinfo(pszName, NULL, &aiHint, &aiRes); +#endif if (nErr) return false; diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 30f5ec8939..f2fb8c877e 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -28,6 +28,7 @@ #include <iostream> +#include <QAction> #include <QApplication> #include <QDateTime> #include <QDesktopWidget> @@ -39,6 +40,7 @@ #include <QMenuBar> #include <QMessageBox> #include <QMimeData> +#include <QPoint> #include <QProgressBar> #include <QProgressDialog> #include <QSettings> @@ -49,6 +51,8 @@ #include <QToolBar> #include <QVBoxLayout> + + #if QT_VERSION < 0x050000 #include <QUrl> #include <QTextDocument> @@ -156,10 +160,13 @@ BitcoinGUI::BitcoinGUI(bool fIsTestnet, QWidget *parent) : QHBoxLayout *frameBlocksLayout = new QHBoxLayout(frameBlocks); frameBlocksLayout->setContentsMargins(3,0,3,0); frameBlocksLayout->setSpacing(3); + unitDisplayControl = new UnitDisplayStatusBarControl(); labelEncryptionIcon = new QLabel(); labelConnectionsIcon = new QLabel(); labelBlocksIcon = new QLabel(); frameBlocksLayout->addStretch(); + frameBlocksLayout->addWidget(unitDisplayControl); + frameBlocksLayout->addStretch(); frameBlocksLayout->addWidget(labelEncryptionIcon); frameBlocksLayout->addStretch(); frameBlocksLayout->addWidget(labelConnectionsIcon); @@ -420,6 +427,8 @@ void BitcoinGUI::setClientModel(ClientModel *clientModel) walletFrame->setClientModel(clientModel); } #endif + + this->unitDisplayControl->setOptionsModel(clientModel->getOptionsModel()); } } @@ -1000,3 +1009,72 @@ void BitcoinGUI::unsubscribeFromCoreSignals() // Disconnect signals from client uiInterface.ThreadSafeMessageBox.disconnect(boost::bind(ThreadSafeMessageBox, this, _1, _2, _3)); } + +UnitDisplayStatusBarControl::UnitDisplayStatusBarControl():QLabel() +{ + optionsModel = 0; + createContextMenu(); + setStyleSheet("font:11pt; color: #333333"); + setToolTip(tr("Unit to show amounts in. Click to select another unit.")); +} + +/** So that it responds to left-button clicks */ +void UnitDisplayStatusBarControl::mousePressEvent(QMouseEvent *event) +{ + onDisplayUnitsClicked(event->pos()); +} + +/** Creates context menu, its actions, and wires up all the relevant signals for mouse events. */ +void UnitDisplayStatusBarControl::createContextMenu() +{ + menu = new QMenu(); + foreach(BitcoinUnits::Unit u, BitcoinUnits::availableUnits()) + { + QAction *menuAction = new QAction(QString(BitcoinUnits::name(u)), this); + menuAction->setData(QVariant(u)); + menu->addAction(menuAction); + } + connect(menu,SIGNAL(triggered(QAction*)),this,SLOT(onMenuSelection(QAction*))); + + // what happens on right click. + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this,SIGNAL(customContextMenuRequested(const QPoint&)),this,SLOT(onDisplayUnitsClicked(const QPoint&))); +} + +/** Lets the control know about the Options Model (and its signals) */ +void UnitDisplayStatusBarControl::setOptionsModel(OptionsModel *optionsModel) +{ + if (optionsModel) + { + this->optionsModel = optionsModel; + + // be aware of a display unit change reported by the OptionsModel object. + connect(optionsModel,SIGNAL(displayUnitChanged(int)),this,SLOT(updateDisplayUnit(int))); + + // initialize the display units label with the current value in the model. + updateDisplayUnit(optionsModel->getDisplayUnit()); + } +} + +/** When Display Units are changed on OptionsModel it will refresh the display text of the control on the status bar */ +void UnitDisplayStatusBarControl::updateDisplayUnit(int newUnits) +{ + setText(BitcoinUnits::name(newUnits)); +} + +/** Shows context menu with Display Unit options by the mouse coordinates */ +void UnitDisplayStatusBarControl::onDisplayUnitsClicked(const QPoint& point) +{ + QPoint globalPos = mapToGlobal(point); + menu->exec(globalPos); +} + +/** Tells underlying optionsModel to update its current display unit. */ +void UnitDisplayStatusBarControl::onMenuSelection(QAction* action) +{ + if (action) + { + optionsModel->setDisplayUnit(action->data()); + } +} + diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index e7a842df99..705e629a69 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -9,12 +9,16 @@ #include "config/bitcoin-config.h" #endif +#include <QLabel> #include <QMainWindow> #include <QMap> +#include <QMenu> +#include <QPoint> #include <QSystemTrayIcon> class ClientModel; class Notificator; +class OptionsModel; class RPCConsole; class SendCoinsRecipient; class WalletFrame; @@ -22,9 +26,13 @@ class WalletModel; class CWallet; +class UnitDisplayStatusBarControl; + QT_BEGIN_NAMESPACE class QAction; class QLabel; +class QMenu; +class QPoint; class QProgressBar; class QProgressDialog; QT_END_NAMESPACE @@ -69,6 +77,7 @@ private: ClientModel *clientModel; WalletFrame *walletFrame; + UnitDisplayStatusBarControl *unitDisplayControl; QLabel *labelEncryptionIcon; QLabel *labelConnectionsIcon; QLabel *labelBlocksIcon; @@ -198,4 +207,32 @@ private slots: void showProgress(const QString &title, int nProgress); }; +class UnitDisplayStatusBarControl : public QLabel +{ + Q_OBJECT + +public: + explicit UnitDisplayStatusBarControl(); + /** Lets the control know about the Options Model (and its signals) */ + void setOptionsModel(OptionsModel *optionsModel); + +protected: + /** So that it responds to left-button clicks */ + void mousePressEvent(QMouseEvent *event); + +private: + OptionsModel *optionsModel; + QMenu* menu; + /** Shows context menu with Display Unit options by the mouse coordinates */ + void onDisplayUnitsClicked(const QPoint& point); + /** Creates context menu, its actions, and wires up all the relevant signals for mouse events. */ + void createContextMenu(); + +private slots: + /** When Display Units are changed on OptionsModel it will refresh the display text of the control on the status bar */ + void updateDisplayUnit(int newUnits); + /** Tells underlying optionsModel to update its current display unit. */ + void onMenuSelection(QAction* action); +}; + #endif // BITCOINGUI_H diff --git a/src/qt/bitcoinunits.cpp b/src/qt/bitcoinunits.cpp index 2fed443cf2..4ba6aba551 100644 --- a/src/qt/bitcoinunits.cpp +++ b/src/qt/bitcoinunits.cpp @@ -169,6 +169,16 @@ bool BitcoinUnits::parse(int unit, const QString &value, qint64 *val_out) return ok; } +QString BitcoinUnits::getAmountColumnTitle(int unit) +{ + QString amountTitle = QObject::tr("Amount"); + if (BitcoinUnits::valid(unit)) + { + amountTitle += " ("+BitcoinUnits::name(unit) + ")"; + } + return amountTitle; +} + int BitcoinUnits::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); diff --git a/src/qt/bitcoinunits.h b/src/qt/bitcoinunits.h index 46517fc07b..451b52ee21 100644 --- a/src/qt/bitcoinunits.h +++ b/src/qt/bitcoinunits.h @@ -54,6 +54,8 @@ public: static QString formatWithUnit(int unit, qint64 amount, bool plussign=false); //! Parse string to coin amount static bool parse(int unit, const QString &value, qint64 *val_out); + //! Gets title for amount column including current display unit if optionsModel reference available */ + static QString getAmountColumnTitle(int unit); ///@} //! @name AbstractListModel implementation diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index c6a6150392..e0a524a55e 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -16,6 +16,8 @@ #include "main.h" #include "wallet.h" +#include <boost/assign/list_of.hpp> // for 'map_list_of()' + #include <QApplication> #include <QCheckBox> #include <QCursor> @@ -400,23 +402,24 @@ void CoinControlDialog::viewItemChanged(QTreeWidgetItem* item, int column) } // 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"); - else if (AllowFree(dPriority / 100000)) return tr("higher"); - 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"); + double p = mempool.estimatePriority(i.first); + if (p > 0 && dPriority >= p) return i.second; } + // 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 @@ -449,7 +452,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) { CTxOut txout(amount, (CScript)vector<unsigned char>(24, 0)); txDummy.vout.push_back(txout); - if (txout.IsDust(CTransaction::minRelayTxFee)) + if (txout.IsDust(::minRelayTxFee)) fDust = true; } } @@ -518,15 +521,20 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) // 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 int64_t nFee = payTxFee.GetFee(max((unsigned int)1000, nBytes)); // 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) { @@ -536,7 +544,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) if (nChange > 0 && nChange < CENT) { CTxOut txout(nChange, (CScript)vector<unsigned char>(24, 0)); - if (txout.IsDust(CTransaction::minRelayTxFee)) + if (txout.IsDust(::minRelayTxFee)) { nPayFee += nChange; nChange = 0; @@ -591,23 +599,23 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) } // 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" l7->setStyleSheet((fDust) ? "color:red;" : ""); // Dust = "yes" // tool tips 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."); 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 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 - 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); l3->setToolTip(toolTip4); @@ -732,7 +740,7 @@ void CoinControlDialog::updateView() // priority 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, " ")); dPrioritySum += (double)out.tx->vout[out.i].nValue * (out.nDepth+1); nInputSum += nInputSize; @@ -765,7 +773,7 @@ void CoinControlDialog::updateView() itemWalletAddress->setText(COLUMN_CHECKBOX, "(" + QString::number(nChildren) + ")"); itemWalletAddress->setText(COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, nSum)); 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, " ")); } } diff --git a/src/qt/coincontroldialog.h b/src/qt/coincontroldialog.h index 465e2a009d..4f7422642f 100644 --- a/src/qt/coincontroldialog.h +++ b/src/qt/coincontroldialog.h @@ -19,6 +19,7 @@ namespace Ui { } class WalletModel; class CCoinControl; +class CTxMemPool; class CoinControlDialog : public QDialog { @@ -32,7 +33,7 @@ public: // static because also called from sendcoinsdialog static void updateLabels(WalletModel*, QDialog*); - static QString getPriorityLabel(double); + static QString getPriorityLabel(const CTxMemPool& pool, double); static QList<qint64> payAmounts; static CCoinControl *coinControl; diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 81b9054252..60a131df7e 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -11,6 +11,7 @@ #include "core.h" #include "init.h" +#include "main.h" #include "protocol.h" #include "util.h" @@ -212,7 +213,7 @@ bool isDust(const QString& address, qint64 amount) CTxDestination dest = CBitcoinAddress(address.toStdString()).Get(); CScript script; script.SetDestination(dest); CTxOut txOut(amount, script); - return txOut.IsDust(CTransaction::minRelayTxFee); + return txOut.IsDust(::minRelayTxFee); } QString HtmlEscape(const QString& str, bool fMultiLine) diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 12d54dff64..9502dba904 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -14,7 +14,10 @@ #include "monitoreddatamapper.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 "txdb.h" // for -dbcache defaults @@ -101,7 +104,9 @@ OptionsDialog::OptionsDialog(QWidget *parent) : #endif 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 */ mapper = new MonitoredDataMapper(this); diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 6d985abaf8..09553a2587 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -308,9 +308,7 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in break; #endif case DisplayUnit: - nDisplayUnit = value.toInt(); - settings.setValue("nDisplayUnit", nDisplayUnit); - emit displayUnitChanged(nDisplayUnit); + setDisplayUnit(value); break; case DisplayAddresses: bDisplayAddresses = value.toBool(); @@ -356,11 +354,24 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in break; } } + emit dataChanged(index, index); return successful; } +/** Updates current unit in memory, settings and emits displayUnitChanged(newUnit) signal */ +void OptionsModel::setDisplayUnit(const QVariant &value) +{ + if (!value.isNull()) + { + QSettings settings; + nDisplayUnit = value.toInt(); + settings.setValue("nDisplayUnit", nDisplayUnit); + emit displayUnitChanged(nDisplayUnit); + } +} + bool OptionsModel::getProxySettings(QNetworkProxy& proxy) const { // Directly query current base proxy, because diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index 2596682d07..89c2ec7453 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -52,6 +52,8 @@ public: int rowCount(const QModelIndex & parent = QModelIndex()) const; QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const; bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole); + /** Updates current unit in memory, settings and emits displayUnitChanged(newUnit) signal */ + void setDisplayUnit(const QVariant &value); /* Explicit getters */ bool getMinimizeToTray() { return fMinimizeToTray; } diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index 6ca90f0513..53db2c5cd9 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -551,7 +551,7 @@ bool PaymentServer::processPaymentRequest(PaymentRequestPlus& request, SendCoins // Extract and check amounts 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).") .arg(BitcoinUnits::formatWithUnit(optionsModel->getDisplayUnit(), sendingTo.second)), CClientUIInterface::MSG_ERROR); diff --git a/src/qt/recentrequeststablemodel.cpp b/src/qt/recentrequeststablemodel.cpp index 844d62518c..b5a998f9f5 100644 --- a/src/qt/recentrequeststablemodel.cpp +++ b/src/qt/recentrequeststablemodel.cpp @@ -21,7 +21,9 @@ RecentRequestsTableModel::RecentRequestsTableModel(CWallet *wallet, WalletModel addNewRequest(request); /* These columns must match the indices in the ColumnIndex enumeration */ - columns << tr("Date") << tr("Label") << tr("Message") << tr("Amount"); + columns << tr("Date") << tr("Label") << tr("Message") << getAmountTitle(); + + connect(walletModel->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit())); } RecentRequestsTableModel::~RecentRequestsTableModel() @@ -101,6 +103,24 @@ QVariant RecentRequestsTableModel::headerData(int section, Qt::Orientation orien return QVariant(); } +/** Updates the column title to "Amount (DisplayUnit)" and emits headerDataChanged() signal for table headers to react. */ +void RecentRequestsTableModel::updateAmountColumnTitle() +{ + columns[Amount] = getAmountTitle(); + emit headerDataChanged(Qt::Horizontal,Amount,Amount); +} + +/** Gets title for amount column including current display unit if optionsModel reference available. */ +QString RecentRequestsTableModel::getAmountTitle() +{ + QString amountTitle = tr("Amount"); + if (this->walletModel->getOptionsModel() != NULL) + { + amountTitle += " ("+BitcoinUnits::name(this->walletModel->getOptionsModel()->getDisplayUnit()) + ")"; + } + return amountTitle; +} + QModelIndex RecentRequestsTableModel::index(int row, int column, const QModelIndex &parent) const { Q_UNUSED(parent); @@ -185,6 +205,11 @@ void RecentRequestsTableModel::sort(int column, Qt::SortOrder order) emit dataChanged(index(0, 0, QModelIndex()), index(list.size() - 1, NUMBER_OF_COLUMNS - 1, QModelIndex())); } +void RecentRequestsTableModel::updateDisplayUnit() +{ + updateAmountColumnTitle(); +} + bool RecentRequestEntryLessThan::operator()(RecentRequestEntry &left, RecentRequestEntry &right) const { RecentRequestEntry *pLeft = &left; diff --git a/src/qt/recentrequeststablemodel.h b/src/qt/recentrequeststablemodel.h index d4cc5078aa..4f0b241259 100644 --- a/src/qt/recentrequeststablemodel.h +++ b/src/qt/recentrequeststablemodel.h @@ -91,12 +91,18 @@ public: public slots: void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + void updateDisplayUnit(); private: WalletModel *walletModel; QStringList columns; QList<RecentRequestEntry> list; int64_t nReceiveRequestsMaxId; + + /** Updates the column title to "Amount (DisplayUnit)" and emits headerDataChanged() signal for table headers to react. */ + void updateAmountColumnTitle(); + /** Gets title for amount column including current display unit if optionsModel reference available. */ + QString getAmountTitle(); }; #endif diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index a935752246..6c1a6c3579 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -235,8 +235,7 @@ TransactionTableModel::TransactionTableModel(CWallet* wallet, WalletModel *paren walletModel(parent), priv(new TransactionTablePriv(wallet, this)) { - columns << QString() << tr("Date") << tr("Type") << tr("Address") << tr("Amount"); - + columns << QString() << tr("Date") << tr("Type") << tr("Address") << BitcoinUnits::getAmountColumnTitle(walletModel->getOptionsModel()->getDisplayUnit()); priv->refreshWallet(); connect(walletModel->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit())); @@ -247,6 +246,13 @@ TransactionTableModel::~TransactionTableModel() delete priv; } +/** Updates the column title to "Amount (DisplayUnit)" and emits headerDataChanged() signal for table headers to react. */ +void TransactionTableModel::updateAmountColumnTitle() +{ + columns[Amount] = BitcoinUnits::getAmountColumnTitle(walletModel->getOptionsModel()->getDisplayUnit()); + emit headerDataChanged(Qt::Horizontal,Amount,Amount); +} + void TransactionTableModel::updateTransaction(const QString &hash, int status) { uint256 updated; @@ -632,5 +638,6 @@ QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex void TransactionTableModel::updateDisplayUnit() { // emit dataChanged to update Amount column with the current unit + updateAmountColumnTitle(); emit dataChanged(index(0, Amount), index(priv->size()-1, Amount)); } diff --git a/src/qt/transactiontablemodel.h b/src/qt/transactiontablemodel.h index 333e6bc6ed..e8b6ed065d 100644 --- a/src/qt/transactiontablemodel.h +++ b/src/qt/transactiontablemodel.h @@ -87,6 +87,8 @@ public slots: void updateTransaction(const QString &hash, int status); void updateConfirmations(); void updateDisplayUnit(); + /** Updates the column title to "Amount (DisplayUnit)" and emits headerDataChanged() signal for table headers to react. */ + void updateAmountColumnTitle(); friend class TransactionTablePriv; }; diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index d4d29416ca..d6d210a561 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -309,7 +309,7 @@ void TransactionView::exportClicked() writer.addColumn(tr("Type"), TransactionTableModel::Type, Qt::EditRole); writer.addColumn(tr("Label"), 0, TransactionTableModel::LabelRole); writer.addColumn(tr("Address"), 0, TransactionTableModel::AddressRole); - writer.addColumn(tr("Amount"), 0, TransactionTableModel::FormattedAmountRole); + writer.addColumn(BitcoinUnits::getAmountColumnTitle(model->getOptionsModel()->getDisplayUnit()), 0, TransactionTableModel::FormattedAmountRole); writer.addColumn(tr("ID"), 0, TransactionTableModel::TxIDRole); if(!writer.write()) { diff --git a/src/rpcmisc.cpp b/src/rpcmisc.cpp index 5b470516a1..9ed6b52513 100644 --- a/src/rpcmisc.cpp +++ b/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("paytxfee", ValueFromAmount(payTxFee.GetFeePerK()))); #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"))); return obj; } diff --git a/src/rpcnet.cpp b/src/rpcnet.cpp index 2d7abb2d58..cd3bd59f8f 100644 --- a/src/rpcnet.cpp +++ b/src/rpcnet.cpp @@ -372,7 +372,7 @@ Value getnetworkinfo(const Array& params, bool fHelp) obj.push_back(Pair("timeoffset", GetTimeOffset())); obj.push_back(Pair("connections", (int)vNodes.size())); 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; { LOCK(cs_mapLocalHost); diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index f47b3385da..5c4d307497 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -512,7 +512,7 @@ void StartRPCThreads() if(!subnet.IsValid()) { uiInterface.ThreadSafeMessageBox( - strprintf("Invalid -rpcallowip subnet specification: %s", strAllow), + strprintf("Invalid -rpcallowip subnet specification: %s. Valid are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24).", strAllow), "", CClientUIInterface::MSG_ERROR); StartShutdown(); return; diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 97a426dd35..a852de5da8 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -80,13 +80,13 @@ public: // Used as belt-and-suspenders check when reading to detect // file corruption - bool AreSane(const std::vector<CFeeRate>& vecFee) + bool AreSane(const std::vector<CFeeRate>& vecFee, const CFeeRate& minRelayFee) { BOOST_FOREACH(CFeeRate fee, vecFee) { if (fee < CFeeRate(0)) return false; - if (fee.GetFee(1000) > CTransaction::minRelayTxFee.GetFee(1000) * 10000) + if (fee.GetFeePerK() > minRelayFee.GetFeePerK() * 10000) return false; } return true; @@ -109,10 +109,10 @@ public: fileout << vecPriority; } - void Read(CAutoFile& filein) { + void Read(CAutoFile& filein, const CFeeRate& minRelayFee) { std::vector<CFeeRate> vecFee; filein >> vecFee; - if (AreSane(vecFee)) + if (AreSane(vecFee, minRelayFee)) feeSamples.insert(feeSamples.end(), vecFee.begin(), vecFee.end()); else 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 == 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". 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 // because it is high-priority or because it has sufficient fees. - bool sufficientFee = (feeRate > CTransaction::minRelayTxFee); + bool sufficientFee = (feeRate > minRelayFee); bool sufficientPriority = AllowFree(dPriority); const char* assignedTo = "unassigned"; if (sufficientFee && !sufficientPriority) @@ -177,7 +177,7 @@ public: 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) { @@ -222,7 +222,7 @@ public: // Fees are stored and reported as BTC-per-kb: CFeeRate feeRate(entry->GetFee(), entry->GetTxSize()); 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++) { @@ -251,8 +251,13 @@ public: std::sort(sortedFeeSamples.begin(), sortedFeeSamples.end(), 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); + } int nBucketSize = history.at(nBlocksToConfirm).FeeSamples(); @@ -281,7 +286,7 @@ public: std::sort(sortedPrioritySamples.begin(), sortedPrioritySamples.end(), std::greater<double>()); } - if (sortedPrioritySamples.size() == 0) + if (sortedPrioritySamples.size() < 11) return -1.0; int nBucketSize = history.at(nBlocksToConfirm).PrioritySamples(); @@ -308,7 +313,7 @@ public: } } - void Read(CAutoFile& filein) + void Read(CAutoFile& filein, const CFeeRate& minRelayFee) { filein >> nBestSeenHeight; size_t numEntries; @@ -317,14 +322,14 @@ public: for (size_t i = 0; i < numEntries; i++) { CBlockAverage entry; - entry.Read(filein); + entry.Read(filein, minRelayFee); history.push_back(entry); } } }; -CTxMemPool::CTxMemPool() +CTxMemPool::CTxMemPool(const CFeeRate& _minRelayFee) : minRelayFee(_minRelayFee) { // Sanity checks off by default for performance, because otherwise // 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)) entries.push_back(mapTx[hash]); } - minerPolicyEstimator->seenBlock(entries, nBlockHeight); + minerPolicyEstimator->seenBlock(entries, nBlockHeight, minRelayFee); BOOST_FOREACH(const CTransaction& tx, vtx) { std::list<CTransaction> dummy; @@ -555,7 +560,7 @@ CTxMemPool::ReadFeeEstimates(CAutoFile& filein) return error("CTxMemPool::ReadFeeEstimates() : up-version (%d) fee estimate file", nVersionRequired); LOCK(cs); - minerPolicyEstimator->Read(filein); + minerPolicyEstimator->Read(filein, minRelayFee); } catch (std::exception &e) { LogPrintf("CTxMemPool::ReadFeeEstimates() : unable to read policy estimator data (non-fatal)"); diff --git a/src/txmempool.h b/src/txmempool.h index f7dbb126a0..41b2c52f39 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -67,13 +67,15 @@ private: unsigned int nTransactionsUpdated; CMinerPolicyEstimator* minerPolicyEstimator; + CFeeRate minRelayFee; // Passed to constructor to avoid dependency on main + public: mutable CCriticalSection cs; std::map<uint256, CTxMemPoolEntry> mapTx; std::map<COutPoint, CInPoint> mapNextTx; std::map<uint256, std::pair<double, int64_t> > mapDeltas; - CTxMemPool(); + CTxMemPool(const CFeeRate& _minRelayFee); ~CTxMemPool(); /* diff --git a/src/util.cpp b/src/util.cpp index 5a8f85ade7..081484c37d 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -95,6 +95,7 @@ bool fDaemon = false; bool fServer = false; string strMiscWarning; bool fLogTimestamps = false; +bool fLogIPs = false; volatile bool fReopenDebugLog = false; CClientUIInterface uiInterface; diff --git a/src/util.h b/src/util.h index 707b8f2d76..60db71bfd0 100644 --- a/src/util.h +++ b/src/util.h @@ -100,6 +100,7 @@ extern bool fPrintToDebugLog; extern bool fServer; extern std::string strMiscWarning; extern bool fLogTimestamps; +extern bool fLogIPs; extern volatile bool fReopenDebugLog; void RandAddSeed(); diff --git a/src/wallet.cpp b/src/wallet.cpp index daca7ac04b..318a1388d2 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -18,8 +18,12 @@ using namespace std; // Settings CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE); +unsigned int nTxConfirmTarget = 1; 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 @@ -1273,6 +1277,7 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend, return false; } + wtxNew.fTimeReceivedIsTxTime = true; wtxNew.BindWallet(this); 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) { CTxOut txout(s.second, s.first); - if (txout.IsDust(CTransaction::minRelayTxFee)) + if (txout.IsDust(::minRelayTxFee)) { strFailReason = _("Transaction amount too small"); return false; @@ -1353,7 +1358,7 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend, // Never create dust outputs; if we would, just // add the dust to the fee. - if (newTxOut.IsDust(CTransaction::minRelayTxFee)) + if (newTxOut.IsDust(::minRelayTxFee)) { nFeeRet += nChange; reservekey.ReturnKey(); @@ -1393,19 +1398,31 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend, } dPriority = wtxNew.ComputePriority(dPriority, nBytes); - // Check that enough fee is included - int64_t nPayFee = payTxFee.GetFee(nBytes); - bool fAllowFree = AllowFree(dPriority); - int64_t nMinFee = GetMinFee(wtxNew, nBytes, fAllowFree, GMF_SEND); - if (nFeeRet < max(nPayFee, nMinFee)) + int64_t nFeeNeeded = GetMinimumFee(nBytes, nTxConfirmTarget, mempool); + + if (nFeeRet >= nFeeNeeded) + break; // Done, enough fee included. + + // 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; } - 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); } +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; +} + diff --git a/src/wallet.h b/src/wallet.h index 1c2512d678..a5162bb838 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -25,12 +25,15 @@ // Settings extern CFeeRate payTxFee; +extern unsigned int nTxConfirmTarget; extern bool bSpendZeroConfChange; // -paytxfee default static const int64_t DEFAULT_TRANSACTION_FEE = 0; // -paytxfee will warn if called with a higher fee than this amount (in satoshis) per KB 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 CCoinControl; @@ -265,6 +268,9 @@ public: std::string SendMoney(CScript scriptPubKey, 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 TopUpKeyPool(unsigned int kpSize = 0); void ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool); |