Skip to content

feat(jsonrpc): implement base eth_simulateV1 JSON-RPC method#6785

Open
APshenkin wants to merge 4 commits into
tronprotocol:developfrom
APshenkin:feature/simulate-mvp
Open

feat(jsonrpc): implement base eth_simulateV1 JSON-RPC method#6785
APshenkin wants to merge 4 commits into
tronprotocol:developfrom
APshenkin:feature/simulate-mvp

Conversation

@APshenkin
Copy link
Copy Markdown

@APshenkin APshenkin commented May 19, 2026

What does this PR do?

Implements geth's eth_simulateV1 on java-tron's JSON-RPC surface for the MVP trading-flow use case: a single round-trip that runs N dependent calls against current head state and returns each call's effect plus synthetic transfer logs.

The endpoint is opt-in via existing eth_call-style flags; no existing behaviour changes.

Why are these changes required?

This is first step to resolve #6199

Tron's JSON-RPC exposes eth_call but not eth_simulateV1. Two concrete consumers benefit:

  1. Trading flows that build raw EVM transactions that depend on each other (approval → swap → settle) and need to verify the actual output of the chain — what each step returns, which logs it emits, how balances move — before broadcasting.
  2. Wallets integrating with dapp-connect protocols (WalletConnect-style sign requests). Before the user signs, the wallet UI wants to show what the transaction will actually do: token transfers in/out, TRC20/TRX value movement, contract state changes. eth_simulateV1 lets the wallet run the unsigned transaction against current head state and decode the resulting transfer logs directly into a human-readable diff.

Both consumers simulate against current head state only — they don't need historical-block context or state overrides. The current implementation covers those cases end-to-end and is sufficient for what we expect to be the majority of eth_simulateV1 usage on Tron.

Future work needed for the remaining use cases (debugging historical txs, what-if analysis with state overrides) requires changes in the archive node to support simulation on a specific block + stateOverrides / blockOverrides. That's intentionally out of scope here.

JSON-RPC surface

  • One simulation block per request: blockStateCalls: [{ calls: [...] }]. Multi-block / blockOverrides / stateOverrides-32602. blockOverrides and stateOverrides are excluded by design — both consumers in the Motivation section simulate against current head state only, and supporting overrides would require the same archive-node plumbing called out as future work (rewinding to a specific block, applying account/storage patches before VM execution). Rejecting them with a clear error is better than silently ignoring them. Hard cap of 32 calls per block — geth's defaults (5000/block, 10000 total) are tuned for general-purpose use; our concrete cases (trading-flow approval+swap+settle, wallet preview of a single user-signed tx that fans out a few internal calls) realistically stay under ~10. Capping at 32 leaves comfortable headroom while bounding per-request memory: at ~10KB of accumulated state per call, the shared root's in-memory cache stays well under ~1MB worst-case (vs ~50MB at 5000). Anything beyond that should either be a separate request or signal misuse.
  • blockNumOrTag: only "latest" and "pending" accepted; both resolve to the head block (Tron has instant finality — no mempool state distinct from latest).
  • traceTransfers: true synthesizes logs at the ERC-7528 native pseudo-address (0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE), distinguished by topic[0]:
    • Transfer(address,address,uint256) for native TRX moves.
    • TRC10Transfer(address,address,uint256,uint256) for TRC-10 moves — topic[3] carries tokenId so consumers can filter logs by asset via topics; data carries amount.
  • returnFullTransactions: true returns block.transactions as TransactionResult objects (synthetic deterministic hash, gasPrice = "0x0", nonce = "0x0", v/r/s zero, blockHash = keccak256("sim:" + headHash + ":1")). Default false returns hash strings.
  • validation: true — Tron-flavored. Geth's checks (baseFee / gasPrice / nonce) don't apply to Tron, so we pre-check per call: sender account exists and sender balance ≥ callValue. Failed pre-check → status: "0x0" with errorMessage, VM not invoked. Default false preserves the existing constant-call permissive behaviour.

Tracing

Five hook sites per transfer kind, each firing after the real balance change succeeds:

Site TRX TRC-10
VMActuator.call() (depth 0) after MUtil.transfer after MUtil.transferToken
VMActuator.create() (depth 0) after MUtil.transfer after MUtil.transferToken
Program.callToAddress (depth ≥1) after addBalance pair after addTokenBalance pair
Program.callToPrecompiledAddress (depth ≥1) after precompile transfer after addTokenBalance pair
Program.suicide / suicide2 after MUtil.transfer new transferAllTokenWithTrace helper — snapshot assetMapV2 before MUtil.transferAllToken, emit one entry per non-zero asset

DELEGATECALL / CALLCODE are explicitly skipped (no real value transfer even when senderAddress != contextAddress).

