Skip to content

feat(jsonrpc): return code 3 for revert and 4444 for pruned history - #7004

Merged
kuny0707 merged 12 commits into
tronprotocol:release_v4.8.3from
0xbigapple:feature/jsonrpc-revert-and-pruned-history
Oct 15, 2026
Merged

kuny0707 merged 12 commits into
tronprotocol:release_v4.8.3from
0xbigapple:feature/jsonrpc-revert-and-pruned-history

Conversation

@0xbigapple

@0xbigapple 0xbigapple commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

First of two PRs for #6676; request validation is split out (see Follow up).

What does this PR do?

Aligns two JSON-RPC error behaviors with the Ethereum Execution API:

  1. Contract revert returns code 3. eth_call and eth_estimateGas return code: 3 instead of -32000; other execution failures stay -32000. Wallet.callConstantContract now records contractRet on the result, so the JSON-RPC layer classifies on the enum rather than on the error message.

  2. Pruned history returns 4444 "Pruned history unavailable".

    • Block-body methods (eth_getBlockByNumber, eth_getBlockTransactionCountByNumber, eth_getTransactionByBlockNumberAndIndex): on a LiteNode, heights below the lowest stored block. Genesis stays queryable.
    • Receipt and log methods (eth_getBlockReceipts, eth_getLogs, eth_newFilter), including when the block is selected by hash: on any node when transHistory.switch = off; otherwise on a LiteNode, heights below the lowest receipt block (the first block in transactionRetStore, read at startup). A FullNode has no such check — receipts from before June 2019 may still be served from TransactionHistoryStore, so the store's first key is not a lower bound — and receipt gaps on a FullNode keep the current behavior.
    • As in geth, 4444 carries no block number; data is the "{}" placeholder every java-tron JSON-RPC error has.
    • earliest resolves to the lowest block with all persisted data: on a LiteNode the lowest receipt block (the lowest stored block when receipts are not persisted), on a FullNode 0.

Why are these changes required?

  • Under -32000, a contract revert cannot be told apart from out-of-energy or any other TVM failure.
  • A LiteNode returns null for pruned blocks, which reads as "block does not exist", and eth_getLogs across the cutoff returns incomplete results without an error.

Breaking changes (release-note items):

  • eth_call / eth_estimateGas: a contract revert returns 3 instead of -32000.
  • LiteNode: heights below the lowest available block return 4444 instead of null on block-body methods, and instead of -32000 or incomplete results on receipt and log methods.
  • Any node with transHistory.switch = off: receipt and log queries return 4444, including for receipts already on disk.
  • earliest on a LiteNode resolves to the lowest available block instead of 0.
  • eth_getTransactionByBlockNumberAndIndex / eth_getTransactionByBlockHashAndIndex: an invalid index returns -32602 instead of null when the block is missing or pruned.

This PR has been tested by:

  • Unit tests — revert classification, the pruned-history rules on both node types with the switch on and off, reading the lowest receipt block at startup (through context startup, and with the switch off), the 4444 / 3 mappings on every affected TronJsonRpc method, and resolver declaration-order independence.
  • Manual testing — 141 JSON-RPC cases against the full and solidity endpoints, on Nile and mainnet LiteNode snapshots (switch on and off) and on a private-chain FullNode (switch on, off, and the receipt gap left after turning it back on). After the review fixes, re-run on a private-chain FullNode and a private LiteNode snapshot (switch on and off): 106/106.

Follow up

Extra details

  • The solidity JSON-RPC endpoint follows the same rules.
  • Side effect (not breaking): gRPC/HTTP triggerConstantContract responses now carry contractRet: REVERT when the call reverts, and so does triggerSmartContract when its selector is a constant ABI function.
  • Every REVERT returns 3, including one with empty revert data.
  • Only single-block and genesis-only queries are exempt for genesis: eth_getLogs from 0x0 to a later block on a LiteNode returns 4444, as geth does.
  • Hash-based selectors, refining "null-only" from [Feature]Standardize JSON-RPC error handling(revert codes, LiteNode pruned-history responses, request fields validation) #6676: a pruned block cannot be found by hash, so a pruned hash cannot be told apart from an unknown one, and those lookups keep their current response: null, or -32602 invalid blockHash on eth_getLogs / eth_newFilter. When eth_getBlockReceipts or eth_getLogs does find the block by hash, its number is known and the receipt rules above apply. eth_getBlockByHash, eth_getTransactionByHash and eth_getTransactionReceipt are unchanged.

