diff --git a/doc/REST-interface.md b/doc/REST-interface.md --- a/doc/REST-interface.md +++ b/doc/REST-interface.md @@ -84,6 +84,7 @@ Returns various information about the TX mempool. Only supports JSON as output format. +* loaded : (boolean) if the mempool is fully loaded * size : (numeric) the number of transactions in the TX mempool * bytes : (numeric) size of the TX mempool in bytes * usage : (numeric) total TX mempool memory usage diff --git a/src/init.cpp b/src/init.cpp --- a/src/init.cpp +++ b/src/init.cpp @@ -237,9 +237,9 @@ g_banman.reset(); g_txindex.reset(); - if (g_is_mempool_loaded && + if (::g_mempool.IsLoaded() && gArgs.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) { - DumpMempool(); + DumpMempool(::g_mempool); } // FlushStateToDisk generates a ChainStateFlushed callback, which we should @@ -1207,9 +1207,9 @@ } } // End scope of CImportingNow if (gArgs.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) { - LoadMempool(config); + LoadMempool(config, ::g_mempool); } - g_is_mempool_loaded = !fRequestShutdown; + ::g_mempool.SetIsLoaded(!fRequestShutdown); } /** Sanity checks diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1487,6 +1487,7 @@ UniValue MempoolInfoToJSON(const CTxMemPool &pool) { UniValue ret(UniValue::VOBJ); + ret.pushKV("loaded", pool.IsLoaded()); ret.pushKV("size", (int64_t)pool.size()); ret.pushKV("bytes", (int64_t)pool.GetTotalTxSize()); ret.pushKV("usage", (int64_t)pool.DynamicMemoryUsage()); @@ -1510,6 +1511,8 @@ "\nReturns details on the active state of the TX memory pool.\n" "\nResult:\n" "{\n" + " \"loaded\": true|false (boolean) True if the mempool is " + "fully loaded\n" " \"size\": xxxxx, (numeric) Current tx count\n" " \"bytes\": xxxxx, (numeric) Transaction size.\n" " \"usage\": xxxxx, (numeric) Total memory usage for " @@ -2197,11 +2200,11 @@ HelpExampleRpc("savemempool", "")); } - if (!g_is_mempool_loaded) { + if (!::g_mempool.IsLoaded()) { throw JSONRPCError(RPC_MISC_ERROR, "The mempool was not loaded yet"); } - if (!DumpMempool()) { + if (!DumpMempool(::g_mempool)) { throw JSONRPCError(RPC_MISC_ERROR, "Unable to dump mempool to disk"); } diff --git a/src/txmempool.h b/src/txmempool.h --- a/src/txmempool.h +++ b/src/txmempool.h @@ -496,6 +496,8 @@ void trackPackageRemoved(const CFeeRate &rate) EXCLUSIVE_LOCKS_REQUIRED(cs); + bool m_is_loaded GUARDED_BY(cs){false}; + public: // public only for testing static const int ROLLING_FEE_HALFLIFE = 60 * 60 * 12; @@ -753,6 +755,12 @@ void GetTransactionAncestry(const uint256 &txid, size_t &ancestors, size_t &descendants) const; + /** @returns true if the mempool is fully loaded */ + bool IsLoaded() const; + + /** Sets the current loaded state */ + void SetIsLoaded(bool loaded); + unsigned long size() const { LOCK(cs); return mapTx.size(); diff --git a/src/txmempool.cpp b/src/txmempool.cpp --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -1238,6 +1238,16 @@ } } +bool CTxMemPool::IsLoaded() const { + LOCK(cs); + return m_is_loaded; +} + +void CTxMemPool::SetIsLoaded(bool loaded) { + LOCK(cs); + m_is_loaded = loaded; +} + SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand(std::numeric_limits::max())), k1(GetRand(std::numeric_limits::max())) {} diff --git a/src/validation.h b/src/validation.h --- a/src/validation.h +++ b/src/validation.h @@ -211,7 +211,6 @@ extern CScript COINBASE_FLAGS; extern CCriticalSection cs_main; extern CTxMemPool g_mempool; -extern std::atomic_bool g_is_mempool_loaded; extern uint64_t nLastBlockTx; extern uint64_t nLastBlockSize; extern const std::string strMessageMagic; @@ -734,9 +733,9 @@ CBlockFileInfo *GetBlockFileInfo(size_t n); /** Dump the mempool to disk. */ -bool DumpMempool(); +bool DumpMempool(const CTxMemPool &pool); /** Load the mempool from disk. */ -bool LoadMempool(const Config &config); +bool LoadMempool(const Config &config, CTxMemPool &pool); #endif // BITCOIN_VALIDATION_H diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -271,7 +271,6 @@ Amount maxTxFee = DEFAULT_TRANSACTION_MAXFEE; CTxMemPool g_mempool; -std::atomic_bool g_is_mempool_loaded{false}; /** Constant stuff for coinbase transactions we create: */ CScript COINBASE_FLAGS; @@ -5602,7 +5601,7 @@ static const uint64_t MEMPOOL_DUMP_VERSION = 1; -bool LoadMempool(const Config &config) { +bool LoadMempool(const Config &config, CTxMemPool &pool) { int64_t nExpiryTimeout = gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 * 60; FILE *filestr = fsbridge::fopen(GetDataDir() / "mempool.dat", "rb"); @@ -5639,14 +5638,14 @@ Amount amountdelta = nFeeDelta * SATOSHI; if (amountdelta != Amount::zero()) { - g_mempool.PrioritiseTransaction(tx->GetId(), prioritydummy, - amountdelta); + pool.PrioritiseTransaction(tx->GetId(), prioritydummy, + amountdelta); } CValidationState state; if (nTime + nExpiryTimeout > nNow) { LOCK(cs_main); AcceptToMemoryPoolWithTime( - config, g_mempool, state, tx, true /* fLimitFree */, + config, pool, state, tx, true /* fLimitFree */, nullptr /* pfMissingInputs */, nTime, false /* fOverrideMempoolLimit */, Amount::zero() /* nAbsurdFee */, false /* test_accept */); @@ -5657,7 +5656,7 @@ // wallet(s) having loaded it while we were processing // mempool transactions; consider these as valid, instead of // failed, but mark them as 'already there' - if (g_mempool.exists(tx->GetHash())) { + if (pool.exists(tx->GetHash())) { ++already_there; } else { ++failed; @@ -5675,7 +5674,7 @@ file >> mapDeltas; for (const auto &i : mapDeltas) { - g_mempool.PrioritiseTransaction(i.first, prioritydummy, i.second); + pool.PrioritiseTransaction(i.first, prioritydummy, i.second); } } catch (const std::exception &e) { LogPrintf("Failed to deserialize mempool data on disk: %s. Continuing " @@ -5690,7 +5689,7 @@ return true; } -bool DumpMempool() { +bool DumpMempool(const CTxMemPool &pool) { int64_t start = GetTimeMicros(); std::map mapDeltas; @@ -5700,12 +5699,12 @@ LOCK(dump_mutex); { - LOCK(g_mempool.cs); - for (const auto &i : g_mempool.mapDeltas) { + LOCK(pool.cs); + for (const auto &i : pool.mapDeltas) { mapDeltas[i.first] = i.second.second; } - vinfo = g_mempool.infoAll(); + vinfo = pool.infoAll(); } int64_t mid = GetTimeMicros(); diff --git a/test/functional/mempool_persist.py b/test/functional/mempool_persist.py --- a/test/functional/mempool_persist.py +++ b/test/functional/mempool_persist.py @@ -37,7 +37,6 @@ """ from decimal import Decimal import os -import time from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -81,9 +80,10 @@ self.start_node(1) self.start_node(0) self.start_node(2) - # Give bitcoind a second to reload the mempool - wait_until(lambda: len(self.nodes[0].getrawmempool()) == 5, timeout=1) - wait_until(lambda: len(self.nodes[2].getrawmempool()) == 5, timeout=1) + wait_until(lambda: self.nodes[0].getmempoolinfo()["loaded"], timeout=1) + wait_until(lambda: self.nodes[2].getmempoolinfo()["loaded"], timeout=1) + assert_equal(len(self.nodes[0].getrawmempool()), 5) + assert_equal(len(self.nodes[2].getrawmempool()), 5) # The others have loaded their mempool. If node_1 loaded anything, we'd probably notice by now: assert_equal(len(self.nodes[1].getrawmempool()), 0) @@ -96,15 +96,15 @@ "Stop-start node0 with -persistmempool=0. Verify that it doesn't load its mempool.dat file.") self.stop_nodes() self.start_node(0, extra_args=["-persistmempool=0"]) - # Give bitcoind a second to reload the mempool - time.sleep(1) + wait_until(lambda: self.nodes[0].getmempoolinfo()["loaded"]) assert_equal(len(self.nodes[0].getrawmempool()), 0) self.log.debug( "Stop-start node0. Verify that it has the transactions in its mempool.") self.stop_nodes() self.start_node(0) - wait_until(lambda: len(self.nodes[0].getrawmempool()) == 5) + wait_until(lambda: self.nodes[0].getmempoolinfo()["loaded"]) + assert_equal(len(self.nodes[0].getrawmempool()), 5) mempooldat0 = os.path.join( self.nodes[0].datadir, 'regtest', 'mempool.dat') @@ -121,7 +121,8 @@ os.rename(mempooldat0, mempooldat1) self.stop_nodes() self.start_node(1, extra_args=[]) - wait_until(lambda: len(self.nodes[1].getrawmempool()) == 5) + wait_until(lambda: self.nodes[1].getmempoolinfo()["loaded"]) + assert_equal(len(self.nodes[1].getrawmempool()), 5) self.log.debug( "Prevent bitcoind from writing mempool.dat to disk. Verify that `savemempool` fails")