A per-frame buffer (BufferingSimulationTracer) with a unified seq counter interleaves explicit LOG opcodes with both synthetic kinds in emission order. Reverted frames drop their entries; logIndex still increments through gaps (matches geth's logtracer.go:128).

Implementation

  • New listener SPI: SimulationTracer (enterFrame / exitFrame / revertFrame / onTransfer / onTokenTransfer / onLog). Default impl BufferingSimulationTracer owns the frame stack and seq counter.
  • VMActuator gets opt-in setters (setInjectedRootRepository, setSimulationTracer). When the injected root is null, the existing fresh-root code path runs unchanged.
  • Program propagates the tracer into child Program instances at every sub-call origination site so nested CALL/CREATE moves are captured.
  • Wallet.simulateConstantContracts is the new entry point. It builds the shared root + per-call child Repositories and shares the per-call execute body with the existing callConstantContract via a new private executeOneConstantInternal helper.
  • DTOs: SimulateV1Args, SimulateBlock (uses @JsonAnySetter to detect forward-incompatible field names), SimulateCallResult, SimulateBlockResult extends BlockResult. Reuses LogFilterElement, CallArguments, TransactionResult (additive raw-fields constructor for synthetic full-tx output).

This PR has been tested by:

Unit

20 tests total:

gradle :framework:test --tests "org.tron.core.jsonrpc.EthSimulateV1*"

Unit (EthSimulateV1ArgsTest, 11 tests)

-32602 input-validation surface, JSON round-trip of SimulateBlockResult, non-numeric tokenId rejection. Mocked Wallet, no chain context.

Integration (EthSimulateV1IntegrationTest, 9 tests, BaseTest + LevelDB)

  • stateSharingAcrossCallsset(42)get() returns 42 in one simulate; on-chain slot unchanged.
  • revertIsolatesPerCallset(99)setRevert(123)get() returns 99.
  • validationRejectsUnactivatedSendervalidation: true with a never-seen from"sender account does not exist".
  • validationRejectsInsufficientBalancevalidation: true with value > balance"insufficient balance for value".
  • createPopulatesContractAddress — CREATE call sets contractAddress to the actual deployed address (read from the VM, not re-synthesized).
  • returnFullTransactionsShape — verifies both response shapes, deterministic hash equality across runs.
  • traceTrc10TopLevelCall — owner sends 50 units of token 1000001 to the pre-deployed contract; verifies all four topics + data; owner on-chain TRC-10 balance unchanged.
  • traceTrc10MixedWithTrx — single call with both value: "0x64" and tokenValue: "0x32" to an "accept-anything" sink contract; both synthetic logs emitted in TRX-first order.
  • buffering_dropsTokenTransferOnRevertFrame — direct buffer exercise: revertFrame drops a buffered onTokenTransfer.

Manual Testing

Launched Nile testnet node with changes and run multiple commands with different setups:

Requests and Responses are long, so hide under spoiler
// simulate TRC10 internal transactions
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_simulateV1",
  "params": [
    {
      "blockStateCalls": [
        {
          "calls": [
            {
              "from": "0x93a8ec1D0698a3873E942A4e3b65A6c20F7310d3",
              "to": "0xCa77E26553571DCD4e5e5377F39D49a17da4c1d2",
              "data": "0x1dc9139d00000000000000000000000093a8ec1d0698a3873e942a4e3b65a6c20f7310d300000000000000000000000000000000000000000000000000000000000f5e880000000000000000000000000000000000000000000000000000000000000007",
              "value": "0x0"
            }
          ]
        }
      ],
      "traceTransfers": true,
      "returnFullTransactions": false,
      "validation": true
    },
    "latest"
  ]
}

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    {
      "baseFeePerGas": "0x0",
      "calls": [
        {
          "contractAddress": "0xca77e26553571dcd4e5e5377f39d49a17da4c1d2",
          "gasUsed": "0x1c09",
          "logs": [
            {
              "address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
              "blockHash": "0x74658adc76f7767802356a499daff01c2ceb508c8b6001f72630f228be52b606",
              "blockNumber": "0x4070777",
              "blockTimestamp": "0x6a0c63ab",
              "data": "0x0000000000000000000000000000000000000000000000000000000000000007",
              "logIndex": "0x0",
              "removed": false,
              "topics": [
                "0xe917b6ed800090e45df2bef0c9b375f409f3565e1ecada6f3e04aca5bb191ea0",
                "0x000000000000000000000000ca77e26553571dcd4e5e5377f39d49a17da4c1d2",
                "0x00000000000000000000000093a8ec1d0698a3873e942a4e3b65a6c20f7310d3",
                "0x00000000000000000000000000000000000000000000000000000000000f5e88"
              ],
              "transactionHash": "0xfdaac4eb9c153654b21f434574514011d839c8bde04a0ed0122b8850bbda5c65",
              "transactionIndex": "0x0"
            }
          ],
          "returnData": "0x",
          "status": "0x1",
          "transactionHash": "0xfdaac4eb9c153654b21f434574514011d839c8bde04a0ed0122b8850bbda5c65",
          "transactionIndex": "0x0"
        }
      ],
      "difficulty": "0x0",
      "extraData": "0x",
      "gasLimit": "0x5f5e100",
      "gasUsed": "0x1c09",
      "hash": "0x74658adc76f7767802356a499daff01c2ceb508c8b6001f72630f228be52b606",
      "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
      "miner": "0x0000000000000000000000000000000000000000",
      "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "nonce": "0x0000000000000000",
      "number": "0x4070777",
      "parentHash": "0x0000000004070776f962952b0d110f2f4955edc721b308c0642dc1f890d1d812",
      "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "sha3Uncles": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "size": "0x0",
      "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "timestamp": "0x6a0c63ad",
      "totalDifficulty": "0x0",
      "transactions": [
        "0xfdaac4eb9c153654b21f434574514011d839c8bde04a0ed0122b8850bbda5c65"
      ],
      "transactionsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "uncles": []
    }
  ]
}

