diff options
author | Gavin Andresen <gavinandresen@gmail.com> | 2013-11-03 20:07:36 -0800 |
---|---|---|
committer | Gavin Andresen <gavinandresen@gmail.com> | 2013-11-03 20:07:36 -0800 |
commit | a95a1c06b1823d13e8b685c6b18696ead1d17422 (patch) | |
tree | 35a62a9d0df1c0bb9157e8cc85ab086c01c55217 | |
parent | 39b4f0d7ddc964b789876d15d888c3b29022939e (diff) | |
parent | 319b11607f8592d7ef67ec82fa73545ad7430974 (diff) |
Merge pull request #3154 from gavinandresen/mempool_refactor
Mempool refactor
-rw-r--r-- | src/Makefile.am | 4 | ||||
-rw-r--r-- | src/checkqueue.h | 1 | ||||
-rw-r--r-- | src/init.cpp | 4 | ||||
-rw-r--r-- | src/limitedmap.h | 1 | ||||
-rw-r--r-- | src/main.cpp | 258 | ||||
-rw-r--r-- | src/main.h | 52 | ||||
-rw-r--r-- | src/miner.cpp | 4 | ||||
-rw-r--r-- | src/rpcmining.cpp | 8 | ||||
-rw-r--r-- | src/rpcrawtransaction.cpp | 2 | ||||
-rw-r--r-- | src/txmempool.cpp | 162 | ||||
-rw-r--r-- | src/txmempool.h | 67 |
11 files changed, 317 insertions, 246 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 14d1dd03a6..508063d5e2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -17,7 +17,7 @@ BITCOIN_CORE_H = addrman.h alert.h allocators.h base58.h bignum.h \ clientversion.h compat.h core.h crypter.h db.h hash.h init.h \ key.h keystore.h leveldb.h limitedmap.h main.h miner.h mruset.h \ netbase.h net.h protocol.h script.h serialize.h sync.h threadsafety.h \ - txdb.h ui_interface.h uint256.h util.h version.h walletdb.h wallet.h + txdb.h txmempool.h ui_interface.h uint256.h util.h version.h walletdb.h wallet.h JSON_H = json/json_spirit.h json/json_spirit_error_position.h \ json/json_spirit_reader.h json/json_spirit_reader_template.h \ @@ -36,7 +36,7 @@ libbitcoin_a_SOURCES = addrman.cpp alert.cpp allocators.cpp bitcoinrpc.cpp bloom init.cpp key.cpp keystore.cpp leveldb.cpp main.cpp miner.cpp \ netbase.cpp net.cpp noui.cpp protocol.cpp rpcblockchain.cpp rpcdump.cpp \ rpcmining.cpp rpcnet.cpp rpcrawtransaction.cpp rpcwallet.cpp script.cpp \ - sync.cpp txdb.cpp util.cpp version.cpp wallet.cpp walletdb.cpp $(JSON_H) \ + sync.cpp txdb.cpp txmempool.cpp util.cpp version.cpp wallet.cpp walletdb.cpp $(JSON_H) \ $(BITCOIN_CORE_H) nodist_libbitcoin_a_SOURCES = $(top_srcdir)/src/obj/build.h diff --git a/src/checkqueue.h b/src/checkqueue.h index eba424fbaa..6e2f609fcf 100644 --- a/src/checkqueue.h +++ b/src/checkqueue.h @@ -4,6 +4,7 @@ #ifndef CHECKQUEUE_H #define CHECKQUEUE_H +#include <boost/foreach.hpp> #include <boost/thread/mutex.hpp> #include <boost/thread/locks.hpp> #include <boost/thread/condition_variable.hpp> diff --git a/src/init.cpp b/src/init.cpp index 647b8d52ea..a803598f32 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -106,7 +106,7 @@ void Shutdown() if (!lockShutdown) return; RenameThread("bitcoin-shutoff"); - nTransactionsUpdated++; + mempool.AddTransactionsUpdated(1); StopRPCThreads(); ShutdownRPCMining(); if (pwalletMain) @@ -478,7 +478,7 @@ bool AppInit2(boost::thread_group& threadGroup) InitWarning(_("Warning: Deprecated argument -debugnet ignored, use -debug=net")); fBenchmark = GetBoolArg("-benchmark", false); - mempool.fChecks = GetBoolArg("-checkmempool", RegTest()); + mempool.setSanityCheck(GetBoolArg("-checkmempool", RegTest())); Checkpoints::fEnabled = GetBoolArg("-checkpoints", true); // -par=0 means autodetect, but nScriptCheckThreads==0 means no concurrency diff --git a/src/limitedmap.h b/src/limitedmap.h index 7049d68e5a..01d1b07df4 100644 --- a/src/limitedmap.h +++ b/src/limitedmap.h @@ -4,6 +4,7 @@ #ifndef BITCOIN_LIMITEDMAP_H #define BITCOIN_LIMITEDMAP_H +#include <assert.h> // TODO: remove #include <map> #include <deque> diff --git a/src/main.cpp b/src/main.cpp index 4a4fcee34a..ecb9711664 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,18 +3,20 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <boost/algorithm/string/replace.hpp> +#include <boost/filesystem.hpp> +#include <boost/filesystem/fstream.hpp> + #include "alert.h" +#include "chainparams.h" #include "checkpoints.h" +#include "checkqueue.h" #include "db.h" -#include "txdb.h" -#include "net.h" #include "init.h" +#include "net.h" +#include "txdb.h" +#include "txmempool.h" #include "ui_interface.h" -#include "checkqueue.h" -#include "chainparams.h" -#include <boost/algorithm/string/replace.hpp> -#include <boost/filesystem.hpp> -#include <boost/filesystem/fstream.hpp> using namespace std; using namespace boost; @@ -29,7 +31,6 @@ set<CWallet*> setpwalletRegistered; CCriticalSection cs_main; CTxMemPool mempool; -unsigned int nTransactionsUpdated = 0; map<uint256, CBlockIndex*> mapBlockIndex; CChain chainActive; @@ -319,6 +320,15 @@ unsigned int CCoinsViewCache::GetCacheSize() { return cacheCoins.size(); } +/** Helper; lookup from tip (used calling mempool.check() + NOTE: code calling this MUST hold the cs_main lock so + another thread doesn't modify pcoinsTip. When we switch + to C++11 this should be replaced by lambda expressions... + **/ +static CCoins &LookupFromTip(const uint256& hash) { + return pcoinsTip->GetCoins(hash); +} + /** CCoinsView that brings transactions from a memorypool into view. It does not check for spendings by memory pool transactions. */ CCoinsViewMemPool::CCoinsViewMemPool(CCoinsView &baseIn, CTxMemPool &mempoolIn) : CCoinsViewBacked(baseIn), mempool(mempoolIn) { } @@ -326,8 +336,8 @@ CCoinsViewMemPool::CCoinsViewMemPool(CCoinsView &baseIn, CTxMemPool &mempoolIn) bool CCoinsViewMemPool::GetCoins(const uint256 &txid, CCoins &coins) { if (base->GetCoins(txid, coins)) return true; - if (mempool.exists(txid)) { - const CTransaction &tx = mempool.lookup(txid); + CTransaction tx; + if (mempool.lookup(txid, tx)) { coins = CCoins(tx, MEMPOOL_HEIGHT); return true; } @@ -734,56 +744,39 @@ int64 GetMinFee(const CTransaction& tx, bool fAllowFree, enum GetMinFee_mode mod return nMinFee; } -void CTxMemPool::pruneSpent(const uint256 &hashTx, CCoins &coins) -{ - LOCK(cs); - - std::map<COutPoint, CInPoint>::iterator it = mapNextTx.lower_bound(COutPoint(hashTx, 0)); - - // iterate over all COutPoints in mapNextTx whose hash equals the provided hashTx - while (it != mapNextTx.end() && it->first.hash == hashTx) { - coins.Spend(it->first.n); // and remove those outputs from coins - it++; - } -} -bool CTxMemPool::accept(CValidationState &state, const CTransaction &tx, bool fLimitFree, +bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree, bool* pfMissingInputs, bool fRejectInsaneFee) { if (pfMissingInputs) *pfMissingInputs = false; if (!CheckTransaction(tx, state)) - return error("CTxMemPool::accept() : CheckTransaction failed"); + return error("AcceptToMemoryPool: : CheckTransaction failed"); // Coinbase is only valid in a block, not as a loose transaction if (tx.IsCoinBase()) - return state.DoS(100, error("CTxMemPool::accept() : coinbase as individual tx")); - - // To help v0.1.5 clients who would see it as a negative number - if ((int64)tx.nLockTime > std::numeric_limits<int>::max()) - return error("CTxMemPool::accept() : not accepting nLockTime beyond 2038 yet"); + return state.DoS(100, error("AcceptToMemoryPool: : coinbase as individual tx")); // Rather not work on nonstandard transactions (unless -testnet/-regtest) string reason; if (Params().NetworkID() == CChainParams::MAIN && !IsStandardTx(tx, reason)) - return error("CTxMemPool::accept() : nonstandard transaction: %s", + return error("AcceptToMemoryPool: : nonstandard transaction: %s", reason.c_str()); // is it already in the memory pool? uint256 hash = tx.GetHash(); - { - LOCK(cs); - if (mapTx.count(hash)) - return false; - } + if (pool.exists(hash)) + return false; // Check for conflicts with in-memory transactions CTransaction* ptxOld = NULL; + { + LOCK(pool.cs); // protect pool.mapNextTx for (unsigned int i = 0; i < tx.vin.size(); i++) { COutPoint outpoint = tx.vin[i].prevout; - if (mapNextTx.count(outpoint)) + if (pool.mapNextTx.count(outpoint)) { // Disable replacement feature for now return false; @@ -791,7 +784,7 @@ bool CTxMemPool::accept(CValidationState &state, const CTransaction &tx, bool fL // Allow replacing with a newer version of the same transaction if (i != 0) return false; - ptxOld = mapNextTx[outpoint].ptx; + ptxOld = pool.mapNextTx[outpoint].ptx; if (IsFinalTx(*ptxOld)) return false; if (!tx.IsNewerThan(*ptxOld)) @@ -799,20 +792,21 @@ bool CTxMemPool::accept(CValidationState &state, const CTransaction &tx, bool fL for (unsigned int i = 0; i < tx.vin.size(); i++) { COutPoint outpoint = tx.vin[i].prevout; - if (!mapNextTx.count(outpoint) || mapNextTx[outpoint].ptx != ptxOld) + if (!pool.mapNextTx.count(outpoint) || pool.mapNextTx[outpoint].ptx != ptxOld) return false; } break; } } + } { CCoinsView dummy; CCoinsViewCache view(dummy); { - LOCK(cs); - CCoinsViewMemPool viewMemPool(*pcoinsTip, *this); + LOCK(pool.cs); + CCoinsViewMemPool viewMemPool(*pcoinsTip, pool); view.SetBackend(viewMemPool); // do we already have it? @@ -832,7 +826,7 @@ bool CTxMemPool::accept(CValidationState &state, const CTransaction &tx, bool fL // are the actual inputs available? if (!view.HaveInputs(tx)) - return state.Invalid(error("CTxMemPool::accept() : inputs already spent")); + return state.Invalid(error("AcceptToMemoryPool: : inputs already spent")); // Bring the best block into scope view.GetBestBlock(); @@ -843,7 +837,7 @@ bool CTxMemPool::accept(CValidationState &state, const CTransaction &tx, bool fL // Check for non-standard pay-to-script-hash in inputs if (Params().NetworkID() == CChainParams::MAIN && !AreInputsStandard(tx, view)) - return error("CTxMemPool::accept() : nonstandard transaction input"); + return error("AcceptToMemoryPool: : nonstandard transaction input"); // Note: if you modify this code to accept non-standard transactions, then // you should add code here to check that the transaction does a @@ -855,7 +849,7 @@ bool CTxMemPool::accept(CValidationState &state, const CTransaction &tx, bool fL // Don't accept it if it can't get into a block int64 txMinFee = GetMinFee(tx, true, GMF_RELAY); if (fLimitFree && nFees < txMinFee) - return error("CTxMemPool::accept() : not enough fees %s, %"PRI64d" < %"PRI64d, + return error("AcceptToMemoryPool: : not enough fees %s, %"PRI64d" < %"PRI64d, hash.ToString().c_str(), nFees, txMinFee); @@ -864,11 +858,12 @@ bool CTxMemPool::accept(CValidationState &state, const CTransaction &tx, bool fL // be annoying or make others' transactions take longer to confirm. if (fLimitFree && nFees < CTransaction::nMinRelayTxFee) { + static CCriticalSection csFreeLimiter; static double dFreeCount; static int64 nLastTime; int64 nNow = GetTime(); - LOCK(cs); + LOCK(csFreeLimiter); // Use an exponentially decaying ~10-minute window: dFreeCount *= pow(1.0 - 1.0/600.0, (double)(nNow - nLastTime)); @@ -876,14 +871,13 @@ bool CTxMemPool::accept(CValidationState &state, const CTransaction &tx, bool fL // -limitfreerelay unit is thousand-bytes-per-minute // At default rate it would take over a month to fill 1GB if (dFreeCount >= GetArg("-limitfreerelay", 15)*10*1000) - return error("CTxMemPool::accept() : free transaction rejected by rate limiter"); - if (fDebug) - LogPrint("mempool", "Rate limit dFreeCount: %g => %g\n", dFreeCount, dFreeCount+nSize); + return error("AcceptToMemoryPool: : free transaction rejected by rate limiter"); + LogPrint("mempool", "Rate limit dFreeCount: %g => %g\n", dFreeCount, dFreeCount+nSize); dFreeCount += nSize; } if (fRejectInsaneFee && nFees > CTransaction::nMinRelayTxFee * 10000) - return error("CTxMemPool::accept() : insane fees %s, %"PRI64d" > %"PRI64d, + return error("AcceptToMemoryPool: : insane fees %s, %"PRI64d" > %"PRI64d, hash.ToString().c_str(), nFees, CTransaction::nMinRelayTxFee * 10000); @@ -891,19 +885,18 @@ bool CTxMemPool::accept(CValidationState &state, const CTransaction &tx, bool fL // This is done last to help prevent CPU exhaustion denial-of-service attacks. if (!CheckInputs(tx, state, view, true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC)) { - return error("CTxMemPool::accept() : ConnectInputs failed %s", hash.ToString().c_str()); + return error("AcceptToMemoryPool: : ConnectInputs failed %s", hash.ToString().c_str()); } } // Store transaction in memory { - LOCK(cs); if (ptxOld) { - LogPrint("mempool", "CTxMemPool::accept() : replacing tx %s with new version\n", ptxOld->GetHash().ToString().c_str()); - remove(*ptxOld); + LogPrint("mempool", "AcceptToMemoryPool: : replacing tx %s with new version\n", ptxOld->GetHash().ToString().c_str()); + pool.remove(*ptxOld); } - addUnchecked(hash, tx); + pool.addUnchecked(hash, tx); } ///// are we sure this is ok when loading transactions or restoring block txes @@ -912,125 +905,12 @@ bool CTxMemPool::accept(CValidationState &state, const CTransaction &tx, bool fL g_signals.EraseTransaction(ptxOld->GetHash()); g_signals.SyncTransaction(hash, tx, NULL); - LogPrint("mempool", "CTxMemPool::accept() : accepted %s (poolsz %"PRIszu")\n", - hash.ToString().c_str(), - mapTx.size()); - return true; -} - - -bool CTxMemPool::addUnchecked(const uint256& hash, const CTransaction &tx) -{ - // Add to memory pool without checking anything. Don't call this directly, - // call CTxMemPool::accept to properly check the transaction first. - { - mapTx[hash] = tx; - for (unsigned int i = 0; i < tx.vin.size(); i++) - mapNextTx[tx.vin[i].prevout] = CInPoint(&mapTx[hash], i); - nTransactionsUpdated++; - } - return true; -} - - -bool CTxMemPool::remove(const CTransaction &tx, bool fRecursive) -{ - // Remove transaction from memory pool - { - LOCK(cs); - uint256 hash = tx.GetHash(); - if (fRecursive) { - for (unsigned int i = 0; i < tx.vout.size(); i++) { - std::map<COutPoint, CInPoint>::iterator it = mapNextTx.find(COutPoint(hash, i)); - if (it != mapNextTx.end()) - remove(*it->second.ptx, true); - } - } - if (mapTx.count(hash)) - { - BOOST_FOREACH(const CTxIn& txin, tx.vin) - mapNextTx.erase(txin.prevout); - mapTx.erase(hash); - nTransactionsUpdated++; - } - } - return true; -} - -bool CTxMemPool::removeConflicts(const CTransaction &tx) -{ - // Remove transactions which depend on inputs of tx, recursively - LOCK(cs); - BOOST_FOREACH(const CTxIn &txin, tx.vin) { - std::map<COutPoint, CInPoint>::iterator it = mapNextTx.find(txin.prevout); - if (it != mapNextTx.end()) { - const CTransaction &txConflict = *it->second.ptx; - if (txConflict != tx) - remove(txConflict, true); - } - } + LogPrint("mempool", "AcceptToMemoryPool: : accepted %s (poolsz %"PRIszu")\n", + hash.ToString().c_str(), + pool.mapTx.size()); return true; } -void CTxMemPool::clear() -{ - LOCK(cs); - mapTx.clear(); - mapNextTx.clear(); - ++nTransactionsUpdated; -} - -bool CTxMemPool::fChecks = false; - -void CTxMemPool::check(CCoinsViewCache *pcoins) const -{ - if (!fChecks) - return; - - LogPrintf("Checking mempool with %u transactions and %u inputs\n", (unsigned int)mapTx.size(), (unsigned int)mapNextTx.size()); - - LOCK(cs); - for (std::map<uint256, CTransaction>::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) { - unsigned int i = 0; - BOOST_FOREACH(const CTxIn &txin, it->second.vin) { - // Check that every mempool transaction's inputs refer to available coins, or other mempool tx's. - std::map<uint256, CTransaction>::const_iterator it2 = mapTx.find(txin.prevout.hash); - if (it2 != mapTx.end()) { - assert(it2->second.vout.size() > txin.prevout.n && !it2->second.vout[txin.prevout.n].IsNull()); - } else { - CCoins &coins = pcoins->GetCoins(txin.prevout.hash); - assert(coins.IsAvailable(txin.prevout.n)); - } - // Check whether its inputs are marked in mapNextTx. - std::map<COutPoint, CInPoint>::const_iterator it3 = mapNextTx.find(txin.prevout); - assert(it3 != mapNextTx.end()); - assert(it3->second.ptx == &it->second); - assert(it3->second.n == i); - i++; - } - } - for (std::map<COutPoint, CInPoint>::const_iterator it = mapNextTx.begin(); it != mapNextTx.end(); it++) { - uint256 hash = it->second.ptx->GetHash(); - std::map<uint256, CTransaction>::const_iterator it2 = mapTx.find(hash); - assert(it2 != mapTx.end()); - assert(&it2->second == it->second.ptx); - assert(it2->second.vin.size() > it->second.n); - assert(it->first == it->second.ptx->vin[it->second.n].prevout); - } -} - -void CTxMemPool::queryHashes(std::vector<uint256>& vtxid) -{ - vtxid.clear(); - - LOCK(cs); - vtxid.reserve(mapTx.size()); - for (map<uint256, CTransaction>::iterator mi = mapTx.begin(); mi != mapTx.end(); ++mi) - vtxid.push_back((*mi).first); -} - - - int CMerkleTx::GetDepthInMainChain(CBlockIndex* &pindexRet) const { @@ -1069,7 +949,7 @@ int CMerkleTx::GetBlocksToMaturity() const bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree) { CValidationState state; - return mempool.accept(state, *this, fLimitFree, NULL); + return ::AcceptToMemoryPool(mempool, state, *this, fLimitFree, NULL); } @@ -1080,10 +960,8 @@ bool GetTransaction(const uint256 &hash, CTransaction &txOut, uint256 &hashBlock { LOCK(cs_main); { - LOCK(mempool.cs); - if (mempool.exists(hash)) + if (mempool.lookup(hash, txOut)) { - txOut = mempool.lookup(hash); return true; } } @@ -1959,7 +1837,7 @@ bool ConnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, C bool SetBestChain(CValidationState &state, CBlockIndex* pindexNew) { - mempool.check(pcoinsTip); + mempool.check(&LookupFromTip); // All modifications to the coin state will be done in this cache. // Only when all have succeeded, we push it to pcoinsTip. @@ -2072,7 +1950,7 @@ bool SetBestChain(CValidationState &state, CBlockIndex* pindexNew) BOOST_FOREACH(CTransaction& tx, vResurrect) { // ignore validation errors in resurrected transactions CValidationState stateDummy; - if (!mempool.accept(stateDummy, tx, false, NULL)) + if (!AcceptToMemoryPool(mempool,stateDummy, tx, false, NULL)) mempool.remove(tx, true); } @@ -2082,7 +1960,7 @@ bool SetBestChain(CValidationState &state, CBlockIndex* pindexNew) mempool.removeConflicts(tx); } - mempool.check(pcoinsTip); + mempool.check(&LookupFromTip); // Update best block in wallet (so we can detect restored wallets) if ((pindexNew->nHeight % 20160) == 0 || (!fIsInitialDownload && (pindexNew->nHeight % 144) == 0)) @@ -2090,7 +1968,7 @@ bool SetBestChain(CValidationState &state, CBlockIndex* pindexNew) // New best block nTimeBestReceived = GetTime(); - nTransactionsUpdated++; + mempool.AddTransactionsUpdated(1); LogPrintf("SetBestChain: new best=%s height=%d log2_work=%.8g tx=%lu date=%s progress=%f\n", chainActive.Tip()->GetBlockHash().ToString().c_str(), chainActive.Height(), log(chainActive.Tip()->nChainWork.getdouble())/log(2.0), (unsigned long)pindexNew->nChainTx, DateTimeStrFormat("%Y-%m-%d %H:%M:%S", chainActive.Tip()->GetBlockTime()).c_str(), @@ -3170,10 +3048,7 @@ bool static AlreadyHave(const CInv& inv) case MSG_TX: { bool txInMap = false; - { - LOCK(mempool.cs); - txInMap = mempool.exists(inv.hash); - } + txInMap = mempool.exists(inv.hash); return txInMap || mapOrphanTransactions.count(inv.hash) || pcoinsTip->HaveCoins(inv.hash); } @@ -3264,9 +3139,8 @@ void static ProcessGetData(CNode* pfrom) } } if (!pushed && inv.type == MSG_TX) { - LOCK(mempool.cs); - if (mempool.exists(inv.hash)) { - CTransaction tx = mempool.lookup(inv.hash); + CTransaction tx; + if (mempool.lookup(inv.hash, tx)) { CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); ss.reserve(1000); ss << tx; @@ -3658,9 +3532,9 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) bool fMissingInputs = false; CValidationState state; - if (mempool.accept(state, tx, true, &fMissingInputs)) + if (AcceptToMemoryPool(mempool, state, tx, true, &fMissingInputs)) { - mempool.check(pcoinsTip); + mempool.check(&LookupFromTip); RelayTransaction(tx, inv.hash); mapAlreadyAskedFor.erase(inv); vWorkQueue.push_back(inv.hash); @@ -3682,7 +3556,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) // anyone relaying LegitTxX banned) CValidationState stateDummy; - if (mempool.accept(stateDummy, orphanTx, true, &fMissingInputs2)) + if (AcceptToMemoryPool(mempool, stateDummy, orphanTx, true, &fMissingInputs2)) { LogPrint("mempool", " accepted orphan tx %s\n", orphanHash.ToString().c_str()); RelayTransaction(orphanTx, orphanHash); @@ -3696,7 +3570,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) vEraseQueue.push_back(orphanHash); LogPrint("mempool", " removed orphan tx %s\n", orphanHash.ToString().c_str()); } - mempool.check(pcoinsTip); + mempool.check(&LookupFromTip); } } @@ -3753,15 +3627,17 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) else if (strCommand == "mempool") { - LOCK(cs_main); + LOCK2(cs_main, pfrom->cs_filter); std::vector<uint256> vtxid; - LOCK2(mempool.cs, pfrom->cs_filter); mempool.queryHashes(vtxid); vector<CInv> vInv; BOOST_FOREACH(uint256& hash, vtxid) { CInv inv(MSG_TX, hash); - if ((pfrom->pfilter && pfrom->pfilter->IsRelevantAndUpdate(mempool.lookup(hash), hash)) || + CTransaction tx; + bool fInMemPool = mempool.lookup(hash, tx); + if (!fInMemPool) continue; // another thread removed since queryHashes, maybe... + if ((pfrom->pfilter && pfrom->pfilter->IsRelevantAndUpdate(tx, hash)) || (!pfrom->pfilter)) vInv.push_back(inv); if (vInv.size() == MAX_INV_SZ) { diff --git a/src/main.h b/src/main.h index d568d8e6b8..80972330f3 100644 --- a/src/main.h +++ b/src/main.h @@ -12,6 +12,7 @@ #include "core.h" #include "bignum.h" #include "sync.h" +#include "txmempool.h" #include "net.h" #include "script.h" @@ -19,12 +20,11 @@ class CBlock; class CBlockIndex; -class CKeyItem; -class CReserveKey; - -class CAddress; class CInv; +class CKeyItem; class CNode; +class CReserveKey; +class CWallet; /** The maximum allowed size for a serialized block, in bytes (network rule) */ static const unsigned int MAX_BLOCK_SIZE = 1000000; @@ -70,8 +70,8 @@ extern CScript COINBASE_FLAGS; extern CCriticalSection cs_main; +extern CTxMemPool mempool; extern std::map<uint256, CBlockIndex*> mapBlockIndex; -extern unsigned int nTransactionsUpdated; extern uint64 nLastBlockTx; extern uint64 nLastBlockSize; extern const std::string strMessageMagic; @@ -175,7 +175,9 @@ bool VerifySignature(const CCoins& txFrom, const CTransaction& txTo, unsigned in /** Abort with a message */ bool AbortNode(const std::string &msg); - +/** (try to) add transaction to memory pool **/ +bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree, + bool* pfMissingInputs, bool fRejectInsaneFee=false); @@ -1029,44 +1031,6 @@ extern CChain chainActive; - - -class CTxMemPool -{ -public: - static bool fChecks; - mutable CCriticalSection cs; - std::map<uint256, CTransaction> mapTx; - std::map<COutPoint, CInPoint> mapNextTx; - - bool accept(CValidationState &state, const CTransaction &tx, bool fLimitFree, bool* pfMissingInputs, bool fRejectInsaneFee = false); - bool addUnchecked(const uint256& hash, const CTransaction &tx); - bool remove(const CTransaction &tx, bool fRecursive = false); - bool removeConflicts(const CTransaction &tx); - void clear(); - void queryHashes(std::vector<uint256>& vtxid); - void pruneSpent(const uint256& hash, CCoins &coins); - void check(CCoinsViewCache *pcoins) const; - - unsigned long size() - { - LOCK(cs); - return mapTx.size(); - } - - bool exists(uint256 hash) - { - return (mapTx.count(hash) != 0); - } - - CTransaction& lookup(uint256 hash) - { - return mapTx[hash]; - } -}; - -extern CTxMemPool mempool; - struct CCoinsStats { int nHeight; diff --git a/src/miner.cpp b/src/miner.cpp index dca8609e17..b03f915dc9 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -521,7 +521,7 @@ void static BitcoinMiner(CWallet *pwallet) // // Create new block // - unsigned int nTransactionsUpdatedLast = nTransactionsUpdated; + unsigned int nTransactionsUpdatedLast = mempool.GetTransactionsUpdated(); CBlockIndex* pindexPrev = chainActive.Tip(); auto_ptr<CBlockTemplate> pblocktemplate(CreateNewBlockWithKey(reservekey)); @@ -623,7 +623,7 @@ void static BitcoinMiner(CWallet *pwallet) break; if (nBlockNonce >= 0xffff0000) break; - if (nTransactionsUpdated != nTransactionsUpdatedLast && GetTime() - nStart > 60) + if (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - nStart > 60) break; if (pindexPrev != chainActive.Tip()) break; diff --git a/src/rpcmining.cpp b/src/rpcmining.cpp index 77dc13815d..d91f26e20a 100644 --- a/src/rpcmining.cpp +++ b/src/rpcmining.cpp @@ -190,7 +190,7 @@ Value getwork(const Array& params, bool fHelp) static int64 nStart; static CBlockTemplate* pblocktemplate; if (pindexPrev != chainActive.Tip() || - (nTransactionsUpdated != nTransactionsUpdatedLast && GetTime() - nStart > 60)) + (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - nStart > 60)) { if (pindexPrev != chainActive.Tip()) { @@ -205,7 +205,7 @@ Value getwork(const Array& params, bool fHelp) pindexPrev = NULL; // Store the pindexBest used before CreateNewBlock, to avoid races - nTransactionsUpdatedLast = nTransactionsUpdated; + nTransactionsUpdatedLast = mempool.GetTransactionsUpdated(); CBlockIndex* pindexPrevNew = chainActive.Tip(); nStart = GetTime(); @@ -326,13 +326,13 @@ Value getblocktemplate(const Array& params, bool fHelp) static int64 nStart; static CBlockTemplate* pblocktemplate; if (pindexPrev != chainActive.Tip() || - (nTransactionsUpdated != nTransactionsUpdatedLast && GetTime() - nStart > 5)) + (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - nStart > 5)) { // Clear pindexPrev so future calls make a new block, despite any failures from here on pindexPrev = NULL; // Store the pindexBest used before CreateNewBlock, to avoid races - nTransactionsUpdatedLast = nTransactionsUpdated; + nTransactionsUpdatedLast = mempool.GetTransactionsUpdated(); CBlockIndex* pindexPrevNew = chainActive.Tip(); nStart = GetTime(); diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp index 49987ecc47..4771094e1f 100644 --- a/src/rpcrawtransaction.cpp +++ b/src/rpcrawtransaction.cpp @@ -550,7 +550,7 @@ Value sendrawtransaction(const Array& params, bool fHelp) if (!fHave) { // push to local node CValidationState state; - if (!mempool.accept(state, tx, false, NULL, !fOverrideFees)) + if (!AcceptToMemoryPool(mempool, state, tx, false, NULL, !fOverrideFees)) throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX rejected"); // TODO: report validation state } } diff --git a/src/txmempool.cpp b/src/txmempool.cpp new file mode 100644 index 0000000000..975955a458 --- /dev/null +++ b/src/txmempool.cpp @@ -0,0 +1,162 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "core.h" +#include "txmempool.h" + +using namespace std; + +CTxMemPool::CTxMemPool() +{ + // Sanity checks off by default for performance, because otherwise + // accepting transactions becomes O(N^2) where N is the number + // of transactions in the pool + fSanityCheck = false; +} + +void CTxMemPool::pruneSpent(const uint256 &hashTx, CCoins &coins) +{ + LOCK(cs); + + std::map<COutPoint, CInPoint>::iterator it = mapNextTx.lower_bound(COutPoint(hashTx, 0)); + + // iterate over all COutPoints in mapNextTx whose hash equals the provided hashTx + while (it != mapNextTx.end() && it->first.hash == hashTx) { + coins.Spend(it->first.n); // and remove those outputs from coins + it++; + } +} + +unsigned int CTxMemPool::GetTransactionsUpdated() const +{ + LOCK(cs); + return nTransactionsUpdated; +} + +void CTxMemPool::AddTransactionsUpdated(unsigned int n) +{ + LOCK(cs); + nTransactionsUpdated += n; +} + + +bool CTxMemPool::addUnchecked(const uint256& hash, const CTransaction &tx) +{ + // Add to memory pool without checking anything. + // Used by main.cpp AcceptToMemoryPool(), which DOES do + // all the appropriate checks. + LOCK(cs); + { + mapTx[hash] = tx; + for (unsigned int i = 0; i < tx.vin.size(); i++) + mapNextTx[tx.vin[i].prevout] = CInPoint(&mapTx[hash], i); + nTransactionsUpdated++; + } + return true; +} + + +bool CTxMemPool::remove(const CTransaction &tx, bool fRecursive) +{ + // Remove transaction from memory pool + { + LOCK(cs); + uint256 hash = tx.GetHash(); + if (fRecursive) { + for (unsigned int i = 0; i < tx.vout.size(); i++) { + std::map<COutPoint, CInPoint>::iterator it = mapNextTx.find(COutPoint(hash, i)); + if (it != mapNextTx.end()) + remove(*it->second.ptx, true); + } + } + if (mapTx.count(hash)) + { + BOOST_FOREACH(const CTxIn& txin, tx.vin) + mapNextTx.erase(txin.prevout); + mapTx.erase(hash); + nTransactionsUpdated++; + } + } + return true; +} + +bool CTxMemPool::removeConflicts(const CTransaction &tx) +{ + // Remove transactions which depend on inputs of tx, recursively + LOCK(cs); + BOOST_FOREACH(const CTxIn &txin, tx.vin) { + std::map<COutPoint, CInPoint>::iterator it = mapNextTx.find(txin.prevout); + if (it != mapNextTx.end()) { + const CTransaction &txConflict = *it->second.ptx; + if (txConflict != tx) + remove(txConflict, true); + } + } + return true; +} + +void CTxMemPool::clear() +{ + LOCK(cs); + mapTx.clear(); + mapNextTx.clear(); + ++nTransactionsUpdated; +} + +void CTxMemPool::check(CTxMemPool::CoinLookupFunc fnLookup) const +{ + if (!fSanityCheck) + return; + + LogPrint("mempool", "Checking mempool with %u transactions and %u inputs\n", (unsigned int)mapTx.size(), (unsigned int)mapNextTx.size()); + + LOCK(cs); + for (std::map<uint256, CTransaction>::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) { + unsigned int i = 0; + BOOST_FOREACH(const CTxIn &txin, it->second.vin) { + // Check that every mempool transaction's inputs refer to available coins, or other mempool tx's. + std::map<uint256, CTransaction>::const_iterator it2 = mapTx.find(txin.prevout.hash); + if (it2 != mapTx.end()) { + assert(it2->second.vout.size() > txin.prevout.n && !it2->second.vout[txin.prevout.n].IsNull()); + } else { + CCoins &coins = (*fnLookup)(txin.prevout.hash); + assert(coins.IsAvailable(txin.prevout.n)); + } + // Check whether its inputs are marked in mapNextTx. + std::map<COutPoint, CInPoint>::const_iterator it3 = mapNextTx.find(txin.prevout); + assert(it3 != mapNextTx.end()); + assert(it3->second.ptx == &it->second); + assert(it3->second.n == i); + i++; + } + } + for (std::map<COutPoint, CInPoint>::const_iterator it = mapNextTx.begin(); it != mapNextTx.end(); it++) { + uint256 hash = it->second.ptx->GetHash(); + std::map<uint256, CTransaction>::const_iterator it2 = mapTx.find(hash); + assert(it2 != mapTx.end()); + assert(&it2->second == it->second.ptx); + assert(it2->second.vin.size() > it->second.n); + assert(it->first == it->second.ptx->vin[it->second.n].prevout); + } +} + +void CTxMemPool::queryHashes(std::vector<uint256>& vtxid) +{ + vtxid.clear(); + + LOCK(cs); + vtxid.reserve(mapTx.size()); + for (map<uint256, CTransaction>::iterator mi = mapTx.begin(); mi != mapTx.end(); ++mi) + vtxid.push_back((*mi).first); +} + +bool CTxMemPool::lookup(uint256 hash, CTransaction& result) const +{ + LOCK(cs); + std::map<uint256, CTransaction>::const_iterator i = mapTx.find(hash); + if (i == mapTx.end()) return false; + result = i->second; + return true; +} diff --git a/src/txmempool.h b/src/txmempool.h new file mode 100644 index 0000000000..1b555d99f4 --- /dev/null +++ b/src/txmempool.h @@ -0,0 +1,67 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +#ifndef BITCOIN_TXMEMPOOL_H +#define BITCOIN_TXMEMPOOL_H + +#include "core.h" + +/* + * CTxMemPool stores valid-according-to-the-current-best-chain + * transactions that may be included in the next block. + * + * Transactions are added when they are seen on the network + * (or created by the local node), but not all transactions seen + * are added to the pool: if a new transaction double-spends + * an input of a transaction in the pool, it is dropped, + * as are non-standard transactions. + */ +class CTxMemPool +{ +private: + bool fSanityCheck; // Normally false, true if -checkmempool or -regtest + unsigned int nTransactionsUpdated; + +public: + mutable CCriticalSection cs; + std::map<uint256, CTransaction> mapTx; + std::map<COutPoint, CInPoint> mapNextTx; + + CTxMemPool(); + + /* + * If sanity-checking is turned on, check makes sure the pool is + * consistent (does not contain two transactions that spend the same inputs, + * all inputs are in the mapNextTx array). If sanity-checking is turned off, + * check does nothing. + */ + typedef CCoins& (*CoinLookupFunc)(const uint256&); + void check(CoinLookupFunc fnLookup) const; + void setSanityCheck(bool _fSanityCheck) { fSanityCheck = _fSanityCheck; } + + bool addUnchecked(const uint256& hash, const CTransaction &tx); + bool remove(const CTransaction &tx, bool fRecursive = false); + bool removeConflicts(const CTransaction &tx); + void clear(); + void queryHashes(std::vector<uint256>& vtxid); + void pruneSpent(const uint256& hash, CCoins &coins); + unsigned int GetTransactionsUpdated() const; + void AddTransactionsUpdated(unsigned int n); + + unsigned long size() + { + LOCK(cs); + return mapTx.size(); + } + + bool exists(uint256 hash) + { + LOCK(cs); + return (mapTx.count(hash) != 0); + } + + bool lookup(uint256 hash, CTransaction& result) const; +}; + +#endif /* BITCOIN_TXMEMPOOL_H */ |