Skip to content

fix(jsonrpc): handle null parameters consistently - #5

Open
waynercheung wants to merge 1 commit into
developfrom
feat/jsonrpc-null-param
Open

fix(jsonrpc): handle null parameters consistently#5
waynercheung wants to merge 1 commit into
developfrom
feat/jsonrpc-null-param

Conversation

@waynercheung

@waynercheung waynercheung commented Aug 29, 2026

Copy link
Copy Markdown
Owner

What does this PR do?

Handles 10 identified top-level parameter positions that can trigger unchecked dereferences, and normalizes seven optional DTO fields whose explicit JSON null currently behaves differently from omission. The per-parameter decision basis is recorded in #PHASE1.

  • Handle the 10 affected positions without unchecked dereferences. For source-gated methods the request-source check (disableInPBFT / FullNode-only) stays first. Object and filter-ID arguments are validated before business processing, while fullTransactionObjects is normalized at its existing use site after the block lookup, preserving the non-existent-block behavior:
    • eth_getLogs / eth_newFilter with a null filter object -> -32602 "invalid filter request"
    • eth_call / eth_estimateGas / buildTransaction with a null argument object -> -32602 "invalid params"
    • eth_uninstallFilter(null) -> false; eth_getFilterChanges(null) / eth_getFilterLogs(null) -> -32000 "filter not found" (go-ethereum's results for a null filter ID)
    • eth_getBlockByHash / eth_getBlockByNumber with a null fullTransactionObjects -> treated as false (Boolean.TRUE.equals(...) instead of auto-unboxing)
  • eth_call validates its required transaction argument before the block selector, so [null, null] returns -32602 instead of -32600.
  • @JsonSetter(nulls = Nulls.SKIP) on the six optional BuildArguments fields (tokenId, tokenValue, consumeUserResourcePercent, originEnergyLimit, permissionId, extraData) and on CallArguments.from, so an explicit JSON null keeps the field default exactly as omitting the field does.
  • A defensive null check at the LogFilter(FilterRequest) constructor, plus a shared INVALID_FILTER_REQUEST message constant.

All error responses keep the existing @JsonRpcErrors mapping: data stays "{}" and the request id is echoed.

Why are these changes required?

Before this PR the ten positions dereferenced the argument without a null check. The resulting NullPointerException is not covered by @JsonRpcErrors, so jsonrpc4j's fallback returned:

{"jsonrpc":"2.0","id":1,"error":{"code":-32001,"message":null,"data":"java.lang.NullPointerException"}}

message: null violates JSON-RPC 2.0 (on Java 17 it becomes a helpful-NPE string that echoes internal class and method names), data exposes the Java exception type, and a required-parameter error such as this example is reported under the code java-tron documents for internal errors. The seven DTO fields were inconsistent among themselves: an explicit null failed with -32001 or -32000 depending on the path, while CallArguments.from returned -32602 where omitting the field continues with the zero address.

The per-parameter decisions follow the basis recorded in #PHASE1: the Execution API required / schema first, then whether a zero value can be inferred from null, then go-ethereum / Besu behavior as a reference. Details and reproduction steps are in #PHASE1.

This PR has been tested by:

  • Unit Tests

  • Manual Testing

  • JsonRpcArgumentNullBindingTest - Jackson binding through ObjectMapper: an explicit null and an omitted field produce the same BuildArguments / CallArguments.

  • JsonRpcCallAndEstimateGasTest / JsonRpcTest - service-level results for null argument objects and null filter requests, and the eth_call argument-before-block ordering.

  • JsonrpcServiceTest - wire-level contract through a real JsonRpcServer: code / message / data for every case above, filter-object null wildcard semantics unchanged (address: null, topics: null, positional [A, null]), optional DTO null fields, and an assertion that no response contains java. or an exception class name. Includes wire regressions for the four hash-parameter methods fixed by fix(jsonrpc): harden RPC/HTTP parameter validation tronprotocol/java-tron#6828.

  • WalletCursorTest - the null checks keep request-source precedence: SolidityNode and PBFT behavior is unchanged.

./gradlew :framework:checkstyleMain :framework:checkstyleTest :framework:test --tests 'org.tron.core.jsonrpc.*' --tests 'org.tron.core.services.jsonrpc.*' passes on JDK 17 (arm64).

Compatibility

Not a breaking change: no request that succeeds today starts failing. Default behavior changes only for the null inputs above:

  • required object parameters change from the unmapped -32001 fallback to -32602, while the two get-filter methods change from -32001 to -32000;
  • eth_uninstallFilter(null) and eth_getBlockBy*(..., null) go from an error to a normal response;
  • explicit null DTO fields recover the omitted-field semantics;
  • eth_call([null, null]) goes from -32600 to -32602;
  • eth_call([null, "earliest"]) and similar shapes where parameter 0 and the block selector are both invalid keep -32602, but the message changes from the block error to "invalid params" because the required argument is now validated first.

Clients matching on the old codes or messages for these paths need to adjust.

Unchanged: results of valid non-null requests, HTTP status, request-source precedence, data on mapped errors, result: null for a non-existent block, filter-object null wildcards, eth_uninstallFilter with an unknown non-null ID (still -32000; aligning it to false is follow-up work), gRPC and non-JSON-RPC HTTP APIs.

Follow up

Extra details

This PR overlaps the request-envelope validation planned in tronprotocol#6676 in JsonRpcApiUtil and TronJsonRpcImpl, and shares TronJsonRpcImpl with the error-mapping PR (no conflicting hunks, verified with git merge-tree). There is no dependency on either: this PR can land in any order, and whichever change lands second rebases.

Pre-submit checklist:

  • Google Java Style; Checkstyle passes on main and test sources
  • No debug code, temporary comments or TODOs
  • No numeric computation or narrowing casts introduced
  • No logging added or changed
  • No DB, consensus, config or dependency changes
  • Comments explain the non-obvious points: @JsonSetter protects Jackson input only, and positional vs. OR-list null topics in LogFilter

Refs tronprotocol#6676

Validate nullable JSON-RPC arguments before they are dereferenced.
Return stable protocol errors instead of jsonrpc4j's unhandled -32001
fallback, which exposes Java exception types.

Validate eth_call's required transaction argument before its block
selector. The double-null case now returns -32602 instead of -32600.

Give null filter IDs a geth-compatible lookup-miss result:
eth_uninstallFilter returns false, while eth_getFilterChanges and
eth_getFilterLogs report "filter not found". Requests to
eth_uninstallFilter with unknown non-null IDs keep the existing -32000
path, locked by service- and wire-level tests.

Treat null block-detail flags as false and retain DTO defaults for
explicitly null optional fields. Preserve filter wildcard semantics and
request-source precedence.

Add binding, service, wire, and VM execution regression tests for error
codes, messages, DTO defaults, and unaffected behavior.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant