feat(net): add rate limiting logic for P2P messages#6393
feat(net): add rate limiting logic for P2P messages#6393kuny0707 merged 8 commits intotronprotocol:release_v4.8.1from
Conversation
| peer.getChannel().close(); | ||
| peer.getNodeStatistics() | ||
| .nodeDisconnectedRemote(((DisconnectMessage)msg).getReason()); | ||
| if (peer.getP2pRateLimiter().tryAcquire(type.asByte())) { |
There was a problem hiding this comment.
why disconnect need a rate limiter? As in your issue mentioned "After receiving the message, the connection will be disconnected and no response will be given."
There was a problem hiding this comment.
Another question: is this rate limit set per Peer or shared by all Peers? Will this rate limiter effect the normal disconnect logic?
There was a problem hiding this comment.
Another question: is this rate limit set per Peer or shared by all Peers? Will this rate limiter effect the normal disconnect logic?
This rate limit is set per peer. This rate limiter does not affect the normal disconnection logic.
There was a problem hiding this comment.
why disconnect need a rate limiter? As in your issue mentioned "After receiving the message, the connection will be disconnected and no response will be given."
To prevent the peer from sending a large number of repeated disconnect messages at once, it will not respond to the message, but will execute the disconnection logic.
|
|
||
| public class P2pRateLimiter { | ||
| private final Cache<Byte, RateLimiter> rateLimiters = CacheBuilder.newBuilder() | ||
| .maximumSize(32).build(); |
There was a problem hiding this comment.
here is a hidden size 32 limit, can make it a large one so it normally won't never hit the limit?
There was a problem hiding this comment.
The message types defined by the current protocol are very limited, and the limit of 32 is sufficient for the foreseeable future.
| lastInteractiveTime = System.currentTimeMillis(); | ||
| p2pRateLimiter.register(SYNC_BLOCK_CHAIN.asByte(), 2); | ||
| p2pRateLimiter.register(FETCH_INV_DATA.asByte(), 2); | ||
| p2pRateLimiter.register(P2P_DISCONNECT.asByte(), 1); |
There was a problem hiding this comment.
make this hardcode 2,2,1 number to use a static variable
There was a problem hiding this comment.
Will relay nodes trigger speed limits? What about busy sync nodes on the network?
There was a problem hiding this comment.
Will relay nodes trigger speed limits? What about busy sync nodes on the network?
For synchronization logic, relay nodes are no different from ordinary full nodes.
| if (!peer.isNeedSyncFromUs()) { | ||
| throw new P2pException(TypeEnum.BAD_MESSAGE, "no need sync"); | ||
| } | ||
| if (!peer.getP2pRateLimiter().tryAcquire(fetchInvDataMsg.getType().asByte())) { |
There was a problem hiding this comment.
consumerInvToFetch task is scheduled every 30ms, but the limit FETCH_INV_DATA_RATE is 3 times/s, there will be conflict.
There was a problem hiding this comment.
This rate limit is based on the premise of block synchronization.
| code = Protocol.ReasonCode.NO_SUCH_MESSAGE; | ||
| break; | ||
| case BAD_MESSAGE: | ||
| case RATE_LIMIT_EXCEEDED: |
There was a problem hiding this comment.
This mapping doesn't seem quite right. RATE_LIMIT_EXCEEDED is a server capacity issue rather than a protocol violation. Should we use a more specific error type or create a dedicated rate limit handler?
There was a problem hiding this comment.
According to normal protocol logic, RATE_LIMIT_EXCEEDED will not appear. Once it appears, it is considered a protocol violation.
|
|
||
| PARAMETER.rateLimiterSyncBlockChain = | ||
| config.hasPath(Constant.RATE_LIMITER_P2P_SYNC_BLOCK_CHAIN) ? config | ||
| .getDouble(Constant.RATE_LIMITER_P2P_SYNC_BLOCK_CHAIN) : 3.0; |
There was a problem hiding this comment.
Please add a comment specifying the rate limit unit: is this per second, per minute, per hour, or something?
There was a problem hiding this comment.
If no special instructions are given, the default is qps.
| public static final String RATE_LIMITER_RPC = "rate.limiter.rpc"; | ||
| public static final String RATE_LIMITER_P2P_SYNC_BLOCK_CHAIN = "rate.limiter.p2p.syncBlockChain"; | ||
| public static final String RATE_LIMITER_P2P_FETCH_INV_DATA = "rate.limiter.p2p.fetchInvData"; | ||
| public static final String RATE_LIMITER_P2P_DISCONNECT = "rate.limiter.p2p.disconnect"; |
There was a problem hiding this comment.
As user can set the value, so add this configuration to config file? such as config.conf.
There was a problem hiding this comment.
This is a repeated question; please refer to the previous response for details.
| .maximumSize(32).build(); | ||
|
|
||
| public void register(Byte type, double rate) { | ||
| RateLimiter rateLimiter = RateLimiter.create(Double.POSITIVE_INFINITY); |
There was a problem hiding this comment.
what about create the RateLimter with rate, such as:
RateLimiter rateLimiter = RateLimiter.create(rate);
There was a problem hiding this comment.
The approach you mentioned has a problem: the initial capacity of the token bucket is only 1. Once the peer completes the handshake, it immediately enters the synchronization state. If the initial token count is 1, it may lead to insufficient tokens.
| public boolean tryAcquire(Byte type) { | ||
| RateLimiter rateLimiter = rateLimiters.getIfPresent(type); | ||
| if (rateLimiter == null) { | ||
| return true; |
There was a problem hiding this comment.
Is there any risk if return true for the unregistered type(rateLimter == null)?
There was a problem hiding this comment.
Unregistered types return true, meaning no speed limit.
| + " message exceeds the rate limit"); | ||
| } | ||
| if (fetchInvDataMsg.getHashList().size() > NetConstants.MAX_BLOCK_FETCH_PER_PEER) { | ||
| throw new P2pException(TypeEnum.BAD_MESSAGE, "fetch too more blocks, size:" |
There was a problem hiding this comment.
use "fetch too many blocks" instead of "fetch too more blocks".
| try { | ||
| fetchInvDataMsgHandler.processMessage(peer, msg); | ||
| } catch (Exception e) { | ||
| Assert.assertEquals("fetch too more blocks, size:101", e.getMessage()); |
There was a problem hiding this comment.
use "fetch too many blocks" instead of "fetch too more blocks".
What does this PR do?
Add rate limiting logic for P2P messages. Closes #6297
Why are these changes required?
This PR has been tested by:
Follow up
Extra details