fix(jsonrpc): enforce log filter cap and improve match efficiency#6732
Open
317787106 wants to merge 23 commits intotronprotocol:developfrom
Open
fix(jsonrpc): enforce log filter cap and improve match efficiency#6732317787106 wants to merge 23 commits intotronprotocol:developfrom
317787106 wants to merge 23 commits intotronprotocol:developfrom
Conversation
alan-eth
reviewed
Apr 29, 2026
0xbigapple
reviewed
Apr 29, 2026
0xbigapple
reviewed
Apr 29, 2026
0xbigapple
reviewed
Apr 29, 2026
0xbigapple
reviewed
Apr 29, 2026
0xbigapple
reviewed
Apr 29, 2026
0xbigapple
reviewed
Apr 29, 2026
0xbigapple
reviewed
Apr 29, 2026
0xbigapple
reviewed
Apr 29, 2026
| @Slf4j(topic = "API") | ||
| public class JsonRpcApiUtil { | ||
|
|
||
| static SecureRandom random = new SecureRandom(); |
Collaborator
There was a problem hiding this comment.
[NIT] Hoisting random to a static field is a nice perf win👍. One small follow-up: consider tightening it to private static final:
private static final SecureRandom random = new SecureRandom();
As written (package-private, non-final), anything in org.tron.core.services.jsonrpc can reassign it. A well-meaning test or perf experiment could quietly do: JsonRpcApiUtil.random = new Random(42);// compiles, no warning
…and filter IDs silently become predictable, which breaks the unguessability assumption clients rely on. private final closes that path at compile time, and also gives JMM safe-publication guarantees for free.
…e addAll matchedLog instead after
e6354d0 to
ad0d688
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Three issues exist in the JSON-RPC log filter subsystem (
eth_newFilter/eth_getLogs), reported in #6510:Unbounded memory growth —
eth_newFilterimposes no limit on the number of active filters held in memory. A client can register filters indefinitely and eventually exhaust heap on the node.Correctness bug —
LogMatch.matchBlockOneByOneevaluated theMAX_RESULTguard afteraddAll, so the result list could transiently contain more thanMAX_RESULTentries before the exception was thrown.Performance bottleneck under high filter count —
handleLogsFilteriterated the filter map with anIteratorand calledresult.add()once per element, which is slow and contention-prone when thousands of filters are active.Changes
1. Enforce a configurable cap on active log filters
Adds
node.jsonrpc.maxLogFilterNum(default:20000). When the cap is reached,eth_newFilterimmediately returnsJsonRpcExceedLimitException(JSON-RPC code-32005) instead of growing without bound.2. Fix the MAX_RESULT boundary check in
LogMatch.matchBlockOneByOne(correctness)Moves the
size + matchedLog.size() > MAX_RESULTguard beforeaddAll. The result list now never exceedsMAX_RESULTentries regardless of how many logs a single block contributes.3. Optimize
handleLogsFilterfor large filter mapsIteratorloop,result.add()per elementConcurrentHashMap.forEachwith local list + singleaddAllForkJoinPool(3)+parallelStreamKey improvements:
addAllon the sharedLinkedBlockingQueue.logsFilterPoolis aForkJoinPool(3)created once perTronJsonRpcImplinstance and shut down with it, avoiding unbounded thread creation under spike load.logger.debugtiming line records dispatch cost and filter-map size.4. Decouple filter state from static class scope
The four filter maps (
eventFilter2ResultFull,blockFilter2ResultFull,eventFilter2ResultSolidity,blockFilter2ResultSolidity),logsFilterPool, andfilterParallelThresholdare now instance fields.handleBLockFilter,handleLogsFilter, andprocessLogFilterEntryare now instance methods.Motivation: with static shared state, any test that constructs a
TronJsonRpcImplinstance would silently read and write the same filter maps used by the running node, making test isolation impossible. Switching to instance state means each instance owns its own filter maps; tests create a lightweight instance with no Spring dependencies (new TronJsonRpcImpl(null, null, null)) and operate on an isolated map.As a consequence,
BlockFilterCapsuleandLogsFilterCapsulenow receive theTronJsonRpcImplinstance at construction time and callhandleBLockFilter/handleLogsFilteron it directly, eliminating the static imports that were the only way to invoke those methods before.5. node.jsonrpc.maxBlockFilterNum = 0 means block filter umber is not limited.
It has the same meaning as node.jsonrpc.maxLogFilterNum =0 for log filter number.
Testing
HandleLogsFilterTest— 10 cases covering event dispatch, block-range filtering, expired-filter removal, solidified/non-solidified routing, and 3 parallel-path cases (all-filters receive events, expired eviction, solidified routing — each withfilterParallelThresholdlowered via reflection to keep the test fast)LogMatchOverLimitTest— 4 cases covering under-limit, exact-limit, exceed-limit (verifies exception is thrown beforeaddAll), and empty-block skipWalletCursorTest.testNewFilter_exceedsCapThrowsException— verifies the cap throws the correct exception with the limit value in the message; fixed to populate the map on the same instance that callsnewFilter(the original test was populating the static map then constructing a new instance, so it never actually tested the cap)BlockFilterCapsuleTest,LogsFilterCapsuleTest,ConcurrentHashMapTest— updated to pass aTronJsonRpcImplinstance to constructors after the static-to-instance refactoringCloses #6510