diff --git a/src/init.cpp b/src/init.cpp index 57048ba491ba..5dfc31eeb4cb 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -459,7 +459,7 @@ std::string HelpMessage(HelpMessageMode mode) } strUsage += HelpMessageOpt("-dbcache=", strprintf(_("Set database cache size in megabytes (%d to %d, default: %d)"), nMinDbCache, nMaxDbCache, nDefaultDbCache)); strUsage += HelpMessageOpt("-loadblock=", _("Imports blocks from external blk000??.dat file on startup")); - strUsage += HelpMessageOpt("-maxorphantx=", strprintf(_("Keep at most unconnectable transactions in memory (default: %u)"), DEFAULT_MAX_ORPHAN_TRANSACTIONS)); + strUsage += HelpMessageOpt("-maxorphantxsize=", strprintf(_("Maximum total size of all orphan transactions in megabytes (default: %u)"), DEFAULT_MAX_ORPHAN_TRANSACTIONS_SIZE)); strUsage += HelpMessageOpt("-maxmempool=", strprintf(_("Keep the transaction memory pool below megabytes (default: %u)"), DEFAULT_MAX_MEMPOOL_SIZE)); strUsage += HelpMessageOpt("-mempoolexpiry=", strprintf(_("Do not keep transactions in the mempool longer than hours (default: %u)"), DEFAULT_MEMPOOL_EXPIRY)); strUsage += HelpMessageOpt("-persistmempool", strprintf(_("Whether to save the mempool on shutdown and load on restart (default: %u)"), DEFAULT_PERSIST_MEMPOOL)); @@ -1416,6 +1416,10 @@ bool AppInitParameterInteraction() return InitError("LLMQ type for ChainLocks can only be overridden on devnet."); } + if (gArgs.IsArgSet("-maxorphantx")) { + InitWarning("-maxorphantx is not supported anymore. Use -maxorphantxsize instead."); + } + return true; } diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 2d3dc4e8d06f..17ad6732db51 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -73,10 +73,12 @@ struct COrphanTx { CTransactionRef tx; NodeId fromPeer; int64_t nTimeExpire; + size_t nTxSize; }; static CCriticalSection g_cs_orphans; std::map mapOrphanTransactions GUARDED_BY(g_cs_orphans); std::map::iterator, IteratorComparator>> mapOrphanTransactionsByPrev GUARDED_BY(g_cs_orphans); +size_t nMapOrphanTransactionsSize = 0; void EraseOrphansFor(NodeId peer); static size_t vExtraTxnForCompactIt GUARDED_BY(g_cs_orphans) = 0; @@ -641,7 +643,7 @@ bool AddOrphanTx(const CTransactionRef& tx, NodeId peer) EXCLUSIVE_LOCKS_REQUIRE return false; } - auto ret = mapOrphanTransactions.emplace(hash, COrphanTx{tx, peer, GetTime() + ORPHAN_TX_EXPIRE_TIME}); + auto ret = mapOrphanTransactions.emplace(hash, COrphanTx{tx, peer, GetTime() + ORPHAN_TX_EXPIRE_TIME, sz}); assert(ret.second); for (const CTxIn& txin : tx->vin) { mapOrphanTransactionsByPrev[txin.prevout].insert(ret.first); @@ -649,6 +651,8 @@ bool AddOrphanTx(const CTransactionRef& tx, NodeId peer) EXCLUSIVE_LOCKS_REQUIRE AddToCompactExtraTransactions(tx); + nMapOrphanTransactionsSize += sz; + LogPrint(BCLog::MEMPOOL, "stored orphan tx %s (mapsz %u outsz %u)\n", hash.ToString(), mapOrphanTransactions.size(), mapOrphanTransactionsByPrev.size()); return true; @@ -668,6 +672,8 @@ int static EraseOrphanTx(uint256 hash) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) if (itPrev->second.empty()) mapOrphanTransactionsByPrev.erase(itPrev); } + assert(nMapOrphanTransactionsSize >= it->second.nTxSize); + nMapOrphanTransactionsSize -= it->second.nTxSize; mapOrphanTransactions.erase(it); return 1; } @@ -689,7 +695,7 @@ void EraseOrphansFor(NodeId peer) } -unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans) +unsigned int LimitOrphanTxSize(unsigned int nMaxOrphansSize) { LOCK(g_cs_orphans); @@ -714,7 +720,7 @@ unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans) nNextSweep = nMinExpTime + ORPHAN_TX_EXPIRE_INTERVAL; if (nErased > 0) LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx due to expiration\n", nErased); } - while (mapOrphanTransactions.size() > nMaxOrphans) + while (!mapOrphanTransactions.empty() && nMapOrphanTransactionsSize > nMaxOrphansSize) { // Evict a random orphan: uint256 randomhash = GetRandHash(); @@ -2221,8 +2227,8 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr AddOrphanTx(ptx, pfrom->GetId()); // DoS prevention: do not allow mapOrphanTransactions to grow unbounded - unsigned int nMaxOrphanTx = (unsigned int)std::max((int64_t)0, gArgs.GetArg("-maxorphantx", DEFAULT_MAX_ORPHAN_TRANSACTIONS)); - unsigned int nEvicted = LimitOrphanTxSize(nMaxOrphanTx); + unsigned int nMaxOrphanTxSize = (unsigned int)std::max((int64_t)0, gArgs.GetArg("-maxorphantxsize", DEFAULT_MAX_ORPHAN_TRANSACTIONS_SIZE)) * 1000000; + unsigned int nEvicted = LimitOrphanTxSize(nMaxOrphanTxSize); if (nEvicted > 0) { LogPrint(BCLog::MEMPOOL, "mapOrphan overflow, removed %u tx\n", nEvicted); } @@ -3658,5 +3664,6 @@ class CNetProcessingCleanup // orphan transactions mapOrphanTransactions.clear(); mapOrphanTransactionsByPrev.clear(); + nMapOrphanTransactionsSize = 0; } } instance_of_cnetprocessingcleanup; diff --git a/src/net_processing.h b/src/net_processing.h index b6c0e8ef6436..6125a661501e 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -9,8 +9,8 @@ #include "net.h" #include "validationinterface.h" -/** Default for -maxorphantx, maximum number of orphan transactions kept in memory */ -static const unsigned int DEFAULT_MAX_ORPHAN_TRANSACTIONS = 100; +/** Default for -maxorphantxsize, maximum size in megabytes the orphan map can grow before entries are removed */ +static const unsigned int DEFAULT_MAX_ORPHAN_TRANSACTIONS_SIZE = 10; // this allows around 100 TXs of max size (and many more of normal size) /** Expiration time for orphan transactions in seconds */ static const int64_t ORPHAN_TX_EXPIRE_TIME = 20 * 60; /** Minimum time between orphan transactions expire time checks in seconds */ diff --git a/test/functional/mempool_packages.py b/test/functional/mempool_packages.py index e5d6ce31cc7f..bdb308f3341b 100755 --- a/test/functional/mempool_packages.py +++ b/test/functional/mempool_packages.py @@ -14,7 +14,7 @@ class MempoolPackagesTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 - self.extra_args = [["-maxorphantx=1000"], ["-maxorphantx=1000", "-limitancestorcount=5"]] + self.extra_args = [["-maxorphantxsize=1000"], ["-maxorphantxsize=1000", "-limitancestorcount=5"]] # Build a transaction that spends parent_txid:vout # Return amount sent diff --git a/test/functional/smartfees.py b/test/functional/smartfees.py index b8d3529bb257..996c6bbd7e08 100755 --- a/test/functional/smartfees.py +++ b/test/functional/smartfees.py @@ -150,9 +150,9 @@ def setup_network(self): But first we need to use one node to create a lot of outputs which we will use to generate our transactions. """ - self.add_nodes(3, extra_args=[["-maxorphantx=1000", "-whitelist=127.0.0.1"], - ["-blockmaxsize=17000", "-maxorphantx=1000"], - ["-blockmaxsize=8000", "-maxorphantx=1000"]]) + self.add_nodes(3, extra_args=[["-maxorphantxsize=1000", "-whitelist=127.0.0.1"], + ["-blockmaxsize=17000", "-maxorphantxsize=1000"], + ["-blockmaxsize=8000", "-maxorphantxsize=1000"]]) # Use node0 to mine blocks for input splitting # Node1 mines small blocks but that are bigger than the expected transaction rate. # NOTE: the CreateNewBlock code starts counting block size at 1,000 bytes,