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:
- 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).
- Resolve the block via
MorphProofStateProviderFactory::state_provider(block) (window + canonical checks already live there).
- 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.
- 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.
- Record the existing
morph.rpc.proofs metrics (or a method label) for this endpoint as well.
- 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.
Background
Upstream reth added
eth_getMultiProofin 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 independenteth_getProofcalls.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, plusMultiProof::account_proof+into_eip1186_response). Historicaleth_getProofis already overridden onto the Morph proofs-history window (EthProofApiOverride/MorphProofStateProviderFactory);getMultiProofis not.Base / OP historical RPC is still
eth_getProofonly. 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 defaulteth_getMultiProofwould also bypass proofs-history and hit reth's in-memory--rpc.eth-proof-windowoverlay (default 0), so historical multi-account proofs would still be wrong unless we override the method ourselves.Proposed work
Add
eth_getMultiProofon the existing historical-proof RPC override, with the same window / canonical-hash / prune-bounds behaviour aseth_getProof.Suggested shape (match reth #26555):
Implementation sketch:
EthProofApiOverridewithgetMultiProof. Keep wiring throughEthProofApiExt::into_rpc()so the override still replaces theethmethods (same path asgetProofin node add-ons).MorphProofStateProviderFactory::state_provider(block)(window + canonical checks already live there).MultiProofTargets(keccak256 of each address / slot), callstate.multiproof(...), thenmultiproof.account_proof(address, &slots)per original target so the response order matches the request.MAX_PROOF_KEYS = 1024is per single-accounteth_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.morph.rpc.proofsmetrics (or a method label) for this endpoint as well.stateRoot; out-of-window / pruned / non-canonical should fail the same way aseth_getProof. Do not mix this with reference-index coverage.Out of scope
debug_executionWitness/debug_executePayload.Acceptance
eth_getMultiProofis served on the authenticated/publicethnamespace through the Morph historical-proof overlay (not the default overlay window).stateRoot.eth_getProof.