// simulate TRX internal transactions (WTRX withdraw method)
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_simulateV1",
  "params": [
    {
      "blockStateCalls": [
        {
          "calls": [
            {
              "from": "0x72f09FB677a83C9CD56cE1A4179afEbC348Fc6D4",
              "to": "0xfb3b3134F13CcD2C81F4012E53024e8135d58FeE",
              "data": "0x2e1a7d4d000000000000000000000000000000000000000000000000000000000082ad8c",
              "value": "0x0"
            }
          ]
        }
      ],
      "traceTransfers": true,
      "returnFullTransactions": true,
      "validation": false
    },
    "latest"
  ]
}

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    {
      "baseFeePerGas": "0x0",
      "calls": [
        {
          "contractAddress": "0xfb3b3134f13ccd2c81f4012e53024e8135d58fee",
          "gasUsed": "0x3697",
          "logs": [
            {
              "address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
              "blockHash": "0x2534e0fd73b47c60f93ca4fb8abf2e8ca09350666a33d4f8d3566f7a3b5dcceb",
              "blockNumber": "0x40707c2",
              "blockTimestamp": "0x6a0c648c",
              "data": "0x000000000000000000000000000000000000000000000000000000000082ad8c",
              "logIndex": "0x0",
              "removed": false,
              "topics": [
                "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
                "0x000000000000000000000000fb3b3134f13ccd2c81f4012e53024e8135d58fee",
                "0x00000000000000000000000072f09fb677a83c9cd56ce1a4179afebc348fc6d4"
              ],
              "transactionHash": "0x655b5bc23d3cfbbe6f6831921100615af427ed485ef1ad749e69df3911955fa8",
              "transactionIndex": "0x0"
            },
            {
              "address": "0xfb3b3134f13ccd2c81f4012e53024e8135d58fee",
              "blockHash": "0x2534e0fd73b47c60f93ca4fb8abf2e8ca09350666a33d4f8d3566f7a3b5dcceb",
              "blockNumber": "0x40707c2",
              "blockTimestamp": "0x6a0c648c",
              "data": "0x000000000000000000000000000000000000000000000000000000000082ad8c",
              "logIndex": "0x1",
              "removed": false,
              "topics": [
                "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65",
                "0x00000000000000000000000072f09fb677a83c9cd56ce1a4179afebc348fc6d4"
              ],
              "transactionHash": "0x655b5bc23d3cfbbe6f6831921100615af427ed485ef1ad749e69df3911955fa8",
              "transactionIndex": "0x0"
            }
          ],
          "returnData": "0x",
          "status": "0x1",
          "transactionHash": "0x655b5bc23d3cfbbe6f6831921100615af427ed485ef1ad749e69df3911955fa8",
          "transactionIndex": "0x0"
        }
      ],
      "difficulty": "0x0",
      "extraData": "0x",
      "gasLimit": "0x5f5e100",
      "gasUsed": "0x3697",
      "hash": "0x2534e0fd73b47c60f93ca4fb8abf2e8ca09350666a33d4f8d3566f7a3b5dcceb",
      "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
      "miner": "0x0000000000000000000000000000000000000000",
      "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "nonce": "0x0000000000000000",
      "number": "0x40707c2",
      "parentHash": "0x00000000040707c1987b1b6a3cfa11bcdd920bf3542d411f86aa9fc35867a32a",
      "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "sha3Uncles": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "size": "0x0",
      "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "timestamp": "0x6a0c648e",
      "totalDifficulty": "0x0",
      "transactions": [
        "0x655b5bc23d3cfbbe6f6831921100615af427ed485ef1ad749e69df3911955fa8"
      ],
      "transactionsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "uncles": []
    }
  ]
}

// simulate TRX internal transactions (WTRX deposit method)
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_simulateV1",
  "params": [
    {
      "blockStateCalls": [
        {
          "calls": [
            {
              "from": "0x72f09FB677a83C9CD56cE1A4179afEbC348Fc6D4",
              "to": "0xfb3b3134F13CcD2C81F4012E53024e8135d58FeE",
              "data": "0xd0e30db0",
              "value": "0x4C4B40"
            }
          ]
        }
      ],
      "traceTransfers": true,
      "returnFullTransactions": false
    },
    "latest"
  ]
}

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    {
      "baseFeePerGas": "0x0",
      "calls": [
        {
          "contractAddress": "0xfb3b3134f13ccd2c81f4012e53024e8135d58fee",
          "gasUsed": "0x1aa0",
          "logs": [
            {
              "address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
              "blockHash": "0x0baf7e3a971363b293befab76a47e2e49fcd7edc6ec30b649cc02b5a232302d1",
              "blockNumber": "0x40707c8",
              "blockTimestamp": "0x6a0c649e",
              "data": "0x00000000000000000000000000000000000000000000000000000000004c4b40",
              "logIndex": "0x0",
              "removed": false,
              "topics": [
                "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
                "0x00000000000000000000000072f09fb677a83c9cd56ce1a4179afebc348fc6d4",
                "0x000000000000000000000000fb3b3134f13ccd2c81f4012e53024e8135d58fee"
              ],
              "transactionHash": "0xfda63659d0ba50fe6f10e51da3f03057a7ed2077dd8ccb9a6867ba68fe32cd2b",
              "transactionIndex": "0x0"
            },
            {
              "address": "0xfb3b3134f13ccd2c81f4012e53024e8135d58fee",
              "blockHash": "0x0baf7e3a971363b293befab76a47e2e49fcd7edc6ec30b649cc02b5a232302d1",
              "blockNumber": "0x40707c8",
              "blockTimestamp": "0x6a0c649e",
              "data": "0x00000000000000000000000000000000000000000000000000000000004c4b40",
              "logIndex": "0x1",
              "removed": false,
              "topics": [
                "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c",
                "0x00000000000000000000000072f09fb677a83c9cd56ce1a4179afebc348fc6d4"
              ],
              "transactionHash": "0xfda63659d0ba50fe6f10e51da3f03057a7ed2077dd8ccb9a6867ba68fe32cd2b",
              "transactionIndex": "0x0"
            }
          ],
          "returnData": "0x",
          "status": "0x1",
          "transactionHash": "0xfda63659d0ba50fe6f10e51da3f03057a7ed2077dd8ccb9a6867ba68fe32cd2b",
          "transactionIndex": "0x0"
        }
      ],
      "difficulty": "0x0",
      "extraData": "0x",
      "gasLimit": "0x5f5e100",
      "gasUsed": "0x1aa0",
      "hash": "0x0baf7e3a971363b293befab76a47e2e49fcd7edc6ec30b649cc02b5a232302d1",
      "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
      "miner": "0x0000000000000000000000000000000000000000",
      "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "nonce": "0x0000000000000000",
      "number": "0x40707c8",
      "parentHash": "0x00000000040707c759842988bd1086303dcd1bb8a67a606d7dc6cfb2b8cb183c",
      "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "sha3Uncles": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "size": "0x0",
      "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "timestamp": "0x6a0c64a0",
      "totalDifficulty": "0x0",
      "transactions": [
        "0xfda63659d0ba50fe6f10e51da3f03057a7ed2077dd8ccb9a6867ba68fe32cd2b"
      ],
      "transactionsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "uncles": []
    }
  ]
}

