Skip to content

Unify and improve rate limiting for TRX and BLOCK INVENTORY Messages #6659

Description

@zeusoo001

Summary

When processing INVENTORY messages, TRON nodes have already implemented a rate limiting mechanism for TRX-type inventories. However, the current logic only counts the number of inventory elements within a historical time window and does not take into account the number of hashes contained in the current message. In addition, BLOCK-type INVENTORY messages currently lack a similar rate limiting mechanism.

The proposed optimizations are:

  • Enhance TRX INVENTORY rate limiting by including the number of items in the current message
  • Introduce a unified and configurable rate limiting mechanism for BLOCK INVENTORY

These improvements aim to provide more accurate control over inventory intake rate, improve node resource management, and enhance P2P network stability.

Root Cause

In the InventoryHandler or related processing logic:

1. Incomplete Rate Limiting for TRX INVENTORY

The current TRX rate limiting logic is:

int count = peer.getPeerStatistics().messageStatistics.tronInTrxInventoryElement.getCount(10);
if (count > maxCountIn10s) {
  logger.warn("Drop inventory from Peer {}, cur:{}, max:{}",
          peer.getInetAddress(), count, maxCountIn10s);
  return;
}

This logic only relies on the historical count (count) and does not include the size of the current message (message.getHashList().size()).

2. Missing Rate Limiting for BLOCK INVENTORY

BLOCK-type INVENTORY messages have no rate limiting control:

for (Hash hash : message.getHashList()) {
  invToFetch.put(hash, peer);
}

As a result, BLOCK INVENTORY intake is unbounded and lacks unified configuration control.

Reproduction

Start a node and connect it to the P2P network.

TRX scenario:

  • Construct TRX INVENTORY messages containing multiple transaction hashes
  • Observe that rate limiting depends only on historical counts

BLOCK scenario:

  • Construct BLOCK INVENTORY messages containing multiple block hashes
  • Observe that the node continuously accepts and inserts them into invToFetch without rate limiting

Impact

  • Inaccurate rate limiting: TRX logic does not account for the current message size
  • Incomplete coverage: BLOCK INVENTORY lacks rate limiting entirely
  • Resource management risk: Large messages with multiple hashes may increase queue pressure and memory usage
  • Inconsistent P2P behavior: Different handling strategies for TRX and BLOCK inventories

Suggested Fix

Apply a unified rate limiting optimization for INVENTORY messages:

1. TRX INVENTORY Enhancement

Include the current message size in the rate limiting check:

if (inventoryType.equals(Protocol.Inventory.InventoryType.TRX)) {
  int count = peer.getPeerStatistics().messageStatistics
          .tronInTrxInventoryElement.getCount(10);
  if (count + message.getHashList().size() > maxCountIn10s) {
      logger.warn("Drop inventory from Peer {}, cur:{}, size:{}, max:{}",
              peer.getInetAddress(),
              count,
              message.getHashList().size(),
              maxCountIn10s);
      return;
  }
}

2. BLOCK INVENTORY Rate Limiting (New)

Introduce a configurable parameter:

public int maxBlockInvPerSecond = 10;

Calculate based on a 10-second sliding window:

private int maxBlockInvIn10s = maxBlockInvPerSecond * 10;

Add rate limiting logic:

if (inventoryType.equals(Protocol.Inventory.InventoryType.BLOCK)) {
  int count = peer.getPeerStatistics().messageStatistics
          .tronInBlockInventoryElement.getCount(10);
  if (count + message.getHashList().size() > maxBlockInvIn10s) {
      logger.warn("Drop block inventory from Peer {}, cur:{}, size:{}, max:{}",
              peer.getInetAddress(),
              count,
              message.getHashList().size(),
              maxBlockInvIn10s);
      return;
  }
}

Core Logic

count + message.getHashList().size() > maxLimit
Meaning:
Historical count + current message size > threshold → drop the message

Benefits

  • More accurate rate limiting: Combines historical window and current message size
  • Unified mechanism: Consistent handling for both TRX and BLOCK inventories
  • Configurable control: BLOCK INVENTORY rate limit adjustable (default: 10/s)
  • Smoother resource management: Prevents bursts caused by large messages
  • Improved P2P stability: More predictable and controlled inventory intake behavior

