rpcdaemon: add eth_getHeaderByHash and eth_getHeaderByNumber - #23717
Merged
Merged
Conversation
MysticRyuujin
force-pushed
the
eth-getheader-methods
branch
from
September 9, 2026 15:56
63cc618 to
d274611
Compare
MysticRyuujin
marked this pull request as ready for review
September 9, 2026 16:01
MysticRyuujin
requested review from
AskAlexSharov,
lupin012 and
yperbasis
as code owners
September 9, 2026 16:01
AskAlexSharov
requested review from
taratorio
and
a balanced review from Copilot
September 12, 2026 03:14
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Unresolvable safe and finalized tags lack regression coverage, and a method comment is misplaced.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds standard Ethereum header lookup RPC methods with specification-aligned null and field behavior.
Changes:
- Adds
eth_getHeaderByNumberandeth_getHeaderByHash. - Marshals fork-gated header fields without block-only
size. - Adds endpoint tests, documentation, and slow-log exclusions.
File summaries
| File | Description |
|---|---|
rpc/rpccfg/rpccfg.go |
Adds header methods to the slow-log blacklist. |
rpc/jsonrpc/eth_block.go |
Implements both header endpoints. |
rpc/jsonrpc/eth_block_test.go |
Tests header lookup and response fields. |
rpc/jsonrpc/eth_api.go |
Exposes methods through EthAPI. |
rpc/ethapi/api.go |
Removes header size output. |
cmd/rpcdaemon/README.md |
Documents endpoint availability. |
Review details
- Files reviewed: 6/6 changed files
- Comments generated: 2
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
eth_getHeaderByNumber and eth_getHeaderByHash opened a raw BeginTemporalRo and let headerBy* select the overlay afterwards. A publish/commit/unpublish cycle landing between the two steps leaves the head block in neither layer, and the unknown-block branch then reports it as null. Use BeginTemporalRoWithOverlay like every sibling endpoint, and add both methods to the overlay-race and prune-gating tables.
GetHeaderByNumber turns an unresolvable fork-choice marker into a null result through rpchelper.UnknownBlockCode. The test module configures both markers, so only the resolvable path was exercised.
AskAlexSharov
approved these changes
Sep 12, 2026
AskAlexSharov
approved these changes
Sep 12, 2026
AskAlexSharov
enabled auto-merge
September 12, 2026 04:08
github-merge-queue
Bot
removed this pull request from the merge queue due to a conflict with the base branch
Sep 14, 2026
Sahil-4555
pushed a commit
to Sahil-4555/erigon
that referenced
this pull request
Sep 14, 2026
…ch#23943) `RPCMarshalHeader` and `RPCMarshalBlock` built a `map[string]any`. That boxes every value into an interface, hashes the map 19 times per header, and then makes `encoding/json` sort the keys and walk the reflect path per entry. `RPCMarshalBlock` now returns `*RPCBlock`. The header builder became the internal `rpcMarshalHeader`, which returns `*RPCHeader` and takes the hash; nothing exported returns `*RPCHeader`, so erigontech#23717 needs to export it when it rebases onto this. `RPCMarshalBlock` and `RPCMarshalBlockEx` also took an `additional map[string]any` that was merged into the result. Every caller passed `nil` or a freshly made map that nothing ever wrote to, so the parameter and the two wrappers are gone; `rpc/ethapi/internal.go` is deleted. ## The JSON is unchanged `TestRPCMarshalHeaderMatchesLegacyJSON` and `TestRPCMarshalBlockMatchesLegacyJSON` keep the old map builders as an oracle and compare the encoded objects field by field. The header sweep covers all 256 combinations of the optional fields, times the three shapes of `extraData` and of the Gnosis AuRa seal (nil, empty, set), times zero and non-zero values — 4608 headers. The block sweep covers withdrawals nil / empty / set, uncles, and each `inclTx`/`fullTx` pair. The empty-but-not-nil cases are the ones that matter. A plain `omitempty` on `withdrawals` drops `"withdrawals":[]`, which is what most post-Shanghai blocks carry, and the same trap applies to `auraSeal`. Both fields use a pointer so the omission tracks nil exactly as the map did. Both traps were confirmed by making them and watching the oracle fail. Key order does change: a map encodes alphabetically, a struct in declaration order. JSON objects are unordered, so no client should care. ## Numbers n5, go1.27.1, `-cpu=1 -count=15`, benchstat over the oracle vs the new type in the same binary: | case | map | struct | delta | map allocs | struct allocs | |---|---|---|---|---|---| | header, london | 13.6µs | 4.4µs | -67.9% | 74 | 4 | | header, prague | 16.6µs | 5.3µs | -68.3% | 89 | 4 | | block, 0 txs | 22.4µs | 9.5µs | -57.5% | 114 | 17 | | block, 1 tx | 26.6µs | 13.4µs | -49.7% | 123 | 26 | | block, 200 txs | 605µs | 585µs | -3.3% | 1715 | 1618 | A 200-tx block is dominated by the transactions, so the header saving is most of a percent there; the header endpoints erigontech#23717 adds get the full win. `transactions` is typed `any` rather than `[]any` on purpose. Under go1.27 a `[]any` struct field costs about two allocations per element where the same slice reached through an `any` costs one, so the typed field would have made large blocks worse. A properly typed transaction slice is worth a follow-up: the same microbenchmark drops from 204 allocations to 2.
Contributor
Author
|
Do ya'll need me to fix the conflicts? |
main now returns typed structs from the block and header marshalers. Export RPCMarshalHeader and return *ethapi.RPCHeader from eth_getHeaderByNumber and eth_getHeaderByHash. RPCHeader has no size, totalDifficulty or transactions fields, so the tests no longer check for those keys.
Collaborator
|
No need, I merged main and resolved the conflicts. main now returns typed structs from the block and header marshalers (#23943), so |
Sahil-4555
pushed a commit
to Sahil-4555/erigon
that referenced
this pull request
Sep 18, 2026
…ontech#24094) QA integration tests for `eth_getHeaderByNumber` and `eth_getHeaderByHash`, the two APIs added in erigontech#23717. The fixtures live in erigontech/rpc-tests#603; this PR only pins `RPC_VERSION` to that branch so the QA workflows pick them up. ### `eth_getHeaderByNumber` | test | param | case | |---|---|---| | 01 | `0x41b57c` | pre-London: no `baseFeePerGas`, no post-Merge fields | | 02 | `0xC65D40` | London: `baseFeePerGas` set, `difficulty` still non-zero | | 03 | `0x1036640` | post-Merge pre-Shanghai: `difficulty` `0x0`, no `withdrawalsRoot` | | 04 | `0x112A880` | Shanghai: `withdrawalsRoot` set, no blob fields | | 05 | `0x12C135B` | Cancun: `blobGasUsed`, `excessBlobGas`, `parentBeaconBlockRoot` | | 06 | `0x1600000` | Prague: `requestsHash` | | 07 | `earliest` | resolves to the genesis header | | 08 | `latest` | head header (`metadata.latest`, runs only with `-L`) | | 09 | `pending` | `null`, the pending block is not exposed | | 10 | `0x7FFFFFFFFFFFFF` | unknown number: `null`, not an error | | 11 | `abc` | invalid number: hex without `0x` prefix, error | ### `eth_getHeaderByHash` | test | case | |---|---| | 01 | pre-London: no `baseFeePerGas`, no post-Merge fields | | 02 | London: `baseFeePerGas` set, `difficulty` still non-zero | | 03 | post-Merge pre-Shanghai: `difficulty` `0x0`, no `withdrawalsRoot` | | 04 | Shanghai: `withdrawalsRoot` set, no blob fields | | 05 | Cancun: `blobGasUsed`, `excessBlobGas`, `parentBeaconBlockRoot` | | 06 | Prague: `requestsHash` | | 07 | genesis block hash | | 08 | unknown hash: `null`, not an error | | 09 | invalid hash: wrong length, error | | 10 | invalid hash: not a string, error | The same blocks are used by number and by hash, so the two APIs are compared on identical headers. ### `Integration-Tests ` Compared eth_getHeaderByNumber() and eth_getHeaderByHash() for historical block automatically on QA - RPC Integration Tests / mainnet-rpc-integ-tests Compared eth_getHeaderByNumber() output with geth on the latest block — results match. Dispatched manually: CI run.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
eth_getHeaderByHashandeth_getHeaderByNumber, implementing the spec proposed in ethereum/execution-apis#877 (ethereum/execution-apis#874). geth, Nethermind, and reth already serve these methods; Erigon has them only in theerigon_namespace.The new methods do not reuse the
erigon_getHeaderBy*path because itstypes.HeaderJSON marshaling emits pre-fork fields as literalnulland returns errors for unknown blocks. They marshal throughethapi.RPCMarshalHeader, which fork-gates optional fields, and returnnullfor an unknown block, for thependingtag, and for an unresolvablesafeorfinalizedtag.RPCMarshalHeaderno longer setssize. Its only caller overwrites the value with the block size, and the header methods must not emit it: the spec excludessizebecause no client computes a header size and the block getters already report the block size.