Known limitations

  • On a LiteNode, blocks between the lowest stored block and the lowest receipt block are served, but receipt-derived fields read as zero: block gasUsed and transaction gas. Clients cannot tell missing receipt data from actual zero usage. This follows from the availability rules above and goes away once the Toolkit follow-up aligns the two boundaries.

- eth_call / eth_estimateGas: contract revert changes from -32000 to 3
- data a node does not have (pruned on a LiteNode, or receipts with
  transHistory.switch off) returns 4444 "Pruned history unavailable"
- 4444 no longer carries the earliest available block in data, as geth
- remove ResultCodeUtil; constant calls record only REVERT
- test that a reverted constant call sets contractRet to REVERT
…ove init methods to the top

- rename probeLowestBlockNumOfReceiptStore to initLowestBlockNumOfReceiptStore
- move init and initLowestBlockNumOfReceiptStore to the top of ChainBaseManager methods
@github-actions
github-actions Bot requested a review from bladehan1 September 28, 2026 13:11
@0xbigapple
0xbigapple requested review from 317787106 and waynercheung and removed request for bladehan1 September 28, 2026 13:11
@halibobo1205 halibobo1205 added this to the GreatVoyage-v4.8.3 milestone Sep 29, 2026
@317787106

Copy link
Copy Markdown
Collaborator

A few points from the parameter-handling review. These issues predate this PR, but the new 4444 path also interacts with the validation order:

  1. Validate eth_call block objects consistently.
    A numeric selector such as "0x64" is rejected, while {"blockNumber":"0x64"} only checks that the block exists and then silently switches execution to latest. Supplying both blockNumber and blockHash, or a non-boolean requireCanonical, is also accepted. If historical execution is unsupported, please reject these selectors consistently and validate the object’s fields instead of silently ignoring them.
  2. Preserve invalid-parameter errors in eth_estimateGas.
    args.parseValue() runs inside a broad catch (Exception), which converts JsonRpcInvalidParamsException into JsonRpcInternalException. Consequently, malformed, negative, or overflowing values return -32000, whereas the same values in eth_call return -32602. Please parse these parameters before the execution block, or rethrow JsonRpcInvalidParamsException unchanged.
  3. Validate transaction indices before block lookup and pruning checks.
    Both transaction-by-block-and-index methods currently validate the index only after finding the block. An invalid index therefore returns -32602 for an existing block, null for a missing block, and now 4444 for a pruned height. Please validate the selector and index syntax before querying availability.

Some optional naming improvements:

  • earliestAvailableBlock → resolveEarliestBlockNum: makes both the tag-resolution role and numeric return value explicit.
  • checkPrunedHistory → checkPrunedBlockHistory: distinguishes the block-body check from checkPrunedReceiptHistory.
  • bnOrId → blockNumOrTag: matches the implementation and accepted selector types.
  • getABIOfSmartContract → getContractCode: the existing method implements eth_getCode and returns runtime bytecode, rather than an ABI.

The existing issues outside this PR’s scope can be handled in this PR or follow-ups.

@0xbigapple

0xbigapple commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator Author

A few points from the parameter-handling review. These issues predate this PR, but the new 4444 path also interacts with the validation order:

  1. Validate eth_call block objects consistently.
    A numeric selector such as "0x64" is rejected, while {"blockNumber":"0x64"} only checks that the block exists and then silently switches execution to latest. Supplying both blockNumber and blockHash, or a non-boolean requireCanonical, is also accepted. If historical execution is unsupported, please reject these selectors consistently and validate the object’s fields instead of silently ignoring them.
  2. Preserve invalid-parameter errors in eth_estimateGas.
    args.parseValue() runs inside a broad catch (Exception), which converts JsonRpcInvalidParamsException into JsonRpcInternalException. Consequently, malformed, negative, or overflowing values return -32000, whereas the same values in eth_call return -32602. Please parse these parameters before the execution block, or rethrow JsonRpcInvalidParamsException unchanged.
  3. Validate transaction indices before block lookup and pruning checks.
    Both transaction-by-block-and-index methods currently validate the index only after finding the block. An invalid index therefore returns -32602 for an existing block, null for a missing block, and now 4444 for a pruned height. Please validate the selector and index syntax before querying availability.