// simulate error return
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_simulateV1",
  "params": [
    {
      "blockStateCalls": [
        {
          "calls": [
            {
              "from": "0x41C2BED267CAD1D3FCF2AB8DAF21A30B02E502FAA0",
              "to": "0x4123065A627120849CCCFEA5F8C6436EF4C1B65D66",
              "data": "0xcef952290000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000009896800000000000000000000000000000000000000000000000004563918244f40000000000000000000000000000c2bed267cad1d3fcf2ab8daf21a30b02e502faa0000000000000000000000000000000000000000000000000000000006a0c450e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000eca9bc828a3005b9a3b909f2cc5c2a54794de05f00000000000000000000000037349aeb75a32f8c4c090daff376cf975f5d2eba000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002763200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
              "value": "0x0"
            }
          ]
        }
      ],
      "traceTransfers": true,
      "returnFullTransactions": false
    },
    "latest"
  ]
}

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    {
      "baseFeePerGas": "0x0",
      "calls": [
        {
          "errorData": "0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000018556e69737761705632526f757465723a20455850495245440000000000000000",
          "errorMessage": "REVERT opcode executed: UniswapV2Router: EXPIRED",
          "gasUsed": "0x7af1",
          "logs": [],
          "returnData": "0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000018556e69737761705632526f757465723a20455850495245440000000000000000",
          "status": "0x0",
          "transactionHash": "0x4f5ee8f5b740311bbf95301f9857f5ce6b80b5f3e42ce838e1cc7e123c9a448a",
          "transactionIndex": "0x0"
        }
      ],
      "difficulty": "0x0",
      "extraData": "0x",
      "gasLimit": "0x5f5e100",
      "gasUsed": "0x7af1",
      "hash": "0xa90a24b9a9fd0fee8fa7dcf9a3c92ac92038b19f6641af69f4d7cc37a58f85e1",
      "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
      "miner": "0x0000000000000000000000000000000000000000",
      "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "nonce": "0x0000000000000000",
      "number": "0x40707dc",
      "parentHash": "0x00000000040707dbb227def6922c33285babb9055ebaa2d6fbc5e7b497b636e1",
      "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "sha3Uncles": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "size": "0x0",
      "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "timestamp": "0x6a0c64dc",
      "totalDifficulty": "0x0",
      "transactions": [
        "0x4f5ee8f5b740311bbf95301f9857f5ce6b80b5f3e42ce838e1cc7e123c9a448a"
      ],
      "transactionsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "uncles": []
    }
  ]
}

// simulate TRC20 transfer from
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_simulateV1",
  "params": [
    {
      "blockStateCalls": [
        {
          "calls": [
            {
              "from": "0x41D5AEF88AAFE8B851FD107B9866159840C38CCCB2",
              "to": "0x41ECA9BC828A3005B9A3B909F2CC5C2A54794DE05F",
              "data": "0x23b872dd000000000000000000000000b738198811642bba819521332da1e22c3f0ccc18000000000000000000000000d5aef88aafe8b851fd107b9866159840c38cccb200000000000000000000000000000000000000000000000000000000000f4240",
              "value": "0x0"
            }
          ]
        }
      ],
      "traceTransfers": true,
      "returnFullTransactions": false
    },
    "latest"
  ]
}

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    {
      "baseFeePerGas": "0x0",
      "calls": [
        {
          "contractAddress": "0xeca9bc828a3005b9a3b909f2cc5c2a54794de05f",
          "gasUsed": "0x4f7d",
          "logs": [
            {
              "address": "0xeca9bc828a3005b9a3b909f2cc5c2a54794de05f",
              "blockHash": "0xa7b4bd465b6f582d106970a39637c7cb505adbccb2bf9ac2870505ec5bf1760b",
              "blockNumber": "0x40707eb",
              "blockTimestamp": "0x6a0c6508",
              "data": "0x00000000000000000000000000000000000000000000000000000000000f4240",
              "logIndex": "0x0",
              "removed": false,
              "topics": [
                "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
                "0x000000000000000000000000b738198811642bba819521332da1e22c3f0ccc18",
                "0x000000000000000000000000d5aef88aafe8b851fd107b9866159840c38cccb2"
              ],
              "transactionHash": "0x6138abb8f25e21bf10ddca7b5e37367b65b5851cd19e0d305f7e90499ca671a9",
              "transactionIndex": "0x0"
            }
          ],
          "returnData": "0x0000000000000000000000000000000000000000000000000000000000000001",
          "status": "0x1",
          "transactionHash": "0x6138abb8f25e21bf10ddca7b5e37367b65b5851cd19e0d305f7e90499ca671a9",
          "transactionIndex": "0x0"
        }
      ],
      "difficulty": "0x0",
      "extraData": "0x",
      "gasLimit": "0x5f5e100",
      "gasUsed": "0x4f7d",
      "hash": "0xa7b4bd465b6f582d106970a39637c7cb505adbccb2bf9ac2870505ec5bf1760b",
      "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
      "miner": "0x0000000000000000000000000000000000000000",
      "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "nonce": "0x0000000000000000",
      "number": "0x40707eb",
      "parentHash": "0x00000000040707eafe60e2747598fed79ffb15f4d67938ba648c381307c28ee1",
      "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "sha3Uncles": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "size": "0x0",
      "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "timestamp": "0x6a0c6509",
      "totalDifficulty": "0x0",
      "transactions": [
        "0x6138abb8f25e21bf10ddca7b5e37367b65b5851cd19e0d305f7e90499ca671a9"
      ],
      "transactionsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "uncles": []
    }
  ]
}