Activity

  1. lxcmyf commented on Apr 8, 2026

    @lxcmyf
    Collaborator

    I checked the current implementation in P2pEventHandlerImpl and the report looks valid on the latest local codebase.

    For INVENTORY messages, the TRX-side guard is currently using tronInTrxInventoryElement.getCount(10) before messageStatistics.addTcpInMessage(msg), so the decision is based only on the historical 10-second window and does not include message.getHashList().size() from the current batch. That means a large current TRX inventory can still pass the pre-check even when it should push the peer over the threshold.

    For BLOCK inventories, I can confirm there is no equivalent pre-check in the same path today. The block inventory element counters already exist in MessageStatistics, so adding a symmetric limit there should be straightforward.

    A reasonable implementation is:

    • keep the pre-dispatch guard in P2pEventHandlerImpl
    • change the TRX condition to count + currentSize > limit
    • add a dedicated BLOCK limit using tronInBlockInventoryElement.getCount(10) + currentSize

    One detail to keep in mind: because the rate-limit check happens before addTcpInMessage(msg), the comparison should intentionally include the current message size, otherwise the check will continue to undercount the batch that is currently being processed.

  2. zeusoo001 commented on Apr 8, 2026

    @zeusoo001
    ContributorAuthor

    @lxcmyf Thanks for the thorough analysis — the description matches what I see in the code as well.

    A few additional observations worth noting:

    On the timing of the check

    The rate-limit guard runs before addTcpInMessage(msg) is called, which means tronInTrxInventoryElement.getCount(10) only reflects hashes accepted in previous messages, not the one currently being evaluated. So the effective window is always one batch behind. Including currentSize in the comparison (count + currentSize > limit) is the correct fix — it closes this gap by treating the current batch as already committed before making the drop decision.

    On BLOCK INVENTORY threshold design

    tronInBlockInventoryElement already exists in MessageStatistics alongside the TRX counterpart, so no new counter infrastructure is needed. The reason BLOCK INV warrants its own configurable threshold rather than reusing maxTps is that block announcement frequency operates on a different scale than transaction throughput. Under normal conditions a node receives only a handful of block hashes per slot, so a dedicated and lower limit is more appropriate than inheriting the transaction rate cap.

  3. abn2357 commented on Apr 9, 2026

    @abn2357
    Contributor

    Hello, I have a suggestion, could we change the transaction abbreviation 'TRX' in the code? It's easily mistaken for TRON's native token.

  4. zeusoo001 commented on Apr 9, 2026

    @zeusoo001
    ContributorAuthor

    @abn2357 Thanks for the suggestion. The abbreviation TRX here stands for Transaction, not TRON's native token. It has been used consistently throughout the codebase for a long time — for example, TrxMessage, TrxInventoryMessage, and related statistics counters all follow this convention. Renaming it would require touching a large number of files and classes, with a high risk of introducing unintended side effects. Given that this is an established internal naming convention rather than a user-facing identifier, we'd prefer to keep it as-is for now.

  5. lxcmyf commented on Apr 15, 2026

    @lxcmyf
    Collaborator

    @zeusoo001

    I agree the key bug here is that the rate-limit check is evaluating the historical counter without accounting for the batch currently being processed, so the decision is effectively one message behind. Folding currentSize into the comparison is the right way to close that gap.

    Also agree on separating the BLOCK INVENTORY threshold from maxTps. Even though the counter plumbing already exists, the expected traffic pattern is very different from TRX INV, so sharing the same cap would be too coarse.

    One small thing I’d make sure to cover in the implementation is boundary behavior: when count + currentSize == limit, should that still pass and only reject > cases? It would be good to lock that down in tests so the config semantics stay predictable.

  6. xxo1shine commented on Apr 15, 2026

    @xxo1shine
    Collaborator

    @lxcmyf,

    Thanks for confirming. On the boundary behavior: the current implementation uses >, meaning count + currentSize == limit still passes — only strictly exceeding the threshold triggers a drop. This is consistent with the existing TRX logic, with the semantics of "allow up to the limit, reject beyond it".

  7. lxcmyf commented on Apr 20, 2026

    @lxcmyf
    Collaborator

    Makes sense. If > is the intended policy, I’d just suggest locking both == limit and > limit cases into tests so that boundary behavior stays explicit and doesn’t drift later.

  8. xxo1shine commented on Apr 21, 2026

    @xxo1shine
    Collaborator

    Makes sense. If > is the intended policy, I’d just suggest locking both == limit and > limit cases into tests so that boundary behavior stays explicit and doesn’t drift later.

    @lxcmyf Thanks, that makes sense.

    Agreed — locking down both boundary cases in tests is important to make the behavior explicit and prevent future regressions.

    We’ll cover at least:

    • count + currentSize == limit → should pass
    • count + currentSize > limit → should be rejected

    This keeps the semantics consistent with the current “allow up to the limit, reject beyond it” policy, while ensuring the behavior remains stable as the code evolves.

  9. moved this to In Progress in java-tronon May 4, 2026
  10. moved this from In Progress to Done in java-tronon May 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions