Add new (unused) limits for cluster size/count
What changed, and why it matters
This commit adds two new command-line options, -limitclustercount and -limitclustersize, and corresponding data fields for mempool limits. However, the commit title and code make clear these new limits are currently unused: no enforcement logic consumes them. It is purely preparatory infrastructure for future mempool policy changes. There is no immediate security impact.
No action required. Treat as normal development/refactoring. Monitor follow-up commits that actually wire these limits into mempool acceptance or eviction logic, as those may carry security relevance.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch introduces cluster_count and cluster_size_vbytes into kernel::MemPoolLimits, exposes -limitclustercount and -limitclustersize as debug/test arguments, parses them in ApplyArgsManOptions, and adds a maximum bound check for cluster_count. The new fields are not referenced anywhere in validation, mempool acceptance, or eviction logic, so they cannot affect transaction processing today. The change is a forward-looking refactor.
Changed components
src/init.cppsrc/kernel/mempool_limits.hsrc/node/mempool_args.cppsrc/policy/policy.hInspect captured patch +20 / −1
diff --git a/src/init.cpp b/src/init.cpp
index dadd56d2..73222961 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -641,6 +641,8 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
argsman.AddArg("-limitdescendantcount=<n>", strprintf("Do not accept transactions if any ancestor would have <n> or more in-mempool descendants (default: %u)", DEFAULT_DESCENDANT_LIMIT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-limitdescendantsize=<n>", strprintf("Do not accept transactions if any ancestor would have more than <n> kilobytes of in-mempool descendants (default: %u).", DEFAULT_DESCENDANT_SIZE_LIMIT_KVB), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-test=<option>", "Pass a test-only option. Options include : " + Join(TEST_OPTIONS_DOC, ", ") + ".", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
+ argsman.AddArg("-limitclustercount=<n>", strprintf("Do not accept transactions into mempool which are directly or indirectly connected to <n> or more other unconfirmed transactions (default: %u, maximum: %u)", DEFAULT_CLUSTER_LIMIT, MAX_CLUSTER_COUNT_LIMIT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
+ argsman.AddArg("-limitclustersize=<n>", strprintf("Do not accept transactions whose virtual size with all in-mempool connected transactions exceeds <n> kilobytes (default: %u)", DEFAULT_CLUSTER_SIZE_LIMIT_KVB), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-capturemessages", "Capture all P2P messages to disk", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-mocktime=<n>", "Replace actual time with " + UNIX_EPOCH_TIME + " (default: 0)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-maxsigcachesize=<n>", strprintf("Limit sum of signature cache and script execution cache sizes to <n> MiB (default: %u)", DEFAULT_VALIDATION_CACHE_BYTES >> 20), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
diff --git a/src/kernel/mempool_limits.h b/src/kernel/mempool_limits.h
index 8d4495c3..cbca1790 100644
--- a/src/kernel/mempool_limits.h
+++ b/src/kernel/mempool_limits.h
@@ -16,6 +16,10 @@ namespace kernel {
* Most of the time, this struct should be referenced as CTxMemPool::Limits.
*/
struct MemPoolLimits {
+ //! The maximum number of transactions in a cluster
+ unsigned cluster_count{DEFAULT_CLUSTER_LIMIT};
+ //! The maximum allowed size in virtual bytes of a cluster.
+ int64_t cluster_size_vbytes{DEFAULT_CLUSTER_SIZE_LIMIT_KVB * 1'000};
//! The maximum allowed number of transactions in a package including the entry and its ancestors.
int64_t ancestor_count{DEFAULT_ANCESTOR_LIMIT};
//! The maximum allowed size in virtual bytes of an entry and its ancestors within a package.
@@ -31,7 +35,7 @@ struct MemPoolLimits {
static constexpr MemPoolLimits NoLimits()
{
int64_t no_limit{std::numeric_limits<int64_t>::max()};
- return {no_limit, no_limit, no_limit, no_limit};
+ return {std::numeric_limits<unsigned>::max(), no_limit, no_limit, no_limit, no_limit, no_limit};
}
};
} // namespace kernel
diff --git a/src/node/mempool_args.cpp b/src/node/mempool_args.cpp
index abbe97d9..93b897d5 100644
--- a/src/node/mempool_args.cpp
+++ b/src/node/mempool_args.cpp
@@ -15,6 +15,7 @@
#include <policy/feerate.h>
#include <policy/policy.h>
#include <tinyformat.h>
+#include <txgraph.h>
#include <util/moneystr.h>
#include <util/translation.h>
@@ -31,6 +32,10 @@ static constexpr int MAX_32BIT_MEMPOOL_MB{500};
namespace {
void ApplyArgsManOptions(const ArgsManager& argsman, MemPoolLimits& mempool_limits)
{
+ mempool_limits.cluster_count = argsman.GetIntArg("-limitclustercount", mempool_limits.cluster_count);
+
+ if (auto vkb = argsman.GetIntArg("-limitclustersize")) mempool_limits.cluster_size_vbytes = *vkb * 1'000;
+
mempool_limits.ancestor_count = argsman.GetIntArg("-limitancestorcount", mempool_limits.ancestor_count);
if (auto vkb = argsman.GetIntArg("-limitancestorsize")) mempool_limits.ancestor_size_vbytes = *vkb * 1'000;
@@ -106,5 +111,9 @@ util::Result<void> ApplyArgsManOptions(const ArgsManager& argsman, const CChainP
ApplyArgsManOptions(argsman, mempool_opts.limits);
+ if (mempool_opts.limits.cluster_count > MAX_CLUSTER_COUNT_LIMIT) {
+ return util::Error{Untranslated(strprintf("limitclustercount must be less than or equal to %d", MAX_CLUSTER_COUNT_LIMIT))};
+ }
+
return {};
}
diff --git a/src/policy/policy.h b/src/policy/policy.h
index 0e4314ea..2a15f29c 100644
--- a/src/policy/policy.h
+++ b/src/policy/policy.h
@@ -65,6 +65,10 @@ static constexpr unsigned int MAX_STANDARD_SCRIPTSIG_SIZE{1650};
static constexpr unsigned int DUST_RELAY_TX_FEE{3000};
/** Default for -minrelaytxfee, minimum relay fee for transactions */
static constexpr unsigned int DEFAULT_MIN_RELAY_TX_FEE{100};
+/** Maximum number of transactions per cluster (default) */
+static constexpr unsigned int DEFAULT_CLUSTER_LIMIT{64};
+/** Maximum size of cluster in virtual kilobytes */
+static constexpr unsigned int DEFAULT_CLUSTER_SIZE_LIMIT_KVB{101};
/** Default for -limitancestorcount, max number of in-mempool ancestors */
static constexpr unsigned int DEFAULT_ANCESTOR_LIMIT{25};
/** Default for -limitancestorsize, maximum kilobytes of tx + all in-mempool ancestors */
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.