// Multiple Ops transfers using Swap Contract (TRX -> WTRX -> USDT)
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_simulateV1",
  "params": [
    {
      "blockStateCalls": [
        {
          "calls": [
            {
              "from": "0x93a8ec1D0698a3873E942A4e3b65A6c20F7310d3",
              "to": "0x81839E7bCcDc7D5f50419bC34209d8ae5969Ef66",
              "data": "0x7ff36ab50000000000000000000000000000000000000000000000000000000006d4ee0f000000000000000000000000000000000000000000000000000000000000008000000000000000000000000093a8ec1d0698a3873e942a4e3b65a6c20f7310d3000000000000000000000000000000000000000000000000000000006a0c79dd0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000fb3b3134f13ccd2c81f4012e53024e8135d58fee000000000000000000000000eca9bc828a3005b9a3b909f2cc5c2a54794de05f",
              "value": "0x5F5E100"
            }
          ]
        }
      ],
      "traceTransfers": true,
      "returnFullTransactions": false
    },
    "latest"
  ]
}

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    {
      "baseFeePerGas": "0x0",
      "calls": [
        {
          "contractAddress": "0x81839e7bccdc7d5f50419bc34209d8ae5969ef66",
          "gasUsed": "0x1848c",
          "logs": [
            {
              "address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
              "blockHash": "0x718c2a920c27f6716f04d73677eeaf31a7faec1cd585b86aa7de8678e8559172",
              "blockNumber": "0x4070e93",
              "blockTimestamp": "0x6a0c7912",
              "data": "0x0000000000000000000000000000000000000000000000000000000005f5e100",
              "logIndex": "0x0",
              "removed": false,
              "topics": [
                "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
                "0x00000000000000000000000093a8ec1d0698a3873e942a4e3b65a6c20f7310d3",
                "0x00000000000000000000000081839e7bccdc7d5f50419bc34209d8ae5969ef66"
              ],
              "transactionHash": "0x344797609db13153fd3c1c23529d7e7b794ddd586d3b6c53dc20fb2343ae3ee4",
              "transactionIndex": "0x0"
            },
            {
              "address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
              "blockHash": "0x718c2a920c27f6716f04d73677eeaf31a7faec1cd585b86aa7de8678e8559172",
              "blockNumber": "0x4070e93",
              "blockTimestamp": "0x6a0c7912",
              "data": "0x0000000000000000000000000000000000000000000000000000000005f5e100",
              "logIndex": "0x1",
              "removed": false,
              "topics": [
                "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
                "0x00000000000000000000000081839e7bccdc7d5f50419bc34209d8ae5969ef66",
                "0x000000000000000000000000fb3b3134f13ccd2c81f4012e53024e8135d58fee"
              ],
              "transactionHash": "0x344797609db13153fd3c1c23529d7e7b794ddd586d3b6c53dc20fb2343ae3ee4",
              "transactionIndex": "0x0"
            },
            {
              "address": "0xfb3b3134f13ccd2c81f4012e53024e8135d58fee",
              "blockHash": "0x718c2a920c27f6716f04d73677eeaf31a7faec1cd585b86aa7de8678e8559172",
              "blockNumber": "0x4070e93",
              "blockTimestamp": "0x6a0c7912",
              "data": "0x0000000000000000000000000000000000000000000000000000000005f5e100",
              "logIndex": "0x2",
              "removed": false,
              "topics": [
                "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c",
                "0x00000000000000000000000081839e7bccdc7d5f50419bc34209d8ae5969ef66"
              ],
              "transactionHash": "0x344797609db13153fd3c1c23529d7e7b794ddd586d3b6c53dc20fb2343ae3ee4",
              "transactionIndex": "0x0"
            },
            {
              "address": "0xfb3b3134f13ccd2c81f4012e53024e8135d58fee",
              "blockHash": "0x718c2a920c27f6716f04d73677eeaf31a7faec1cd585b86aa7de8678e8559172",
              "blockNumber": "0x4070e93",
              "blockTimestamp": "0x6a0c7912",
              "data": "0x0000000000000000000000000000000000000000000000000000000005f5e100",
              "logIndex": "0x3",
              "removed": false,
              "topics": [
                "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
                "0x00000000000000000000000081839e7bccdc7d5f50419bc34209d8ae5969ef66",
                "0x0000000000000000000000006af7a2b30e6e90f6907b440aebe6c7f5cb82f321"
              ],
              "transactionHash": "0x344797609db13153fd3c1c23529d7e7b794ddd586d3b6c53dc20fb2343ae3ee4",
              "transactionIndex": "0x0"
            },
            {
              "address": "0xeca9bc828a3005b9a3b909f2cc5c2a54794de05f",
              "blockHash": "0x718c2a920c27f6716f04d73677eeaf31a7faec1cd585b86aa7de8678e8559172",
              "blockNumber": "0x4070e93",
              "blockTimestamp": "0x6a0c7912",
              "data": "0x0000000000000000000000000000000000000000000000000000000006e68b71",
              "logIndex": "0x4",
              "removed": false,
              "topics": [
                "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
                "0x0000000000000000000000006af7a2b30e6e90f6907b440aebe6c7f5cb82f321",
                "0x00000000000000000000000093a8ec1d0698a3873e942a4e3b65a6c20f7310d3"
              ],
              "transactionHash": "0x344797609db13153fd3c1c23529d7e7b794ddd586d3b6c53dc20fb2343ae3ee4",
              "transactionIndex": "0x0"
            },
            {
              "address": "0x6af7a2b30e6e90f6907b440aebe6c7f5cb82f321",
              "blockHash": "0x718c2a920c27f6716f04d73677eeaf31a7faec1cd585b86aa7de8678e8559172",
              "blockNumber": "0x4070e93",
              "blockTimestamp": "0x6a0c7912",
              "data": "0x000000000000000000000000000000000000000000000000000007476c37f06900000000000000000000000000000000000000000000000000000644c0da4fa5",
              "logIndex": "0x5",
              "removed": false,
              "topics": [
                "0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1"
              ],
              "transactionHash": "0x344797609db13153fd3c1c23529d7e7b794ddd586d3b6c53dc20fb2343ae3ee4",
              "transactionIndex": "0x0"
            },
            {
              "address": "0x6af7a2b30e6e90f6907b440aebe6c7f5cb82f321",
              "blockHash": "0x718c2a920c27f6716f04d73677eeaf31a7faec1cd585b86aa7de8678e8559172",
              "blockNumber": "0x4070e93",
              "blockTimestamp": "0x6a0c7912",
              "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000006e68b710000000000000000000000000000000000000000000000000000000000000000",
              "logIndex": "0x6",
              "removed": false,
              "topics": [
                "0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822",
                "0x00000000000000000000000081839e7bccdc7d5f50419bc34209d8ae5969ef66",
                "0x00000000000000000000000093a8ec1d0698a3873e942a4e3b65a6c20f7310d3"
              ],
              "transactionHash": "0x344797609db13153fd3c1c23529d7e7b794ddd586d3b6c53dc20fb2343ae3ee4",
              "transactionIndex": "0x0"
            }
          ],
          "returnData": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000006e68b71",
          "status": "0x1",
          "transactionHash": "0x344797609db13153fd3c1c23529d7e7b794ddd586d3b6c53dc20fb2343ae3ee4",
          "transactionIndex": "0x0"
        }
      ],
      "difficulty": "0x0",
      "extraData": "0x",
      "gasLimit": "0x5f5e100",
      "gasUsed": "0x1848c",
      "hash": "0x718c2a920c27f6716f04d73677eeaf31a7faec1cd585b86aa7de8678e8559172",
      "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
      "miner": "0x0000000000000000000000000000000000000000",
      "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "nonce": "0x0000000000000000",
      "number": "0x4070e93",
      "parentHash": "0x0000000004070e92faddcad7527771849166dfe418c1fd3ef3dc1bebf3937956",
      "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "sha3Uncles": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "size": "0x0",
      "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "timestamp": "0x6a0c7913",
      "totalDifficulty": "0x0",
      "transactions": [
        "0x344797609db13153fd3c1c23529d7e7b794ddd586d3b6c53dc20fb2343ae3ee4"
      ],
      "transactionsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "uncles": []
    }
  ]
}

