init: add -txsendrate configuration parameter
What changed, and why it matters
This commit adds a new debug-only command-line option called -txsendrate that lets node operators change how many transaction announcements per second Bitcoin Core sends to inbound peers. The default behavior stays the same (14 transactions per second), and the setting is clamped between 1 and 1000. It is intended for testing and low-risk tuning, not for normal user operation.
No security action required. Treat as a normal feature/review commit. Operators should note that -txsendrate is a debug option and should not be changed on production nodes without understanding the relay implications.
Security signals we found
Adds a new DEBUG_ONLY configuration knob for transaction inventory broadcast rate
Clamps user-supplied value to 1-1000 tx/s to prevent extreme misconfiguration
Replaces hardcoded constant with runtime option; default behavior unchanged
No validation, cryptography, or network parsing code is modified
Evidence from the diff
The patch introduces a configurable tx_send_rate in PeerManager::Options, replacing the previously hardcoded INVENTORY_BROADCAST_PER_SECOND (14). The value is read from the -txsendrate argument, clamped to [1, 1000], and used to initialize the inbound and outbound inventory token buckets in net_processing. The option is marked DEBUG_ONLY and lives in the NODE_RELAY category. No logic changes beyond parameterization are present.
Changed components
src/init.cpp (argument registration)src/net_processing.cpp (inventory bucket initialization)src/net_processing.h (PeerManager::Options and DEFAULT_TX_SEND_RATE)src/node/peerman_args.cpp (argument parsing and clamping)src/node/txdownloadman.h (comment reference update)Inspect captured patch +16 / −9
diff --git a/src/init.cpp b/src/init.cpp
index 290f0936..4eee0ac5 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -710,6 +710,10 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
OptionsCategory::NODE_RELAY);
argsman.AddArg("-minrelaytxfee=<amt>", strprintf("Fees (in %s/kvB) smaller than this are considered zero fee for relaying, mining and transaction creation (default: %s)",
CURRENCY_UNIT, FormatMoney(DEFAULT_MIN_RELAY_TX_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
+ argsman.AddArg("-txsendrate=<n>",
+ strprintf("Set the maximum ongoing rate for sending transactions to (inbound) peers (default: %u tx/s)",
+ DEFAULT_TX_SEND_RATE),
+ ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-privatebroadcast",
strprintf(
"Broadcast transactions submitted via sendrawtransaction RPC using short-lived "
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index bc90b0c5..56c79c2c 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -168,11 +168,6 @@ static constexpr auto INBOUND_INVENTORY_BROADCAST_INTERVAL{5s};
* Use a smaller delay as there is less privacy concern for them.
* Blocks and peers with NetPermissionFlags::NoBan permission bypass this. */
static constexpr auto OUTBOUND_INVENTORY_BROADCAST_INTERVAL{2s};
-/** Maximum rate of inventory items to send per second.
- * Limits the impact of low-fee transaction floods. */
-[[maybe_unused]] static constexpr unsigned int INVENTORY_BROADCAST_PER_SECOND{14};
-/** Target number of tx inventory items to send per transmission. */
-[[maybe_unused]] static constexpr unsigned int INVENTORY_BROADCAST_TARGET = INVENTORY_BROADCAST_PER_SECOND * count_seconds(INBOUND_INVENTORY_BROADCAST_INTERVAL);
/** Multiplier for the inventory bucket rate for outbounds */
static constexpr double OUTBOUND_INVENTORY_BUCKET_MULTIPLIER{Ticks<SecondsDouble>(INBOUND_INVENTORY_BROADCAST_INTERVAL) / Ticks<SecondsDouble>(OUTBOUND_INVENTORY_BROADCAST_INTERVAL)};
/** Delay between checking inventory bucket and backlog */
@@ -531,7 +526,7 @@ struct InvToSendBucket {
static constexpr double INBOUND_COUNT_SECONDS{30}; // cap/initial at 30s/mult worth of txs
InvToSendBucket(unsigned int rate, double mult)
- : count_floor{-1.0 * INVENTORY_BROADCAST_TARGET},
+ : count_floor{-1.0 * rate * count_seconds(INBOUND_INVENTORY_BROADCAST_INTERVAL)},
size_bucket(/*rate=*/SIZE_REFILL * mult, /*value=*/SIZE_INIT, /*cap=*/SIZE_CAP),
count_bucket(/*rate=*/rate * mult, /*value=*/rate * INBOUND_COUNT_SECONDS, /*cap=*/rate * INBOUND_COUNT_SECONDS)
{
@@ -2112,8 +2107,8 @@ PeerManagerImpl::PeerManagerImpl(CConnman& connman, AddrMan& addrman,
m_txdownloadman(node::TxDownloadOptions{pool, m_rng, opts.deterministic_rng}),
m_warnings{warnings},
m_opts{opts},
- m_inbound_inv_bucket(/*rate=*/INVENTORY_BROADCAST_PER_SECOND, /*mult=*/1.0),
- m_outbound_inv_bucket(/*rate=*/INVENTORY_BROADCAST_PER_SECOND, /*mult=*/OUTBOUND_INVENTORY_BUCKET_MULTIPLIER)
+ m_inbound_inv_bucket(/*rate=*/m_opts.tx_send_rate, /*mult=*/1.0),
+ m_outbound_inv_bucket(/*rate=*/m_opts.tx_send_rate, /*mult=*/OUTBOUND_INVENTORY_BUCKET_MULTIPLIER)
{
// While Erlay support is incomplete, it must be enabled explicitly via -txreconciliation.
// This argument can go away after Erlay support is complete.
diff --git a/src/net_processing.h b/src/net_processing.h
index f29adc01..8e6c4b87 100644
--- a/src/net_processing.h
+++ b/src/net_processing.h
@@ -42,6 +42,8 @@ static constexpr bool DEFAULT_TXRECONCILIATION_ENABLE{false};
/** Default number of non-mempool transactions to keep around for block reconstruction. Includes
orphan, replaced, and rejected transactions. */
static const uint32_t DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN{100};
+/** Default maximum per-second rate for sending transaction inventory to peers. */
+static constexpr unsigned int DEFAULT_TX_SEND_RATE{14};
static const bool DEFAULT_PEERBLOOMFILTERS = false;
static const bool DEFAULT_PEERBLOCKFILTERS = false;
/** Maximum number of outstanding CMPCTBLOCK requests for the same block. */
@@ -96,6 +98,8 @@ public:
uint32_t max_headers_result{MAX_HEADERS_RESULTS};
//! Whether private broadcast is used for sending transactions.
bool private_broadcast{DEFAULT_PRIVATE_BROADCAST};
+ //! Maximum per-second rate for sending transaction inventory to peers.
+ unsigned int tx_send_rate{DEFAULT_TX_SEND_RATE};
};
static std::unique_ptr<PeerManager> make(CConnman& connman, AddrMan& addrman,
diff --git a/src/node/peerman_args.cpp b/src/node/peerman_args.cpp
index 9745d69d..47413af0 100644
--- a/src/node/peerman_args.cpp
+++ b/src/node/peerman_args.cpp
@@ -24,6 +24,10 @@ void ApplyArgsManOptions(const ArgsManager& argsman, PeerManager::Options& optio
if (auto value{argsman.GetBoolArg("-blocksonly")}) options.ignore_incoming_txs = *value;
+ if (auto value{argsman.GetIntArg("-txsendrate")}) {
+ options.tx_send_rate = uint32_t(std::clamp<int64_t>(*value, 1, 1000));
+ }
+
if (auto value{argsman.GetBoolArg("-privatebroadcast")}) options.private_broadcast = *value;
}
diff --git a/src/node/txdownloadman.h b/src/node/txdownloadman.h
index 2cc1ec2c..bef1d162 100644
--- a/src/node/txdownloadman.h
+++ b/src/node/txdownloadman.h
@@ -25,7 +25,7 @@ class TxDownloadManagerImpl;
static constexpr int32_t MAX_PEER_TX_REQUEST_IN_FLIGHT = 100;
/** Maximum number of transactions to consider for requesting, per peer. It provides a reasonable DoS limit to
* per-peer memory usage spent on announcements, while covering peers continuously sending INVs at the maximum
- * rate (by our own policy, see INVENTORY_BROADCAST_PER_SECOND) for several minutes, while not receiving
+ * rate (by our own policy, see DEFAULT_TX_SEND_RATE) for several minutes, while not receiving
* the actual transaction (from any peer) in response to requests for them. */
static constexpr int32_t MAX_PEER_TX_ANNOUNCEMENTS = 5000;
/** How long to delay requesting transactions via txids, if we have wtxid-relaying peers */
Why this scored 19/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.