Skip to content

feat(rpc): add eth_getMultiProof on the historical-proof RPC override #169

Description

@panos-xyz

Background

Upstream reth added eth_getMultiProof in paradigmxyz/reth#26555 (landed 2026-08-03, first released in reth v2.5.0). The method takes a batch of (address, storage keys[]) targets, computes one consolidated trie multiproof, and returns EIP-1186 account proofs in request order. For historical state that is cheaper than N independent eth_getProof calls.

Morph is still pinned to reth v2.4.0, so the default Eth API does not expose this method. The trie/provider stack we already have does support it (StateProofProvider::multiproof / overlay multiproof, plus MultiProof::account_proof + into_eip1186_response). Historical eth_getProof is already overridden onto the Morph proofs-history window (EthProofApiOverride / MorphProofStateProviderFactory); getMultiProof is not.

Base / OP historical RPC is still eth_getProof only. This is a Morph-side RPC we can ship on 2.4 without waiting for the next reth bump.

Problem

Callers that need proofs for several accounts at one retained canonical block (bridges, indexers, light-client style verification) must issue sequential eth_getProofs. Each request re-walks the same historical trie. After Morph eventually bumps to reth ≥ 2.5, the default eth_getMultiProof would also bypass proofs-history and hit reth's in-memory --rpc.eth-proof-window overlay (default 0), so historical multi-account proofs would still be wrong unless we override the method ourselves.

Proposed work

Add eth_getMultiProof on the existing historical-proof RPC override, with the same window / canonical-hash / prune-bounds behaviour as eth_getProof.

Suggested shape (match reth #26555):

eth_getMultiProof(targets: Vec<(Address, Vec<B256>)>, block: Option<BlockId>)
  -> Vec<EIP1186AccountProofResponse>

Implementation sketch:

  1. Extend EthProofApiOverride with getMultiProof. Keep wiring through EthProofApiExt::into_rpc() so the override still replaces the eth methods (same path as getProof in node add-ons).
  2. Resolve the block via MorphProofStateProviderFactory::state_provider(block) (window + canonical checks already live there).
  3. Build MultiProofTargets (keccak256 of each address / slot), call state.multiproof(...), then multiproof.account_proof(address, &slots) per original target so the response order matches the request.
  4. Apply a combined storage-key cap (today MAX_PROOF_KEYS = 1024 is per single-account eth_getProof). Decide whether the same 1024 applies to the sum of all targets, and reject empty / duplicate-address requests explicitly if we do not want silent merge.
  5. Record the existing morph.rpc.proofs metrics (or a method label) for this endpoint as well.
  6. E2E in the proof-history suite: two+ accounts at a retained historical block, verify against that block's stateRoot; out-of-window / pruned / non-canonical should fail the same way as eth_getProof. Do not mix this with reference-index coverage.

Out of scope

  • Bumping reth to v2.5.0.
  • Changing proofs-history storage or ExEx catch-up.
  • debug_executionWitness / debug_executePayload.

Acceptance

  • eth_getMultiProof is served on the authenticated/public eth namespace through the Morph historical-proof overlay (not the default overlay window).
  • A multi-account proof at a retained historical block verifies against that block's stateRoot.
  • Requests outside the proofs-history window fail consistently with eth_getProof.
  • Combined key-limit and empty-target behaviour is documented in the RPC error.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions