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

0X
15d ago
+1071-165

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.

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, 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

  • Request-envelope validation (jsonrpc version, non-string method, explicit id: null) in a new PR.
  • Toolkit: retain the snapshot window's receipts so bodies and receipts share one boundary.

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.
  • Every REVERT returns 3, including one with empty revert data; geth returns -32000 in that case.
  • 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 #6676: a pruned block cannot be found by hash, so a pruned hash cannot be told apart from an invalid one, and those lookups keep returning null. 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.