Some optional naming improvements:

  • earliestAvailableBlock → resolveEarliestBlockNum: makes both the tag-resolution role and numeric return value explicit.
  • checkPrunedHistory → checkPrunedBlockHistory: distinguishes the block-body check from checkPrunedReceiptHistory.
  • bnOrId → blockNumOrTag: matches the implementation and accepted selector types.
  • getABIOfSmartContract → getContractCode: the existing method implements eth_getCode and returns runtime bytecode, rather than an ABI.

The existing issues outside this PR’s scope can be handled in this PR or follow-ups.

@317787106 Thanks.
3. Fixed: the index is now validated before the block lookup.
1, 2. Will handle in follow-ups.
Naming: renamed earliestAvailableBlock and checkPrunedHistory as suggested; bnOrId and getABIOfSmartContract are pre-existing and left as is.

…sonrpc-revert-and-pruned-history

# Conflicts:
#	framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java
#	framework/src/test/java/org/tron/core/jsonrpc/JsonrpcServiceTest.java
#	framework/src/test/java/org/tron/core/services/jsonrpc/JsonRpcErrorResolverTest.java

@waynercheung waynercheung left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-checked 66b53b5 against release_v4.8.3 at 7ebc2eb.

The merge preserves the relevant validation order: eth_getBlockByNumber validates fullTransactionObjects before the pruning check, and both by-index methods validate the index before lookup. The receipt-floor rules and direct JsonRpcException inheritance match the design settled in #6676, and the 4444 response keeps the specified message without cutoff metadata.

I did not find a blocking correctness issue. Please address the two test gaps noted inline and make sure CI passes on the final head before approval.

[SHOULD] Please document the retained-body receipt gap as a known limitation. Between the body floor and the receipt floor, body queries still return receipt-derived fields as zero (block gasUsed, transaction gas). This is existing behavior under the agreed availability policy, but clients cannot tell missing receipt data from actual zero usage.

[SHOULD] Please update the reference.conf / config.conf comments to say that transHistory.switch = off also disables receipt/log queries even when receipts remain on disk (genesis-only queries excepted). The PR description already lists this change; please carry it into the release notes.

[QUESTION] The revert message stays REVERT opcode executed[: reason], while geth uses execution reverted[: reason]. Is keeping it intentional? If so, please state it in the description.

[QUESTION] Could you link the tracking issues for the deferred eth_call block-object validation, estimateGas parameter-error handling, Toolkit receipt retention, and the docs-en / docs-zh error-code updates?

[NIT]

  • TransactionRetStore.getLowestBlockNum() shares its name with ChainBaseManager.getLowestBlockNum() / Wallet.getLowestBlockNum(), which mean the body floor; getLowestReceiptBlockNum() would avoid the ambiguity.
  • Description: contractRet: REVERT also reaches triggersmartcontract when its selector is a constant ABI function.
  • Description: pin or remove the empty-revert comparison with geth; v1.17.5 returns code 3 for empty revert data.
  • Description: narrow the unknown-hash "null" wording; eth_getLogs / eth_newFilter keep returning -32602 invalid blockHash.

Comment thread framework/src/main/java/org/tron/core/db/Manager.java
- rename TransactionRetStore.getLowestBlockNum to getLowestReceiptBlockNum
- note that transHistory.switch off disables receipt and log queries
@0xbigapple

0xbigapple commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator Author

Re-checked 66b53b5 against release_v4.8.3 at 7ebc2eb.

The merge preserves the relevant validation order: eth_getBlockByNumber validates fullTransactionObjects before the pruning check, and both by-index methods validate the index before lookup. The receipt-floor rules and direct JsonRpcException inheritance match the design settled in #6676, and the 4444 response keeps the specified message without cutoff metadata.

I did not find a blocking correctness issue. Please address the two test gaps noted inline and make sure CI passes on the final head before approval.

[SHOULD] Please document the retained-body receipt gap as a known limitation. Between the body floor and the receipt floor, body queries still return receipt-derived fields as zero (block gasUsed, transaction gas). This is existing behavior under the agreed availability policy, but clients cannot tell missing receipt data from actual zero usage.

[SHOULD] Please update the reference.conf / config.conf comments to say that transHistory.switch = off also disables receipt/log queries even when receipts remain on disk (genesis-only queries excepted). The PR description already lists this change; please carry it into the release notes.

