Skip to content

MIP-16: JSON-RPC Query Methods (Review) - #89

Draft
typedarray wants to merge 14 commits into
mainfrom
typedarray/mip-16-review
Draft

typedarray wants to merge 14 commits into
mainfrom
typedarray/mip-16-review

Conversation

@typedarray

Copy link
Copy Markdown
Contributor

Continued from #88, this PR moves the MIP from Draft to Review and includes further revisions.

@DanielVF

DanielVF commented Sep 23, 2026 •

Copy link
Copy Markdown

@typedarray Maybe it should be explicit about how eth_queryTransfers handles being in a reverted call set.

For example:

a -> b
        b->c (transfers 10 eth)
        b->d (reverts)

Would seem to have no way to know from the contents of eth_queryTransfers that the transfer did not in fact occur - it would look like the call was successful.

@kyscott18

kyscott18 commented Sep 23, 2026 •

Copy link
Copy Markdown

@DanielVF Yes, it's important to highlight there are two key revert questions for a trace:

  • Did the call trace have an error that caused it to revert (including child call traces with errors that propogated)
  • Was the result of the call trace eventually reverted because of an error in a parent trace (note that this is not as simple as checking transaction.status)



I think reverted for a trace and transfer should refer the latter.







It's also an open question whether or not to filter out reverted transfers (writing this out convinced me that, yes, it should be filtered out).

@DanielVF

Copy link
Copy Markdown

@kyscott18 so when the status field is set to reverted then the call was reverted, either from itself, or because something else in the tree reverted? That's good.

Might need a clearer description that that is what status means in the spec then.

And yes, I think it makes sense for ETH transfers to act like logs and not show things that ended up not happening.

@kyscott18

kyscott18 commented Sep 24, 2026 •

Copy link
Copy Markdown

I did some more research into trace error fields for geth vs parity.

Fields

geth callTracer parity trace_*
Frame failed error is present error is present
Decoded reason revertReason (Error(string) and Panic(uint256) only) none
Revert data output (absent if empty) result.output ("0x" if empty)
Rolled back by an ancestor no field no field

Observed values (Ethereum mainnet)

Case geth parity (Erigon 3.6.1)
Revert with reason error: "execution reverted", revertReason: "ERC20: transfer amount exceeds balance", output: "0x08c379a0…" error: "Reverted", result.output: "0x08c379a0…"
Panic error: "execution reverted", revertReason: "arithmetic underflow or overflow", output: "0x4e487b71…11" error: "Reverted", result.output: "0x4e487b71…11"
Custom error error: "execution reverted", no revertReason, output: "0x22611167" error: "Reverted", result.output: "0x22611167"
Empty revert error: "execution reverted", no output error: "Reverted", result.output: "0x"
Invalid opcode error: "invalid opcode: INVALID", no output error: "invalid opcode: INVALID", result: null
Out of gas (18 frames in scan) error: "out of gas"
Insufficient balance error: "insufficient balance for transfer" error: "insufficient balance for transfer", result: null
Transfer rolled back in a successful tx no error on the frame; ancestor 0.1.4 has error no error; ancestor 0.1.3 has error

trace_filter

This means that trace_filter has a logical gap because it's impossible to tell whether the result of a trace was eventually reverted by a parent trace.

What does this mean for eth_query

I think we should add a new trace field to communicate whether the result of a trace was eventually reverted by a parent trace and therefore did not contribute any state diffs. In the current proposal, I called that field status. That name is unclear and confusing because it actually means something slightly different than transaction.status.

persisted might be a more applicable name. There should also be a filter added to traces and transfers that by default filters out persisted: false results.

Comment thread MIPs/MIP-16.md Outdated
@DanielVF

Copy link
Copy Markdown

I like the clarity with having reverted and error on traces / transfers, and removing status. includeReverted is good too.

typedarray and others added 7 commits September 28, 2026 16:00
Remove `status` and `revertReason` from trace and transfer objects and
add a boolean `reverted` field. Remove the `isTopLevel` filter and add
an `includeReverted` filter to `eth_queryTraces` and `eth_queryTransfers`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@QEDK
QEDK force-pushed the typedarray/mip-16-review branch from 27c431f to d8ab22d Compare September 28, 2026 12:00
@DZakh

DZakh commented Oct 1, 2026

Copy link
Copy Markdown

