fix(prover): implement eth_getBlockNumber verified request handler#9203
fix(prover): implement eth_getBlockNumber verified request handler#9203TechFusionData wants to merge 5 commits intoChainSafe:unstablefrom
Conversation
Resolves ChainSafe#5796. The prover was forwarding eth_getBlockNumber to the execution layer instead of serving it locally from the PayloadStore, which defeats the purpose of the verified light client proxy. Changes: - Add eth_getBlockNumber handler in verified_requests/ that reads the latest block number from proofProvider.getExecutionPayload('latest') and returns it as a hex string - Register the handler in verifiableMethodHandlers in utils/process.ts - Add unit tests for happy path and verification-failed error path Note: Implementation assisted with AI (Claude).
There was a problem hiding this comment.
Code Review
This pull request introduces a new verified request handler for retrieving the latest block number. The implementation includes the handler logic, its registration in the processing utility, and corresponding unit tests. The primary feedback across all files is that the method name used, eth_getBlockNumber, should be renamed to eth_blockNumber to comply with the standard Ethereum JSON-RPC specification. This change is necessary to ensure that incoming standard-compliant requests are correctly routed to and processed by this handler.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7ab28268cc
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| eth_getTransactionCount: eth_getTransactionCount, | ||
| eth_getBlockByHash: eth_getBlockByHash, | ||
| eth_getBlockByNumber: eth_getBlockByNumber, | ||
| eth_getBlockNumber: eth_getBlockNumber, |
There was a problem hiding this comment.
Register verified handler under eth_blockNumber
processAndVerifyRequest routes verification by exact RPC method name from verifiableMethodHandlers, but this entry is keyed as eth_getBlockNumber instead of the standard eth_blockNumber. In practice, clients call eth_blockNumber, so those requests will still be classified as non-verifiable and forwarded to EL, meaning this change does not actually implement verified block-number handling (and keeps the original trust gap).
Useful? React with 👍 / 👎.
The standard Ethereum JSON-RPC method is eth_blockNumber, not eth_getBlockNumber. The incorrect key in verifiableMethodHandlers caused standard-compliant requests to bypass the verified handler entirely. Changes: - Rename eth_getBlockNumber.ts -> eth_blockNumber.ts - Rename eth_getBlockNumber.test.ts -> eth_blockNumber.test.ts - Rename exported handler constant to eth_blockNumber - Update registration key in verifiableMethodHandlers - Update error message string to eth_blockNumber - Update test: method field, import, and expected error message Addresses bot review feedback on ChainSafe#9203.
| export const eth_blockNumber: ELVerifiedRequestHandler<[], string> = async ({payload, logger, proofProvider}) => { | ||
| try { | ||
| const executionPayload = await proofProvider.getExecutionPayload("latest"); | ||
| const blockNumber = numberToHex(executionPayload.blockNumber); | ||
|
|
||
| return getResponseForRequest(payload, blockNumber); |
There was a problem hiding this comment.
The verification process is missing in these statements, which is main motivation of the verified_requests in prover.
User request for eth_bl;ockNumber meant to fetch latest block from the EL nodes. So we should fetch that first and then verify against our CL light-client.
Fetch block number from EL node and verify against CL light client before returning, matching the pattern used by other verified_requests handlers. Add eth_blockNumber to ELApi type map.
|
Thanks for the review — you're right, the handler was only reading from the CL without fetching from the EL first. Fixed in the latest commit: now fetches |
| export const eth_blockNumber: ELVerifiedRequestHandler<[], string> = async ({payload, rpc, logger, proofProvider}) => { | ||
| try { | ||
| const elResponse = await rpc.request("eth_blockNumber", [], {raiseError: false}); | ||
|
|
||
| if (!isValidResponse(elResponse)) { | ||
| throw new Error("Invalid response from EL for eth_blockNumber"); | ||
| } | ||
|
|
||
| const elBlockNumber = parseInt(elResponse.result, 16); | ||
| const executionPayload = await proofProvider.getExecutionPayload("latest"); | ||
| const clBlockNumber = executionPayload.blockNumber; | ||
|
|
||
| if (elBlockNumber < clBlockNumber) { | ||
| throw new Error( | ||
| `EL block number (${elBlockNumber}) is behind CL block number (${clBlockNumber})` | ||
| ); | ||
| } | ||
|
|
||
| return getResponseForRequest(payload, numberToHex(elBlockNumber)); |
There was a problem hiding this comment.
Looking at this whole logic holistically, I feel it's not right.
el < clmeans the EL is stale/inconsistent relative to your trusted CL-derived execution payloadel >= clmeans at least the EL is not behind the trusted lower bound
But there’s still a conceptual issue:
- if you return
elBlockNumberwhenel > cl, that returned value is not actually CL-verified - it is only “not contradicted by CL”
So looking from the user perspective what he is expecting a verified latest block number and that can only mean a CL execution payload block number.
So we can do it with either 2 ways:
- Do not call EL at all and only return what CL verified view is
- Add drift window e.g. 2 blocks, so if EL is maximum 2 blocks forward then it's ok otherwise we consider that CL is too much behind to even consider it a verified latest block number.
The second approach seems more reasonable to me.
… fix/prover-eth-getBlockNumber-5796
Previous logic returned EL's block number when EL >= CL, but that value was not actually CL-verified — only 'not contradicted by CL'. Now we return the CL block number directly and use the EL response only as a sanity check that CL isn't too stale (drift window of 2 blocks).
|
Good point — you're right that returning EL's block when EL > CL means it's only "not contradicted" by CL, not actually verified. Updated to your second approach: drift window of 2 blocks. The handler now uses the EL response only as a staleness check (rejects if |EL − CL| > 2) and returns the CL-verified block number itself. Tests cover the four edge cases (within window, equal, EL behind CL, CL too stale). |
Closes #5796. The prover was forwarding eth_getBlockNumber to the execution layer instead of serving it locally from the PayloadStore, which defeats the purpose of the verified light client proxy.
Changes:
Note: Implementation assisted with AI (Claude).
Motivation
The prover was forwarding
eth_getBlockNumberto the execution layer instead of serving it locally, defeating the purpose of the verified light client proxy.Implements a verified handler for
eth_getBlockNumberfollowing the same pattern aseth_getBlockByNumber. Reads the block number fromproofProvider.getExecutionPayload('latest')and returns it as a hex string. Includes unit tests for both happy path and verification failure.Closes #issue_number
AI Assistance Disclosure
Used Claude (Anthropic) to assist with implementation and writing unit tests. The approach, logic, and code were reviewed and understood by me before submission.