// multiple calls in one request, second call reverted, calls depend on state (Wrap 5 TRX to WTRX, try to unwrap 5.000001 TRX (error), try to unwrap 5 TRX (success))
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_simulateV1",
  "params": [
    {
      "blockStateCalls": [
        {
          "calls": [
            {
              "from": "0x93a8ec1D0698a3873E942A4e3b65A6c20F7310d3",
              "to": "0xfb3b3134F13CcD2C81F4012E53024e8135d58FeE",
              "data": "0xd0e30db0",
              "value": "0x4C4B40"
            },
            {
              "from": "0x93a8ec1D0698a3873E942A4e3b65A6c20F7310d3",
              "to": "0xfb3b3134F13CcD2C81F4012E53024e8135d58FeE",
              "data": "0x2e1a7d4d00000000000000000000000000000000000000000000000000000000004C4B41",
              "value": "0x0"
            },
            {
              "from": "0x93a8ec1D0698a3873E942A4e3b65A6c20F7310d3",
              "to": "0xfb3b3134F13CcD2C81F4012E53024e8135d58FeE",
              "data": "0x2e1a7d4d00000000000000000000000000000000000000000000000000000000004C4B40",
              "value": "0x0"
            }
          ]
        }
      ],
      "traceTransfers": true,
      "returnFullTransactions": false
    },
    "latest"
  ]
}

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    {
      "baseFeePerGas": "0x0",
      "calls": [
        {
          "contractAddress": "0xfb3b3134f13ccd2c81f4012e53024e8135d58fee",
          "gasUsed": "0x5538",
          "logs": [
            {
              "address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
              "blockHash": "0x5bfb602ccdeff58b0fbd7efba7329316c19acaf0fa07cb46a388348479712ec5",
              "blockNumber": "0x4070efd",
              "blockTimestamp": "0x6a0c7a4f",
              "data": "0x00000000000000000000000000000000000000000000000000000000004c4b40",
              "logIndex": "0x0",
              "removed": false,
              "topics": [
                "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
                "0x00000000000000000000000093a8ec1d0698a3873e942a4e3b65a6c20f7310d3",
                "0x000000000000000000000000fb3b3134f13ccd2c81f4012e53024e8135d58fee"
              ],
              "transactionHash": "0x851bf70c374fd30c8f8ed82042735b3fe5b8fb1e2a7eb6c2a887b970b08b7cf3",
              "transactionIndex": "0x0"
            },
            {
              "address": "0xfb3b3134f13ccd2c81f4012e53024e8135d58fee",
              "blockHash": "0x5bfb602ccdeff58b0fbd7efba7329316c19acaf0fa07cb46a388348479712ec5",
              "blockNumber": "0x4070efd",
              "blockTimestamp": "0x6a0c7a4f",
              "data": "0x00000000000000000000000000000000000000000000000000000000004c4b40",
              "logIndex": "0x1",
              "removed": false,
              "topics": [
                "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c",
                "0x00000000000000000000000093a8ec1d0698a3873e942a4e3b65a6c20f7310d3"
              ],
              "transactionHash": "0x851bf70c374fd30c8f8ed82042735b3fe5b8fb1e2a7eb6c2a887b970b08b7cf3",
              "transactionIndex": "0x0"
            }
          ],
          "returnData": "0x",
          "status": "0x1",
          "transactionHash": "0x851bf70c374fd30c8f8ed82042735b3fe5b8fb1e2a7eb6c2a887b970b08b7cf3",
          "transactionIndex": "0x0"
        },
        {
          "errorMessage": "REVERT opcode executed",
          "gasUsed": "0x206",
          "logs": [],
          "returnData": "0x",
          "status": "0x0",
          "transactionHash": "0x5bfb602ccdeff58b0fbd7efba7329316c19acaf0fa07cb46a388348479712ec5",
          "transactionIndex": "0x1"
        },
        {
          "contractAddress": "0xfb3b3134f13ccd2c81f4012e53024e8135d58fee",
          "gasUsed": "0x3697",
          "logs": [
            {
              "address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
              "blockHash": "0x5bfb602ccdeff58b0fbd7efba7329316c19acaf0fa07cb46a388348479712ec5",
              "blockNumber": "0x4070efd",
              "blockTimestamp": "0x6a0c7a4f",
              "data": "0x00000000000000000000000000000000000000000000000000000000004c4b40",
              "logIndex": "0x2",
              "removed": false,
              "topics": [
                "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
                "0x000000000000000000000000fb3b3134f13ccd2c81f4012e53024e8135d58fee",
                "0x00000000000000000000000093a8ec1d0698a3873e942a4e3b65a6c20f7310d3"
              ],
              "transactionHash": "0x5f1ddb59ccbb4bae0db5418018e2a17b89f0c9aaba824bf22197278b677fb982",
              "transactionIndex": "0x2"
            },
            {
              "address": "0xfb3b3134f13ccd2c81f4012e53024e8135d58fee",
              "blockHash": "0x5bfb602ccdeff58b0fbd7efba7329316c19acaf0fa07cb46a388348479712ec5",
              "blockNumber": "0x4070efd",
              "blockTimestamp": "0x6a0c7a4f",
              "data": "0x00000000000000000000000000000000000000000000000000000000004c4b40",
              "logIndex": "0x3",
              "removed": false,
              "topics": [
                "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65",
                "0x00000000000000000000000093a8ec1d0698a3873e942a4e3b65a6c20f7310d3"
              ],
              "transactionHash": "0x5f1ddb59ccbb4bae0db5418018e2a17b89f0c9aaba824bf22197278b677fb982",
              "transactionIndex": "0x2"
            }
          ],
          "returnData": "0x",
          "status": "0x1",
          "transactionHash": "0x5f1ddb59ccbb4bae0db5418018e2a17b89f0c9aaba824bf22197278b677fb982",
          "transactionIndex": "0x2"
        }
      ],
      "difficulty": "0x0",
      "extraData": "0x",
      "gasLimit": "0x5f5e100",
      "gasUsed": "0x8dd5",
      "hash": "0x5bfb602ccdeff58b0fbd7efba7329316c19acaf0fa07cb46a388348479712ec5",
      "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
      "miner": "0x0000000000000000000000000000000000000000",
      "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "nonce": "0x0000000000000000",
      "number": "0x4070efd",
      "parentHash": "0x0000000004070efce9ab496dd918acc8efb66ef8fec03290ff59510b8286dc91",
      "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "sha3Uncles": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "size": "0x0",
      "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "timestamp": "0x6a0c7a51",
      "totalDifficulty": "0x0",
      "transactions": [
        "0x851bf70c374fd30c8f8ed82042735b3fe5b8fb1e2a7eb6c2a887b970b08b7cf3",
        "0x5bfb602ccdeff58b0fbd7efba7329316c19acaf0fa07cb46a388348479712ec5",
        "0x5f1ddb59ccbb4bae0db5418018e2a17b89f0c9aaba824bf22197278b677fb982"
      ],
      "transactionsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "uncles": []
    }
  ]
}