Hey, I'm from Envio. We run HyperSync, which indexes 90+ EVM chains and traces for some of them, including Monad. Some notes after mapping the spec to our data:

1. Transfers include DELEGATECALL

"Any call frame whose value is greater than zero" matches DELEGATECALL frames. They carry the parent's value, but nothing moves.

  • Monad (100 blocks): 354 of 1,399 frames with value > 0 are DELEGATECALL (25%)
  • Ethereum (30 blocks): 447 of 9,641 (4.6%)

Transfers should be CALL, CREATE and CREATE2 with value > 0, plus SELFDESTRUCT with a non-zero balance. CALLCODE should be excluded too, it sends value to the caller itself.

2. SELFDESTRUCT has no input, gas or gasUsed

These are Required for all traces. Should be Type-dependent. For SELFDESTRUCT, to is the beneficiary and value is the balance.

3. Precompile calls

callTracer includes them, parity-style trace_block doesn't. This changes traceAddress, so the same call has a different address depending on the provider. The spec should pick one. Most archives skip them, HyperSync too.

4. Missing fields

  • blocks: mixHash, requestsHash
  • transactions: authorizationList (EIP-7702)

5. Always return join keys

You can request blocks without blockNumber on logs, and then you can't match them. The server reads these keys anyway, so it should return them.

@DanielVF

DanielVF commented Oct 6, 2026

Copy link
Copy Markdown

On Monad, staking is a precompile, and it would fairly useful to have it in both Traces and in Transfers.

kyscott18 and others added 6 commits October 7, 2026 16:37
* separate monad fields into appendix

* add totalDifficulty to Monad blocks appendix

* note totalDifficulty is always 0x0 on Monad

* address MIP-16 review comments

Simplify the normative-fields sentences, rename the appendix to Appendix 1, move the transactions logs sentence below the table, remove the mainnet timestamp, and move the error/reverted explanation into a Rationale entry.

---------

Co-authored-by: Kevin <90073088+typedarray@users.noreply.github.com>
@DZakh

DZakh commented Oct 8, 2026

Copy link
Copy Markdown

By the way, we are very close to releasing the first implementation of the standard at ENVIO. I'm already excited to share it with you 😉

It's an amazing job, and big thanks for making this standard. I believe it'll drive the whole industry forward. 🙏

…n, traces scope

