Skip to content

feat(jsonrpc): standardize JSON-RPC error handling - #18

Open
0xbigapple wants to merge 3 commits into
developfrom
feature/jsonrpc-error-handling
Open

feat(jsonrpc): standardize JSON-RPC error handling#18
0xbigapple wants to merge 3 commits into
developfrom
feature/jsonrpc-error-handling

Conversation

@0xbigapple

@0xbigapple 0xbigapple commented Aug 7, 2026

Copy link
Copy Markdown
Owner

What does this PR do?

Standardizes three JSON-RPC behaviors to match the Ethereum Execution API:

  1. Contract revert gets its own error code (3). New JsonRpcExecutionRevertedException,
    mapped to code: 3 with the raw revert payload in data. Wallet.callConstantContract now
    records contractRet = REVERT on the revert branch, so the JSON-RPC layer classifies on the
    enum rather than on an error-message string, and TronJsonRpcImpl routes eth_call and
    eth_estimateGas through one requireExecutionSuccess guard so the two endpoints cannot
    drift apart.

  2. Pruned history on a LiteNode answers 4444 "Pruned history unavailable", matching the
    Ethereum Execution API, geth (PR #31361) and Besu (PR #9643). Covers the six number/tag based
    methods: eth_getBlockByNumber, eth_getBlockTransactionCountByNumber,
    eth_getTransactionByBlockNumberAndIndex, eth_getBlockReceipts, eth_getLogs,
    eth_newFilter. earliest now resolves to lowestBlockNum, so fromBlock: "earliest"
    keeps working. The cutoff is checked before any store lookup, so a LiteNode pays no I/O for
    history it cannot serve.

  3. Optional strict JSON-RPC 2.0 request validation. New config
    node.jsonrpc.strictComplianceMode, off by default. When enabled, the servlet rejects —
    before dispatch — any request object malformed per spec §4:

    Problem jsonrpc4j today With the switch on
    jsonrpc missing or not "2.0" accepted -32600
    method missing or not a string -32601 method not found -32600
    params present but not an array/object no response at all -32600
    id present but not a string/number no response at all -32600

    Batches are validated per member: a bad member gets its own -32600 while the compliant
    members still execute.

Why are these changes required?

Each divergence costs dapp / SDK / tooling authors real debugging time:

  • eth_call / eth_estimateGas collapse every TVM failure into -32000, so a contract
    revert is indistinguishable from out-of-energy or a bad jump — clients cannot tell "your
    input was rejected by the contract" from "the node failed to execute".
  • A LiteNode silently returns null for blocks it has pruned, which clients cannot tell apart
    from "this block does not exist"; worse, an eth_getLogs range crossing the pruning cutoff
    quietly returns incomplete results.
  • jsonrpc4j answers nothing at all for a non-scalar id or a non-structured params, so
    those callers wait forever today; a missing method is blamed as -32601 "no such method"
    when the real problem is a malformed request object. All reproduced on a live node before and
    after the change. Default-off means zero behaviour change for existing clients; operators can
    opt in for stricter client diagnostics.

This PR has been tested by:

  • Unit Tests — 372 tests across the jsonrpc packages and config binding, all green. New
    coverage for revert classification, the pruned-history guard (including the genesis and
    future-block edges), and strict-mode validation in both switch states.
  • Manual Testing — 29 integration cases on a Nile LiteNode, all passing: revert → 3 with
    payload; 4444 on pruned heights while genesis and the cutoff block stay queryable;
    earliest resolving to lowestBlockNum; a future height answering null rather than
    4444; the strict-mode matrix with the switch off and on.

Follow up

Extra details


Summary by cubic

Standardizes JSON-RPC error handling to match the Ethereum Execution API. Adds code 3 for contract reverts, code 4444 for pruned history with receipt-aware floors on LiteNode, and an optional strict JSON-RPC 2.0 validator.

  • New Features

    • Contract reverts return error code 3 with the raw revert payload in error data; applies to eth_call and eth_estimateGas. Other execution failures remain -32000 and include return data when present.
    • LiteNode pruned history returns error code 4444 for eth_getBlockByNumber, eth_getBlockTransactionCountByNumber, eth_getTransactionByBlockNumberAndIndex, eth_getBlockReceipts, eth_getLogs, eth_newFilter. On LiteNode "earliest" resolves to the first block with receipts (falls back to the body floor when receipts are not persisted). Pruning checks run before store lookups.
    • Optional strict request validation via node.jsonrpc.strictComplianceMode (default off): enforces "jsonrpc":"2.0", string method, array/object (non-null) params when present, and scalar id; batches validate per member and reject invalid members instead of hanging on bad id/params.
  • Migration

    • No changes by default. To enable stricter validation, set node.jsonrpc.strictComplianceMode = true. Clients must send spec-compliant JSON-RPC 2.0 requests when strict mode is on.

Written for commit 7a7c797. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added optional strict JSON-RPC 2.0 validation for individual and batch requests.
    • Added clear errors for queries targeting pruned block or receipt history.
    • Added accurate earliest history boundaries and node APIs for available history ranges.
    • Added detailed contract execution failure and revert reporting, including revert data.
  • Bug Fixes

    • Improved transaction result codes for runtime execution failures.
    • Preserved valid JSON-RPC request IDs and notification behavior.
    • Improved handling of historical block, receipt, log, and filter queries.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9f1b6b29-0fac-4f43-bb68-0937afc700af

📥 Commits

Reviewing files that changed from the base of the PR and between a831507 and 7a7c797.

📒 Files selected for processing (4)
  • framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java
  • framework/src/test/java/org/tron/core/db/TransactionRetStoreTest.java
  • framework/src/test/java/org/tron/core/jsonrpc/JsonRpcCallAndEstimateGasTest.java
  • framework/src/test/java/org/tron/core/jsonrpc/JsonRpcPrunedHistoryTest.java
🚧 Files skipped from review as they are similar to previous changes (4)
  • framework/src/test/java/org/tron/core/db/TransactionRetStoreTest.java
  • framework/src/test/java/org/tron/core/jsonrpc/JsonRpcCallAndEstimateGasTest.java
  • framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java
  • framework/src/test/java/org/tron/core/jsonrpc/JsonRpcPrunedHistoryTest.java

📝 Walkthrough

Walkthrough

The PR adds retained block and receipt cutoffs, pruned-history JSON-RPC errors, execution-revert handling, and optional strict JSON-RPC request validation. It also adds configuration wiring and tests for these behaviors.

Changes

JSON-RPC history, execution, and compliance

Layer / File(s) Summary
Retained history cutoffs
chainbase/src/main/java/org/tron/core/..., framework/src/main/java/org/tron/core/Wallet.java, framework/src/test/java/org/tron/core/db/...
The node records the lowest retained block and receipt block. TransactionRetStore exposes the lowest stored key.
Execution result classification
chainbase/src/main/java/org/tron/core/capsule/..., common/src/main/java/org/tron/core/exception/jsonrpc/..., framework/src/main/java/org/tron/core/utils/..., framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpc*.java, framework/src/test/java/org/tron/core/jsonrpc/JsonRpcCallAndEstimateGasTest.java, framework/src/test/java/org/tron/core/services/jsonrpc/JsonRpcErrorResolverTest.java
Execution failures map to contract result codes. Reverts use JSON-RPC error code 3 and preserve return data.
Pruned-history RPC handling
common/src/main/java/org/tron/core/exception/jsonrpc/..., framework/src/main/java/org/tron/core/services/jsonrpc/..., framework/src/test/java/org/tron/core/jsonrpc/JsonRpcPrunedHistoryTest.java, framework/src/test/java/org/tron/core/jsonrpc/*FilterTest.java
Block, receipt, log, and filter methods check retained history and return pruned-history errors with code 4444.
Strict JSON-RPC validation
common/src/main/java/org/tron/common/parameter/..., common/src/main/java/org/tron/core/config/args/..., common/src/main/resources/reference.conf, framework/src/main/java/org/tron/core/config/args/Args.java, framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcServlet.java, framework/src/test/java/org/tron/core/services/jsonrpc/JsonRpcServletTest.java
An optional setting validates JSON-RPC version, method, params, IDs, and batch members. Invalid requests return INVALID_REQUEST.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant JsonRpcServlet
  participant TronJsonRpcImpl
  participant JsonRpcApiUtil
  Client->>JsonRpcServlet: send JSON-RPC request
  JsonRpcServlet->>JsonRpcServlet: validate request in strict mode
  JsonRpcServlet->>TronJsonRpcImpl: dispatch valid request
  TronJsonRpcImpl->>JsonRpcApiUtil: validate retained history
  JsonRpcApiUtil-->>TronJsonRpcImpl: allow request or return pruning error
  TronJsonRpcImpl-->>JsonRpcServlet: return result or mapped exception
  JsonRpcServlet-->>Client: send JSON-RPC response
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.58% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: standardized JSON-RPC error handling across reverts, pruned history, and strict request validation.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/jsonrpc-error-handling

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 24 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcServlet.java">

<violation number="1" location="framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcServlet.java:305">
P3: Strict compliance mode rejects an explicit JSON `null` `id` with -32600, but JSON-RPC 2.0 §4.3 explicitly permits an id of String, Number, **or NULL**. Because a null id arrives as a Jackson NullNode, the final `id != null && !id.isTextual() && !id.isNumber()` check flags it as non-compliant even though it is spec-valid. This is inconsistent with the feature's stated 'align with the JSON-RPC 2.0 spec' goal and would reject spec-conforming clients that send `"id":null`. The anti-hang motivation for structured ids (array/object) is reasonable; consider distinguishing null ids (spec-valid, deterministic) from structured ids (undeterminable) rather than treating both identically, e.g. by rejecting only array/object ids, or by ensuring a -32600/null-id reply is written for null ids without claiming they are malformed.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

// becomes a notification, an array or an object makes its parseId throw — which leaves
// the client waiting forever, so a -32600 beats a silent hang.
JsonNode id = node.get("id");
return id != null && !id.isTextual() && !id.isNumber();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Strict compliance mode rejects an explicit JSON null id with -32600, but JSON-RPC 2.0 §4.3 explicitly permits an id of String, Number, or NULL. Because a null id arrives as a Jackson NullNode, the final id != null && !id.isTextual() && !id.isNumber() check flags it as non-compliant even though it is spec-valid. This is inconsistent with the feature's stated 'align with the JSON-RPC 2.0 spec' goal and would reject spec-conforming clients that send "id":null. The anti-hang motivation for structured ids (array/object) is reasonable; consider distinguishing null ids (spec-valid, deterministic) from structured ids (undeterminable) rather than treating both identically, e.g. by rejecting only array/object ids, or by ensuring a -32600/null-id reply is written for null ids without claiming they are malformed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcServlet.java, line 305:

<comment>Strict compliance mode rejects an explicit JSON `null` `id` with -32600, but JSON-RPC 2.0 §4.3 explicitly permits an id of String, Number, **or NULL**. Because a null id arrives as a Jackson NullNode, the final `id != null && !id.isTextual() && !id.isNumber()` check flags it as non-compliant even though it is spec-valid. This is inconsistent with the feature's stated 'align with the JSON-RPC 2.0 spec' goal and would reject spec-conforming clients that send `"id":null`. The anti-hang motivation for structured ids (array/object) is reasonable; consider distinguishing null ids (spec-valid, deterministic) from structured ids (undeterminable) rather than treating both identically, e.g. by rejecting only array/object ids, or by ensuring a -32600/null-id reply is written for null ids without claiming they are malformed.</comment>

<file context>
@@ -265,6 +273,38 @@ private void handleBatch(HttpServletResponse resp, JsonNode rootNode, int maxRes
+    // becomes a notification, an array or an object makes its parseId throw — which leaves
+    // the client waiting forever, so a -32600 beats a silent hang.
+    JsonNode id = node.get("id");
+    return id != null && !id.isTextual() && !id.isNumber();
+  }
+
</file context>

@0xbigapple
0xbigapple force-pushed the feature/jsonrpc-error-handling branch from 97a7447 to a831507 Compare August 12, 2026 03:11

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java (1)

1450-1475: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Keep future-only filters usable when receipt history is unavailable.

newFilter creates LogFilterWrapper with the current head. The wrapper rejects this height when the receipt floor is head + 1 after LiteNode startup, or Long.MAX_VALUE when receipt persistence is disabled. A default eth_newFilter then returns 4444 instead of subscribing to future LogsFilterCapsule events.

Apply receipt-history validation when eth_getFilterLogs performs a historical scan. Keep filter installation valid for future-only subscriptions. Add the corresponding 4444 interface mapping and regression tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java`
around lines 1450 - 1475, Update newFilter and LogFilterWrapper creation so
installing an eth_newFilter does not validate the current head against the
receipt-history floor, allowing future-only subscriptions when history is
unavailable. Move receipt-history validation to the historical scan performed by
eth_getFilterLogs, preserving the 4444 error for requests that require
unavailable receipts. Add the corresponding 4444 interface mapping and
regression coverage for both future-only installation and historical rejection.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java`:
- Around line 644-647: Update the “earliest” resolution logic to always return
wallet.getLowestBlockNum(), removing the receiptFloor lookup and fallback so
LiteNode block-number methods use the retained block floor.
- Around line 729-734: Update the receipt-history validation around
wallet.getLowestReceiptBlockNum() so a Long.MAX_VALUE receiptFloor immediately
throws JsonRpcPrunedHistoryException with the non-persisted-history message,
including when blockNum equals the sentinel. Keep the existing floor comparison
and prunedMessage(receiptFloor) behavior for finite receipt floors.

In `@framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcServlet.java`:
- Around line 300-303: Update the JSON-RPC request validation around the id
handling so an explicitly present null id is accepted alongside textual and
numeric ids, while a missing id remains a notification only when appropriate.
Preserve the null id through jsonrpc4j so it produces a normal response
containing "id": null, and add single-request and batch coverage for
explicit-null ids.

In `@framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java`:
- Around line 400-419: Update getBlockByNumOrTag in
framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java:400-419
so the earliest block tag resolves from wallet.getLowestBlockNum(), while
retaining receipt-floor checks only for receipt and log access; update
framework/src/test/java/org/tron/core/jsonrpc/JsonRpcPrunedHistoryTest.java:86-90
to expect LOWEST_BLOCK_NUM instead of RECEIPT_FLOOR_BLOCK_NUM.

In `@framework/src/main/java/org/tron/core/utils/ResultCodeUtil.java`:
- Line 46: Update ResultCodeUtil.resolve to recognize
Program.StaticCallModificationException and return the same result code used by
the consensus classifier before the existing contractResult.UNKNOWN fallback;
preserve the fallback for all other unrecognized exceptions.

In `@framework/src/test/java/org/tron/core/db/TransactionRetStoreTest.java`:
- Around line 69-75: Ensure the test cleanup for keys 3 and 7 in
TransactionRetStoreTest runs even when either assertion fails by moving the
deletion calls into a finally-protected cleanup block. Preserve the existing
assertions and delete all inserted test keys, including blockNum, regardless of
test outcome.

In
`@framework/src/test/java/org/tron/core/jsonrpc/JsonRpcCallAndEstimateGasTest.java`:
- Around line 78-81: Update the assertions in the panic and short-data eth_call
tests, plus the corresponding eth_estimateGas tests, to expect
JsonRpcExecutionRevertedException instead of the broader
JsonRpcInternalException. Keep the existing message and raw-data assertions
unchanged so every contractResult.REVERT path verifies the revert-specific
exception.

---

Outside diff comments:
In `@framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java`:
- Around line 1450-1475: Update newFilter and LogFilterWrapper creation so
installing an eth_newFilter does not validate the current head against the
receipt-history floor, allowing future-only subscriptions when history is
unavailable. Move receipt-history validation to the historical scan performed by
eth_getFilterLogs, preserving the 4444 error for requests that require
unavailable receipts. Add the corresponding 4444 interface mapping and
regression coverage for both future-only installation and historical rejection.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3d4aca8f-7a46-4b9b-a42e-240123a4e7a1

📥 Commits

Reviewing files that changed from the base of the PR and between 4a21592 and a831507.

📒 Files selected for processing (29)
  • chainbase/src/main/java/org/tron/core/ChainBaseManager.java
  • chainbase/src/main/java/org/tron/core/capsule/TransactionResultCapsule.java
  • chainbase/src/main/java/org/tron/core/store/TransactionRetStore.java
  • common/src/main/java/org/tron/common/parameter/CommonParameter.java
  • common/src/main/java/org/tron/core/config/args/NodeConfig.java
  • common/src/main/java/org/tron/core/exception/jsonrpc/JsonRpcExecutionRevertedException.java
  • common/src/main/java/org/tron/core/exception/jsonrpc/JsonRpcPrunedHistoryException.java
  • common/src/main/resources/reference.conf
  • framework/src/main/java/org/tron/core/Wallet.java
  • framework/src/main/java/org/tron/core/config/args/Args.java
  • framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java
  • framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcServlet.java
  • framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpc.java
  • framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java
  • framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogFilterAndResult.java
  • framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogFilterWrapper.java
  • framework/src/main/java/org/tron/core/utils/ResultCodeUtil.java
  • framework/src/main/resources/config.conf
  • framework/src/test/java/org/tron/core/db/TransactionRetStoreTest.java
  • framework/src/test/java/org/tron/core/jsonrpc/HandleLogsFilterTest.java
  • framework/src/test/java/org/tron/core/jsonrpc/JsonRpcCallAndEstimateGasTest.java
  • framework/src/test/java/org/tron/core/jsonrpc/JsonRpcPrunedHistoryTest.java
  • framework/src/test/java/org/tron/core/jsonrpc/JsonRpcTest.java
  • framework/src/test/java/org/tron/core/jsonrpc/JsonrpcServiceTest.java
  • framework/src/test/java/org/tron/core/jsonrpc/LogMatchOverLimitTest.java
  • framework/src/test/java/org/tron/core/jsonrpc/SectionBloomStoreTest.java
  • framework/src/test/java/org/tron/core/services/jsonrpc/JsonRpcErrorResolverTest.java
  • framework/src/test/java/org/tron/core/services/jsonrpc/JsonRpcServletTest.java
  • framework/src/test/java/org/tron/core/utils/ResultCodeUtilTest.java

Comment on lines +644 to +647
// "earliest" anchors to the receipt floor (first block with complete data); with
// receipt persistence off no such block exists — fall back to the body floor
long receiptFloor = wallet.getLowestReceiptBlockNum();
return receiptFloor == Long.MAX_VALUE ? wallet.getLowestBlockNum() : receiptFloor;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Resolve "earliest" from the block floor.

For a LiteNode, this code returns getLowestReceiptBlockNum(). The PR contract requires "earliest" to resolve to getLowestBlockNum(). When receipt retention starts later than block retention, block-number methods skip retained blocks.

Proposed fix
-      // "earliest" anchors to the receipt floor (first block with complete data); with
-      // receipt persistence off no such block exists — fall back to the body floor
-      long receiptFloor = wallet.getLowestReceiptBlockNum();
-      return receiptFloor == Long.MAX_VALUE ? wallet.getLowestBlockNum() : receiptFloor;
+      return wallet.getLowestBlockNum();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// "earliest" anchors to the receipt floor (first block with complete data); with
// receipt persistence off no such block exists — fall back to the body floor
long receiptFloor = wallet.getLowestReceiptBlockNum();
return receiptFloor == Long.MAX_VALUE ? wallet.getLowestBlockNum() : receiptFloor;
return wallet.getLowestBlockNum();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java`
around lines 644 - 647, Update the “earliest” resolution logic to always return
wallet.getLowestBlockNum(), removing the receiptFloor lookup and fallback so
LiteNode block-number methods use the retained block floor.

Comment on lines +729 to +734
long receiptFloor = wallet.getLowestReceiptBlockNum();
if (wallet.isLiteNode() && blockNum < receiptFloor) {
throw new JsonRpcPrunedHistoryException(receiptFloor == Long.MAX_VALUE
? PRUNED_HISTORY_ERROR + ": transaction history is not persisted on this node"
: prunedMessage(receiptFloor));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Handle disabled receipt persistence before comparing the floor.

When receipt persistence is disabled, receiptFloor is Long.MAX_VALUE. A request for 0x7fffffffffffffff passes the < receiptFloor check and does not return error 4444. Reject the sentinel state before the height comparison.

Proposed fix
   long receiptFloor = wallet.getLowestReceiptBlockNum();
-  if (wallet.isLiteNode() && blockNum < receiptFloor) {
-    throw new JsonRpcPrunedHistoryException(receiptFloor == Long.MAX_VALUE
-        ? PRUNED_HISTORY_ERROR + ": transaction history is not persisted on this node"
-        : prunedMessage(receiptFloor));
+  if (wallet.isLiteNode() && receiptFloor == Long.MAX_VALUE) {
+    throw new JsonRpcPrunedHistoryException(
+        PRUNED_HISTORY_ERROR + ": transaction history is not persisted on this node");
+  }
+  if (wallet.isLiteNode() && blockNum < receiptFloor) {
+    throw new JsonRpcPrunedHistoryException(prunedMessage(receiptFloor));
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
long receiptFloor = wallet.getLowestReceiptBlockNum();
if (wallet.isLiteNode() && blockNum < receiptFloor) {
throw new JsonRpcPrunedHistoryException(receiptFloor == Long.MAX_VALUE
? PRUNED_HISTORY_ERROR + ": transaction history is not persisted on this node"
: prunedMessage(receiptFloor));
}
long receiptFloor = wallet.getLowestReceiptBlockNum();
if (wallet.isLiteNode() && receiptFloor == Long.MAX_VALUE) {
throw new JsonRpcPrunedHistoryException(
PRUNED_HISTORY_ERROR + ": transaction history is not persisted on this node");
}
if (wallet.isLiteNode() && blockNum < receiptFloor) {
throw new JsonRpcPrunedHistoryException(prunedMessage(receiptFloor));
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java`
around lines 729 - 734, Update the receipt-history validation around
wallet.getLowestReceiptBlockNum() so a Long.MAX_VALUE receiptFloor immediately
throws JsonRpcPrunedHistoryException with the non-persisted-history message,
including when blockNum equals the sentinel. Keep the existing floor comparison
and prunedMessage(receiptFloor) behavior for finite receipt floors.

Comment on lines +300 to +303
// Any non-scalar id is rejected. Spec deviation: null is a valid id per JSON-RPC 2.0,
// but jsonrpc4j treats it as a notification and answers nothing, so -32600 beats silence.
JsonNode id = node.get("id");
return id != null && !id.isTextual() && !id.isNumber();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Accept an explicit null JSON-RPC ID.

id: null is a valid JSON-RPC 2.0 request ID. Strict mode currently returns -32600 for this request.

Preserve the explicit-null request through jsonrpc4j without converting it to a notification. Return a normal response with "id": null. Add single-request and batch tests for this case.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcServlet.java`
around lines 300 - 303, Update the JSON-RPC request validation around the id
handling so an explicitly present null id is accepted alongside textual and
numeric ids, while a missing id remains a notification only when appropriate.
Preserve the null id through jsonrpc4j so it produces a normal response
containing "id": null, and add single-request and batch coverage for
explicit-null ids.

Comment on lines +400 to +419
private Block getBlockByNumOrTag(String blockNumOrTag)
throws JsonRpcInvalidParamsException, JsonRpcPrunedHistoryException {
long blockNum;
if (JsonRpcApiUtil.isBlockTag(blockNumOrTag)) {
if (LATEST_STR.equalsIgnoreCase(blockNumOrTag)) {
// Return the head block directly from blockStore, bypassing blockIndexStore
// which may not yet be written when latestBlockHeaderNumber is already updated.
return wallet.getNowBlock();
}
return wallet.getBlockByNum(JsonRpcApiUtil.parseBlockTag(blockNumOrTag, wallet));
blockNum = JsonRpcApiUtil.parseBlockTag(blockNumOrTag, wallet);
} else {
blockNum = parseBlockNumber(blockNumOrTag);
}
// Reject a pruned height before touching any store, so a LiteNode pays no lookup for
// history it cannot serve. Genesis is exempt: a snapshot copies block 0 explicitly, and
// lowestBlockNum is computed from block 1 upwards, so block 0 is always retained.
if (blockNum > 0) {
JsonRpcApiUtil.checkPrunedHistory(blockNum, wallet);
}
return wallet.getBlockByNum(parseBlockNumber(blockNumOrTag));
return wallet.getBlockByNum(blockNum);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Resolve LiteNode earliest from the block floor.

The supplied JsonRpcApiUtil.parseBlockTag implementation resolves LiteNode earliest to getLowestReceiptBlockNum(). This makes block selector methods skip retained blocks between the body floor and receipt floor. It conflicts with the PR objective that earliest resolves to lowestBlockNum.

  • framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java#L400-L419: resolve earliest block selectors from wallet.getLowestBlockNum(). Keep receipt-floor checks limited to receipt and log access.
  • framework/src/test/java/org/tron/core/jsonrpc/JsonRpcPrunedHistoryTest.java#L86-L90: expect LOWEST_BLOCK_NUM, not RECEIPT_FLOOR_BLOCK_NUM.
📍 Affects 2 files
  • framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java#L400-L419 (this comment)
  • framework/src/test/java/org/tron/core/jsonrpc/JsonRpcPrunedHistoryTest.java#L86-L90
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java`
around lines 400 - 419, Update getBlockByNumOrTag in
framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java:400-419
so the earliest block tag resolves from wallet.getLowestBlockNum(), while
retaining receipt-floor checks only for receipt and log access; update
framework/src/test/java/org/tron/core/jsonrpc/JsonRpcPrunedHistoryTest.java:86-90
to expect LOWEST_BLOCK_NUM instead of RECEIPT_FLOOR_BLOCK_NUM.

if (exception instanceof Program.InvalidCodeException) {
return contractResult.INVALID_CODE;
}
return contractResult.UNKNOWN;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Map Program.StaticCallModificationException before the fallback.

ResultCodeUtil.resolve sends this exception to contractResult.UNKNOWN. The new parity test includes this exception. This can fail testStaysInSyncWithConsensusClassifier and report the wrong constant-call result code.

Proposed fix
     if (exception instanceof Program.InvalidCodeException) {
       return contractResult.INVALID_CODE;
     }
+    if (exception instanceof Program.StaticCallModificationException) {
+      return contractResult.STATIC_CALL_MODIFICATION;
+    }
     return contractResult.UNKNOWN;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/src/main/java/org/tron/core/utils/ResultCodeUtil.java` at line 46,
Update ResultCodeUtil.resolve to recognize
Program.StaticCallModificationException and return the same result code used by
the consensus classifier before the existing contractResult.UNKNOWN fallback;
preserve the fallback for all other unrecognized exceptions.

Comment thread framework/src/test/java/org/tron/core/db/TransactionRetStoreTest.java Outdated
Comment thread framework/src/test/java/org/tron/core/jsonrpc/JsonRpcCallAndEstimateGasTest.java Outdated
- probe the lowest receipted block from transactionRetStore at startup
- eth_getLogs / eth_newFilter / eth_getBlockReceipts answer 4444 below it
- resolve "earliest" to the receipt floor on a LiteNode
@0xbigapple
0xbigapple force-pushed the feature/jsonrpc-error-handling branch from a831507 to 7a7c797 Compare August 12, 2026 05:50
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