@APshenkin APshenkin changed the title implement eth_simulateV1 JSON-RPC method for trading-flow case feat(jsonrpc): implement eth_simulateV1 JSON-RPC method for trading-flow case May 19, 2026
@APshenkin APshenkin changed the title feat(jsonrpc): implement eth_simulateV1 JSON-RPC method for trading-flow case feat(jsonrpc): implement base eth_simulateV1 JSON-RPC method May 19, 2026
@bladehan1 bladehan1 dismissed a stale review May 22, 2026 09:24

Re-submitting as COMMENT (AI never APPROVE/REQUEST_CHANGES; reviewer judgment left to humans).

TransactionContext ctx;
try {
ctx = executeOneConstantInternal(trxCap, headBlockCapsule, perCallChild, tracer);
} catch (RuntimeException e) {
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.

[SHOULD] RuntimeException rethrow aborts the entire batch — diverges from per-call isolation

If any single call in simulateConstantContracts throws a RuntimeException (NPE in VM, downstream lookup failure, etc.), the catch rethrows and TronJsonRpcImpl.ethSimulateV1 translates that to a top-level JsonRpcInternalException — the client loses results for ALL 32 calls in the batch, including the ones that already succeeded with perCallChild.commit(). ETH eth_simulateV1 semantics expect per-call isolation: failed call returns status=0x0 + errorMessage, subsequent calls keep running.

Suggestion: Catch the RuntimeException, build a synthetic failed ProgramResult with setException(e), add it to outcomes, and continue to the next call. If fail-fast is intentional (e.g. sharedRoot may be corrupted after such a fault), say so in javadoc and document the deviation from ETH spec.

private final Deque<List<Entry>> stack = new ArrayDeque<>();
private long seq;

public BufferingSimulationTracer() {
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.

[SHOULD] Per-call entry buffer is unbounded — quantify or cap

A single call can produce thousands of LOG opcodes (only energy-bounded) plus transfer entries; each goes into the root frame's ArrayList<Entry> with no upper bound. With MAX_SIMULATE_CALLS_PER_BLOCK=32, a malicious or pathological request can buffer hundreds of MB worth of Entry objects (per-Entry object header + topics + 32-byte data + byte arrays). The 32 cap was justified in the PR description as ~10 KB/call typical, but worst case is much higher.

Suggestion: Either add a stress test that pins the realistic upper bound (and document it), or introduce a per-call entry-count cap (e.g. 4096 or 100_000) that aborts the call with a clear errorMessage when exceeded.


public static final class SimulateCallOutcome {

private final ProgramResult result;
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.

[SHOULD] Move SimulateCallOutcome / SimulateOutcome out of Wallet.java into jsonrpc/types/

Wallet.java is already 4500+ lines and is a load-bearing god class. The new public static final class SimulateCallOutcome and SimulateOutcome are pure data carriers shaped exactly for the jsonrpc layer (they wrap ProgramResult + BufferingSimulationTracer.Entry) and have no semantic dependency on Wallet's instance state. Embedding them here further inflates the class and forces every consumer (TronJsonRpcImpl, future actuators, tests) to import Wallet.SimulateOutcome — a layering smell.

Suggestion: Promote both to top-level classes under framework/src/main/java/org/tron/core/services/jsonrpc/types/ (e.g. SimulateOutcome.java, SimulateCallOutcome.java); simulateConstantContracts returns the new top-level type. Wallet shrinks; jsonrpc DTO layer grows in the natural direction.

.setOriginAddress(ByteString.copyFrom(owner))
.setBytecode(ByteString.copyFrom(data))
.setCallValue(value)
.setConsumeUserResourcePercent(100)
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.

[SHOULD] Deduplicate CREATE-path magic numbers — extract a shared buildEvmCreateSmartContract helper

In buildSimulateTransactionCapsule, the CREATE branch hand-builds a CreateSmartContract with setConsumeUserResourcePercent(100) and setOriginEnergyLimit(1) — identical to Wallet.triggerConstantContract (around line 3084-3098 in HEAD) which does the same construction for triggerConstantContract deploy. These two magic constants encode Tron's EVM CREATE convention (also asserted at Program.java:851/867); duplicating them at three sites means a future tweak (say, supporting non-zero origin energy under a fork rule) must touch every copy.

Note: the TriggerSmartContract branch of buildSimulateTransactionCapsule already correctly reuses triggerCallContract + wallet.createTransactionCapsule; the asymmetry between branches is itself a smell.

Suggestion: Extract a Wallet#buildEvmCreateSmartContract(byte[] ownerAddress, byte[] code, long value) (or static on a SmartContractBuilders util) that sets the 100/1 convention in one place; have both buildSimulateTransactionCapsule and triggerConstantContract call it.

return wallet.createTransactionCapsule(trigger, ContractType.TriggerSmartContract);
}

private SimulateBlockResult buildSimulateBlockResult(Wallet.SimulateOutcome outcome,
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.

[SHOULD] Lift the 9 "no value" constants in BlockResult to inline field defaults

Anchored here on buildSimulateBlockResult because this PR-added builder constructs a SimulateBlockResult (extending BlockResult) and inherits the same "9 constant field assignments per construction" pattern.

In BlockResult.java lines 98-115, every construction sets nonce, sha3Uncles, logsBloom, receiptsRoot, difficulty, totalDifficulty, extraData, uncles, and baseFeePerGas to the same constant "no value" string — while mixHash at line 85 already follows the inline-default pattern (private String mixHash = ByteArray.toJsonHex(new byte[32]);).

Suggestion: In BlockResult.java, promote the 9 assignments to inline field initializers (matching the existing mixHash pattern at line 85), then delete the now-redundant assignments in the BlockResult(Block, boolean, Wallet) ctor. Both the existing call site and the new simulate-block construction get the defaults for free, and future schema drift becomes one-line.

}

/** Reset per-call state. Drops any leftover frames and starts a fresh root. */
public void beginCall() {
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.

[SHOULD] beginCall and dropCall have identical bodies; constructor calls beginCall then caller calls it again

Both beginCall and dropCall are stack.clear(); stack.push(new ArrayList<>()); — same code, different intent. Two methods with the same body invite drift: if dropCall should ever do extra accounting (e.g. release pooled buffers, record metrics) the next contributor will only patch one. Separately, the constructor calls beginCall() and then Wallet.simulateConstantContracts calls tracer.beginCall() again on iter 0; both invocations are idempotent but the second is a no-op that masks the lifecycle.

Suggestion: Keep both method names for semantic clarity but have one delegate to the other (e.g. dropCall() { beginCall(); }). Drop the constructor's implicit beginCall() and let the caller drive lifecycle.

System.currentTimeMillis());
}

private static byte[] leftPad32(byte[] src) {
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.

[SHOULD] leftPad32 + longToBytes reinvent DataWord

leftPad32(longToBytes(value)) is exactly new DataWord(value).getData(); the existing DataWord already does both steps and is used elsewhere in this file. Two private helpers that overlap with project infrastructure add maintenance surface for no win — and worse, the next person reading this won't know whether the duplicate exists for a subtle reason (endianness, padding convention) or just because the author didn't notice DataWord.

Suggestion: Replace leftPad32(longToBytes(amount)) and leftPad32(longToBytes(tokenId)) with new DataWord(value).getData(); delete leftPad32 and longToBytes.

@JsonInclude(JsonInclude.Include.NON_NULL)
@Getter
@Setter
private String errorCode;
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.

[NIT] errorCode field is declared but never assigned

private String errorCode; is declared with @JsonInclude(NON_NULL) and exposed via getter/setter, but nothing in the codebase calls the setter — the field is always omitted in the JSON output. Either dead code, or a forgotten plumbing step.

Suggestion: Either populate it (map EVM revert / exception types to a stable error-code enum) and add a test pinning the mapping, or delete the field until there's a concrete need.

long headNum = head.getNum();
byte[] headHash = head.getBlockId().getBytes();
byte[] simBlockHash = Hash.sha3(
(SIMULATE_BLOCK_HASH_PREFIX + ByteArray.toHexString(headHash) + ":1").getBytes());
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.

[NIT] String.getBytes() uses platform default charset

Lines 1013 and 1046 use (SIMULATE_BLOCK_HASH_PREFIX + ByteArray.toHexString(headHash) + ":1").getBytes() with no charset argument. The content is ASCII so it works today, but the same class at line 908 already uses StandardCharsets.UTF_8 for the TRC10 topic — be consistent, and avoid any platform-default surprise that could perturb the synthetic-hash determinism contract.

Suggestion: Pass StandardCharsets.UTF_8 to both .getBytes(...) calls.

private long callPenaltyEnergy;
@Getter
@Setter
private SimulationTracer simulationTracer;
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.

[NIT] Documentation & test backlog — 5 rolled-up items

Grouped as one comment since all are doc-or-test asks (no production-code change). Anchor placed on Program.simulationTracer because the contract this field encodes underpins items 3 and 4.

  1. simulationTracer consensus-path javadocProgram.java:152 (this line). The field's invariant "MUST be null on consensus execution" is not enforced anywhere; setter is public. Add a javadoc spelling out the read-only-simulation-only contract; consider tightening setter visibility or adding Args.getInstance().isSupportConstant() guard.

  2. transferAllTokenWithTrace byte-equivalence comment + testProgram.java around line 541. The if (simulationTracer == null) { MUtil.transferAllToken(...); return; } early-return is the consensus path; any future hoisting (logger, Args read, getAccount call) above the null check silently breaks sync-from-genesis. Add an invariant comment and a SELFDESTRUCT-with-multi-TRC10 equivalence test (pre/post state byte-equal to the pre-refactor path).

  3. DELEGATECALL / CALLCODE skip integration testProgram.java around lines 1169 / 1188 / 1803 / 1822 (4 hook sites). The msg.getOpCode() != Op.DELEGATECALL && msg.getOpCode() != Op.CALLCODE guard prevents synthetic Transfer logs but has no test coverage; if a future cleanup drops any single guard, clients see a ghost transfer log and rebuild incorrect ledgers with no CI signal. Add an integration test: A delegatecalls B and B does address.transfer(value); assert traceTransfers=true returns NO ERC-7528 Transfer entry.

  4. TRC10Transfer topic[0] lock-down (javadoc + test)TronJsonRpcImpl.java around line 1071. TRC10_TRANSFER_TOPIC_HEX is derived from the literal signature string and is a Tron-private extension; clients will hard-code this hex. Add a javadoc "Tron private extension; signature is a stable client contract" and a unit test asserting the exact hex value of the topic so any future signature edit fails CI.

  5. validation ETH-spec divergence javadocSimulateV1Args.java around line 29. ETH spec validation=true enables signature + nonce + gas-pricing checks; here it's only "sender activated + balance >= callValue". Clients porting code from geth will hit silently different behavior. Add a javadoc listing exactly which checks run and noting the divergence.

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.

Add Support for eth_simulateV1 Method in Tron JSON-RPC

2 participants