- Traces and transfers include system transactions and precompile calls
- Empty filter arrays and null filter values place no constraint
- Reject null entries in filter arrays (except top-level topics positions)
- Empty topic position array is a wildcard; drop trailing-null sentence
- Require primary key in fields; reject empty fields arrays
- Reject unknown request and filter keys with -32602
aramalipoor added a commit to blockchain-data-standards/manifesto that referenced this pull request Oct 8, 2026
Makes the BDS eth_query* wire contract conform to Monad MIP-16 (draft,
monad-crypto/MIPs#89): protos, JSON-RPC request parsing, JSON-RPC
response rendering, error codes and the trace/receipt conversion helpers.

Breaking, as MIP-16 requires:
- Requests: limit -> target (field 4); cursor removed (reserved 5); the
  next page is fromBlock = cursorBlock.number +/- 1. Only fromBlock,
  toBlock, order, target, filter and fields are accepted.
- Filters: isTopLevel -> includeReverted; eth_queryBlocks gains miner.
- Fields: exact selection, primary key required, "all", per-chain schema
  (Monad 143/10143 = MIP-16 Appendix 1); unknown keys/names -> -32602.
- Schemas: transactions gain status, cumulativeGasUsed, contractAddress,
  logsBloom, authorizationList and rename gasLimit -> gas; traces and
  transfers use the MIP-16 trace schema; transfers exclude DELEGATECALL
  and CALLCODE. Blocks gain requestsHash, lose transactionCount.
- Go API: Query*RequestFromJsonRpc(chainID, params),
  Query*ResponseToJsonRpc(chainID, req, resp),
  TraceFromGethDebug(item, transactionIndex, ...).
- New: QueryErrorCode, ValidateQueryRange, QuerySchemaForChain,
  PropagateParityReverted, MergeReceipt.

The root main.go is package manifesto (it was package main without a
func main, so go build ./... failed).

Verified: all 236 mip-16-spec fixtures parse and render exactly (the
inverted-range ones are checked by servers via ValidateQueryRange);
go build/vet/test and cargo build pass; two independent reviews.
aramalipoor added a commit to erpc/erpc that referenced this pull request Oct 9, 2026
…SON-RPC and gRPC

JSON-RPC and gRPC eth_query* now share one executor that follows Monad
MIP-16 (draft, monad-crypto/MIPs#89) on the BDS manifesto MIP-16 contract
(manifesto#23):

- Requests: target instead of limit, no cursor (the next page is
  fromBlock = cursorBlock.number +/- 1), MIP-16 defaults per order, tags
  resolved once, desc bounds never swapped, strict -32602 validation.
- Native QueryService upstreams are tried first, the request piped
  unchanged; a JSON call takes one page. The upstream's grpc.headers now
  reach QueryService streams (they never did).
- Proto shim otherwise: block-aligned pages ending at toBlock, target or
  the page budget (queryShim.maxBlocksPerPage / maxPageDuration; -32005
  when fromBlock cannot complete); real fromBlock/toBlock/cursorBlock
  references; relations, receipts and ordering per MIP-16; traces from
  trace_block or debug_traceBlockByNumber; includeReverted.
- Chain consistency: every object is checked against its block header and
  headers must link by parentHash; an inconsistent tail is re-scanned once
  without cache, else the page ends at the last consistent block. A page
  pins its sub-requests to one upstream; a lagging upstream's null or an
  error moves the pin to an upstream that has the data.
- Errors: -32602, -32001 (availability), -32004 (no upstream serves the
  method), -32005; gRPC INVALID_ARGUMENT / OUT_OF_RANGE / UNIMPLEMENTED /
  RESOURCE_EXHAUSTED.

Breaking: the old eth_query* contract (limit, cursor, isTopLevel, extra
identity fields) is gone; queryShim drops concurrency, maxBlockRange,
maxLimit and defaultLimit (strict config parsing rejects them) and gains
maxBlocksPerPage and maxPageDuration. The old map-based JSON shim in
architecture/evm and the gRPC client's JSON eth_query* path are removed.

Verified: the mip-16-spec conformance suite passes 236/236 against a local
erpc with a Monad archive shim upstream; native path smoke-tested against
prism; go test ./erpc ./clients ./common ./architecture/evm pass.
aramalipoor added a commit to erpc/erpc that referenced this pull request Oct 9, 2026
…SON-RPC and gRPC

JSON-RPC and gRPC eth_query* now share one executor that follows Monad
MIP-16 (draft, monad-crypto/MIPs#89) on the BDS manifesto MIP-16 contract
(manifesto#23):

- Requests: target instead of limit, no cursor (the next page is
  fromBlock = cursorBlock.number +/- 1), MIP-16 defaults per order, tags
  resolved once, desc bounds never swapped, strict -32602 validation.
- Native QueryService upstreams are tried first, the request piped
  unchanged; a JSON call takes one page. The upstream's grpc.headers now
  reach QueryService streams (they never did).
- Proto shim otherwise: block-aligned pages ending at toBlock, target or
  the page budget (queryShim.maxBlocksPerPage / maxPageDuration; -32005
  when fromBlock cannot complete); real fromBlock/toBlock/cursorBlock
  references; relations, receipts and ordering per MIP-16; traces from
  trace_block or debug_traceBlockByNumber; includeReverted.
- Chain consistency: every object is checked against its block header and
  headers must link by parentHash; an inconsistent tail is re-scanned once
  without cache, else the page ends at the last consistent block. A page
  pins its sub-requests to one upstream; a lagging upstream's null or an
  error moves the pin to an upstream that has the data.
- Errors: -32602, -32001 (availability), -32004 (no upstream serves the
  method), -32005; gRPC INVALID_ARGUMENT / OUT_OF_RANGE / UNIMPLEMENTED /
  RESOURCE_EXHAUSTED.

Breaking: the old eth_query* contract (limit, cursor, isTopLevel, extra
identity fields) is gone; queryShim drops concurrency, maxBlockRange,
maxLimit and defaultLimit (strict config parsing rejects them) and gains
maxBlocksPerPage and maxPageDuration. The old map-based JSON shim in
architecture/evm and the gRPC client's JSON eth_query* path are removed.

Verified: the mip-16-spec conformance suite passes 236/236 against a local
erpc with a Monad archive shim upstream; native path smoke-tested against
prism; go test ./erpc ./clients ./common ./architecture/evm pass.
aramalipoor added a commit to erpc/erpc that referenced this pull request Oct 9, 2026
…SON-RPC and gRPC

JSON-RPC and gRPC eth_query* now share one executor that follows Monad
MIP-16 (draft, monad-crypto/MIPs#89) on the BDS manifesto MIP-16 contract
(manifesto#23):

- Requests: target instead of limit, no cursor (the next page is
  fromBlock = cursorBlock.number +/- 1), MIP-16 defaults per order, tags
  resolved once, desc bounds never swapped, strict -32602 validation.
- Native QueryService upstreams are tried first, the request piped
  unchanged; a JSON call takes one page. The upstream's grpc.headers now
  reach QueryService streams (they never did).
- Proto shim otherwise: block-aligned pages ending at toBlock, target or
  the page budget (queryShim.maxBlocksPerPage / maxPageDuration; -32005
  when fromBlock cannot complete); real fromBlock/toBlock/cursorBlock
  references; relations, receipts and ordering per MIP-16; traces from
  trace_block or debug_traceBlockByNumber; includeReverted.
- Chain consistency: every object is checked against its block header and
  headers must link by parentHash; an inconsistent tail is re-scanned once
  without cache, else the page ends at the last consistent block. A page
  pins its sub-requests to one upstream; a lagging upstream's null or an
  error moves the pin to an upstream that has the data.
- Errors: -32602, -32001 (availability), -32004 (no upstream serves the
  method), -32005; gRPC INVALID_ARGUMENT / OUT_OF_RANGE / UNIMPLEMENTED /
  RESOURCE_EXHAUSTED.

Breaking: the old eth_query* contract (limit, cursor, isTopLevel, extra
identity fields) is gone; queryShim drops concurrency, maxBlockRange,
maxLimit and defaultLimit (strict config parsing rejects them) and gains
maxBlocksPerPage and maxPageDuration. The old map-based JSON shim in
architecture/evm and the gRPC client's JSON eth_query* path are removed.

Verified: the mip-16-spec conformance suite passes 236/236 against a local
erpc with a Monad archive shim upstream; native path smoke-tested against
prism; go test ./erpc ./clients ./common ./architecture/evm pass.
aramalipoor added a commit to erpc/erpc that referenced this pull request Oct 9, 2026
…ON-RPC and gRPC

JSON-RPC and gRPC eth_query* now share one executor that follows Monad
MIP-16 (draft, monad-crypto/MIPs#89) on the BDS manifesto MIP-16 contract
(manifesto#23):

- Requests: target instead of limit, no cursor (the next page is
  fromBlock = cursorBlock.number +/- 1), MIP-16 defaults per order, tags
  resolved once, desc bounds never swapped, strict -32602 validation.
- Native QueryService upstreams are tried first, the request piped
  unchanged; a JSON call takes one page. The upstream's grpc.headers now
  reach QueryService streams (they never did).
- Proto shim otherwise: block-aligned pages ending at toBlock, target or
  the page budget (queryShim.maxBlocksPerPage / maxPageDuration; -32005
  when fromBlock cannot complete); real fromBlock/toBlock/cursorBlock
  references; relations, receipts and ordering per MIP-16; traces from
  trace_block or debug_traceBlockByNumber; includeReverted.
- Chain consistency: every object is checked against its block header and
  headers must link by parentHash; an inconsistent tail is re-scanned once
  without cache, else the page ends at the last consistent block. A page
  pins its sub-requests to one upstream; a lagging upstream's null or an
  error moves the pin to an upstream that has the data.
- Errors: -32602, -32001 (availability), -32004 (no upstream serves the
  method), -32005; gRPC INVALID_ARGUMENT / OUT_OF_RANGE / UNIMPLEMENTED /
  RESOURCE_EXHAUSTED.

The old eth_query* contract (limit, cursor, isTopLevel, extra identity
fields) is replaced; it had no users. queryShim drops concurrency,
maxBlockRange, maxLimit and defaultLimit and gains maxBlocksPerPage and
maxPageDuration. The old map-based JSON shim in architecture/evm and the
gRPC client's JSON eth_query* path are removed.

Verified: the mip-16-spec conformance suite passes 236/236 against a local
erpc with a Monad archive shim upstream; native path smoke-tested against
prism; go test ./erpc ./clients ./common ./architecture/evm pass.
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.

5 participants