net_processing: Remove per-peer rate-limiting
What changed, and why it matters
This Bitcoin Core change removes a per-peer speed limit on how many transaction announcements (INV messages) a node sends out at once. Previously a peer would only announce up to about 1,000 transactions per batch, with a dynamic cap. Now the node empties the whole outgoing queue every time, so a single INV message can grow as large as the protocol allows (about 50,000 items). The commit message says this avoids storage/compute costs and past severe bugs tied to per-peer rate limiting, but it also increases the maximum announcement burst a peer can receive.
Treat as a notable network-behavior change rather than a confirmed vulnerability. Review whether larger INV bursts affect downstream peer bandwidth, memory, or mempool-flood handling. Monitor for follow-up fixes or discussions on the bitcoin-core-dev mailing list and PR tracker. No immediate patch action is indicated from the diff alone.
Security signals we found
Removal of a rate-limiting control that constrained per-peer transaction announcement bursts
Increase in maximum single INV message size from ~1,000 to ~50,000 items
Commit message explicitly references 'severe bugs in the past' caused by per-peer rate-limiting logic
Change reduces per-peer state/compute overhead, which is the stated defensive goal
Evidence from the diff
The patch in src/net_processing.cpp eliminates per-peer transaction-inventory rate limiting. Constants INVENTORY_BROADCAST_PER_SECOND and INVENTORY_BROADCAST_TARGET are kept but marked [[maybe_unused]]; INVENTORY_BROADCAST_MAX and its static_asserts are removed. In PeerManagerImpl::SendMessages, the loop that relayed transactions now runs until vInvTx is empty instead of stopping at a computed broadcast_max, and the reserve size is capped only by MAX_INV_SZ. The practical effect is that a peer may receive INV messages up to MAX_INV_SZ (~50,000 entries) rather than the previous ~1,000-entry limit, increasing burst bandwidth and memory use for receivers and potentially changing transaction-flood propagation dynamics.
Changed components
src/net_processing.cppPeerManagerImpl::SendMessagestransaction inventory relay (INV message generation)Inspect captured patch +6 / −15
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 3397b388..b5b2375f 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -169,13 +169,9 @@ static constexpr auto INBOUND_INVENTORY_BROADCAST_INTERVAL{5s};
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. */
-static constexpr unsigned int INVENTORY_BROADCAST_PER_SECOND{14};
+[[maybe_unused]] static constexpr unsigned int INVENTORY_BROADCAST_PER_SECOND{14};
/** Target number of tx inventory items to send per transmission. */
-static constexpr unsigned int INVENTORY_BROADCAST_TARGET = INVENTORY_BROADCAST_PER_SECOND * count_seconds(INBOUND_INVENTORY_BROADCAST_INTERVAL);
-/** Maximum number of inventory items to send per transmission. */
-static constexpr unsigned int INVENTORY_BROADCAST_MAX = 1000;
-static_assert(INVENTORY_BROADCAST_MAX >= INVENTORY_BROADCAST_TARGET, "INVENTORY_BROADCAST_MAX too low");
-static_assert(INVENTORY_BROADCAST_MAX <= node::MAX_PEER_TX_ANNOUNCEMENTS, "INVENTORY_BROADCAST_MAX too high");
+[[maybe_unused]] static constexpr unsigned int INVENTORY_BROADCAST_TARGET = INVENTORY_BROADCAST_PER_SECOND * count_seconds(INBOUND_INVENTORY_BROADCAST_INTERVAL);
/** Average delay between feefilter broadcasts in seconds. */
static constexpr auto AVG_FEEFILTER_BROADCAST_INTERVAL{10min};
/** Maximum feefilter broadcast delay after significant change. */
@@ -6074,7 +6070,7 @@ bool PeerManagerImpl::SendMessages(CNode& node)
std::vector<CInv> vInv;
{
LOCK(peer.m_block_inv_mutex);
- vInv.reserve(std::max<size_t>(peer.m_blocks_for_inv_relay.size(), INVENTORY_BROADCAST_TARGET));
+ vInv.reserve(peer.m_blocks_for_inv_relay.size());
// Add blocks
for (const uint256& hash : peer.m_blocks_for_inv_relay) {
@@ -6152,16 +6148,12 @@ bool PeerManagerImpl::SendMessages(CNode& node)
}
const CFeeRate filterrate{tx_relay->m_fee_filter_received.load()};
// Topologically and fee-rate sort the inventory we send for privacy and priority reasons.
- // A heap is used so that not all items need sorting if only a few are being sent.
+ // A heap is used even though all items are being processed/sent.
CompareInvMempoolOrder compareInvMempoolOrder(&m_mempool);
std::make_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder);
- // No reason to drain out at many times the network's capacity,
- // especially since we have many peers and some will draw much shorter delays.
- unsigned int nRelayedTransactions = 0;
LOCK(tx_relay->m_bloom_filter_mutex);
- size_t broadcast_max{INVENTORY_BROADCAST_TARGET + (tx_relay->m_tx_inventory_to_send.size()/1000)*5};
- broadcast_max = std::min<size_t>(INVENTORY_BROADCAST_MAX, broadcast_max);
- while (!vInvTx.empty() && nRelayedTransactions < broadcast_max) {
+ vInv.reserve(std::min<size_t>(MAX_INV_SZ, tx_relay->m_tx_inventory_to_send.size()));
+ while (!vInvTx.empty()) {
// Fetch the top element from the heap
std::pop_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder);
std::set<Wtxid>::iterator it = vInvTx.back();
@@ -6191,7 +6183,6 @@ bool PeerManagerImpl::SendMessages(CNode& node)
if (tx_relay->m_bloom_filter && !tx_relay->m_bloom_filter->IsRelevantAndUpdate(*txinfo.tx)) continue;
// Send
vInv.push_back(inv);
- nRelayedTransactions++;
if (vInv.size() == MAX_INV_SZ) {
MakeAndPushMessage(node, NetMsgType::INV, vInv);
vInv.clear();
Why this scored 45/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.