[QUESTION] The revert message stays REVERT opcode executed[: reason], while geth uses execution reverted[: reason]. Is keeping it intentional? If so, please state it in the description.

[QUESTION] Could you link the tracking issues for the deferred eth_call block-object validation, estimateGas parameter-error handling, Toolkit receipt retention, and the docs-en / docs-zh error-code updates?

[NIT]

  • TransactionRetStore.getLowestBlockNum() shares its name with ChainBaseManager.getLowestBlockNum() / Wallet.getLowestBlockNum(), which mean the body floor; getLowestReceiptBlockNum() would avoid the ambiguity.
  • Description: contractRet: REVERT also reaches triggersmartcontract when its selector is a constant ABI function.
  • Description: pin or remove the empty-revert comparison with geth; v1.17.5 returns code 3 for empty revert data.
  • Description: narrow the unknown-hash "null" wording; eth_getLogs / eth_newFilter keep returning -32602 invalid blockHash.

@waynercheung Thanks for the re-check.

  • Test gaps: addressed inline.
  • Receipt gap: added under Known limitations in the description.
  • Config comments: updated reference.conf and config.conf; the release-note line is already under Breaking changes.
  • Revert message: intentional for now. REVERT opcode executed is the TVM's own revert message and already states the failure clearly, so we keep it.
  • Tracking: the eth_call block object and the eth_estimateGas value error predate this PR and are out of its scope; they are recorded in #6676. Toolkit receipts: will open a separate issue. The docs-en / docs-zh error-code pages will be updated with the 4.8.3 release.
  • Renamed TransactionRetStore.getLowestBlockNum() to getLowestReceiptBlockNum().
  • Description NITs: fixed.

@0xbigapple
0xbigapple requested review from lvs0075 and lxcmyf October 14, 2026 02:58
@lvs0075
lvs0075 removed their request for review October 14, 2026 03:03
@lvs0075
lvs0075 removed their request for review October 14, 2026 10:02

@waynercheung waynercheung left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-reviewed 6e84880 and tested a local merge with release_v4.8.3 at 4db3688. The merge is clean.

Both previous test gaps are addressed. On the merged tree, removing the 4444 mappings, removing the 3 mappings, removing the Manager init call, or replacing Long.MAX_VALUE with getHeadBlockNum() + 1 each fails a test.

I rechecked the revert classification, receipt-floor initialization, pruning boundaries and validation order, and found no blocking issues. GitHub CI for the PR head is green.

[NIT] Please link the Toolkit receipt-retention issue once it is open, and update the request-envelope follow-up to reference the merged PR #7032.

Approved.

@0xbigapple
0xbigapple requested a review from 3for October 15, 2026 05:34
Comment thread chainbase/src/main/java/org/tron/core/ChainBaseManager.java
Comment thread framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpc.java Outdated
Comment thread framework/src/test/java/org/tron/core/WalletTest.java Outdated
Comment thread common/src/main/resources/reference.conf Outdated
Comment thread chainbase/src/main/java/org/tron/core/ChainBaseManager.java
- run the constant revert test with debug on to skip the cpu time limit
- clarify transHistory.switch comment in reference.conf and config.conf
- drop unused throws from getFilterChanges

@lxcmyf lxcmyf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed the revert classification and pruned-history handling on 6e84880, including startup receipt-floor initialization, body/receipt/log cutoff boundaries, genesis exceptions, error-code resolution, and validation order. Targeted tests and static security scans pass. No additional blocking issues found.

@0xbigapple

Copy link
Copy Markdown
Collaborator Author

Re-reviewed 6e84880 and tested a local merge with release_v4.8.3 at 4db3688. The merge is clean.

Both previous test gaps are addressed. On the merged tree, removing the 4444 mappings, removing the 3 mappings, removing the Manager init call, or replacing Long.MAX_VALUE with getHeadBlockNum() + 1 each fails a test.

I rechecked the revert classification, receipt-floor initialization, pruning boundaries and validation order, and found no blocking issues. GitHub CI for the PR head is green.

[NIT] Please link the Toolkit receipt-retention issue once it is open, and update the request-envelope follow-up to reference the merged PR #7032.

Approved.

@waynercheung Thanks. The request-envelope follow-up now references #7032. I'll link the Toolkit issue once it is open.

@kuny0707
kuny0707 merged commit 3b35e94 into tronprotocol:release_v4.8.3 Oct 15, 2026
10 checks passed
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.

10 participants