Skip to content

feat(evmonly): add eth_getBlockByNumber and eth_getBlockByHash to the EVM-only executor - #4208

Merged
shemnon merged 4 commits into
giga-1from
shemnon/giga-evmonly-block-by
Sep 16, 2026
Merged

shemnon merged 4 commits into
giga-1from
shemnon/giga-evmonly-block-by

Conversation

@shemnon

@shemnon shemnon commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Describe your changes and provide context

Adds eth_getBlockByNumber and eth_getBlockByHash to the Autobahn EVM-only executor's RPC server, on top of eth_getTransactionByHash (#4205). This unblocks forge script --broadcast, which unconditionally calls eth_getBlockByNumber("latest", false) to instantiate its forked backend — even with --skip-simulation and explicit gas flags, it currently hard-fails with the method eth_getBlockByNumber does not exist.

  • new blockAPI (giga/evmonly/rpc/block.go) with GetBlockByNumber/GetBlockByHash, reusing the existing Backend.Block (by height) and a newly added Backend.BlockByHash — the latter delegates straight to Environment.BlockByHash, which was already Autobahn-aware and already returns null for an unknown hash rather than an error
  • unlike the state-reading methods (GetBalance/GetTransactionCount/Call), this does not reuse requireCurrentState's "current only" restriction: block and receipt data is retained per-height indefinitely (proven by GetTransactionReceipt already serving arbitrary past heights), unlike account state, which has no historical view yet. latest/safe/finalized/pending resolve to the current committed block; any other explicit height is served directly; a future height or (in this fork) earliest (rpc.EarliestBlockNumber == 0, an unused height) both fall through to null
  • hash-only transaction listing decodes and re-hashes each tx exactly like GetTransactionByHash does — deliberately does not use Block.GetTxHashes(), which returns a Tendermint-internal hash rather than the Ethereum keccak256 hash every other endpoint here keys by; a dedicated test cross-checks the two paths produce identical hashes
  • gasUsed for a block comes from the last transaction's receipt CumulativeGasUsed, not a per-tx sum
  • new EvmGasLimit plumbed through Proxy/Environment/evmOnlyApplication, mirroring the existing EvmCall/EvmChainConfig optional-interface pattern; verified the app's gas limit never changes after InitChain, so using the "current" value is exact for any historical block, not an approximation
  • parentHash/stateRoot/transactionsRoot/receiptsRoot/miner/logsBloom are documented, confirmed gaps (verified against the real gigaRouterCommon block-translation path, not assumed) rather than silently wrong values
  • rebased onto giga-1's current tip after feat(evmonly): add eth_getTransactionByHash RPC to the EVM-only executor #4205 merged; also aligned block.go's base fee handling with the real EvmBaseFee() accessor feat(evmonly): add eth_getTransactionByHash RPC to the EVM-only executor #4205 gained during its own review (was a hardcoded zero when this branch was written against the pre-review version)
  • documents both methods and a cast block usage example in the Autobahn README

Testing performed to validate your change

  • go build ./...
  • go test -count=1 ./giga/evmonly/... ./sei-tendermint/internal/proxy/... ./sei-tendermint/internal/evmonlyapp/... ./sei-tendermint/internal/rpc/core/... ./sei-tendermint/node/...
  • TestEncodeBlockHashOnlyListMatchesGetTransactionByHash cross-checks the hash-only list against GetTransactionByHash for the same transactions
  • TestEncodeBlockDocumentedHeaderGaps pins which header fields are real vs. always-zero on this execution path
  • Coverage for both number tags and explicit/historical/future heights, known vs. unknown hash, full-tx vs. hash-only, empty and multi-tx blocks
  • golangci-lint run v2.13.2 scoped to all touched packages: 0 issues
  • golangci-lint fmt --diff: clean

🤖 Generated with Claude Code

Add the two block-lookup RPCs to the Autobahn EVM-only executor's JSON-RPC
server, matching go-ethereum's response shape while sourcing data from the
already-existing Backend.Block/BlockByHash and receipt store.

- New giga/evmonly/rpc/block.go: blockAPI.GetBlockByNumber/GetBlockByHash.
  latest/safe/finalized/pending resolve to the current committed block;
  any other explicit height (including a past one) is served directly,
  since block/receipt data is retained indefinitely unlike this
  application's current-only state view; a height outside the committed
  range, or "earliest" (this go-ethereum fork's literal height 0, which
  this executor never commits), resolves to null.
- gasUsed is read from the last transaction's receipt CumulativeGasUsed
  rather than summed per transaction, avoiding an all-receipts fetch.
  Hash-only transaction lists are the Ethereum keccak256 hash of each
  decoded transaction, not Block.GetTxHashes()'s Tendermint-internal hash.
- parentHash/stateRoot/transactionsRoot/receiptsRoot/miner/logsBloom stay
  zero-valued: Autobahn's translation from its internal block into this
  RPC's coretypes.ResultBlock shape never populates the header fields they
  would read from (confirmed by a scratch test against the real
  translation, not assumed).
- Extend Backend with BlockByHash and EvmGasLimit; thread EvmGasLimit
  through proxy.Proxy (optional-interface pattern, matching EvmChainConfig)
  and evmOnlyApplication, which never changes the gas limit after
  InitChain, so the current value is also correct for any past block.
- Update integration_test/autobahn/README.md's RPC surface list and add a
  cast block usage example.
…rebase

giga-1's eth_getTransactionByHash gained a real EvmBaseFee() accessor during
review (replacing a hardcoded zero) after this branch's eth_call/tx work was
written. Align eth_getBlockByNumber/eth_getBlockByHash's full-tx encoding and
baseFeePerGas field the same way, and fix the test fixture the rebase
otherwise leaves with a nil EvmBaseFee panic.
@cursor

cursor Bot commented Sep 16, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
New public RPC surface with nuanced semantics (historical blocks, lane-scoped gasUsed, partial headers); changes are well-tested but clients may misinterpret empty header fields or gas totals.

Overview
Adds eth_getBlockByNumber and eth_getBlockByHash to the Autobahn EVM-only JSON-RPC server via a new blockAPI, unblocking tooling (e.g. forge script --broadcast) that requires a latest block.

Block lookups use the existing height-based Backend.Block and a new Backend.BlockByHash. Unlike balance/nonce/eth_call, explicit historical heights are allowed when the block is still retained; future, pruned, or invalid heights and unknown hashes return null without error. Responses map Tendermint blocks to Ethereum-shaped JSON: transaction lists use keccak hashes (or full RPCTransaction objects when fullTx is true), gasUsed comes from the last tx receipt’s cumulative gas (single-lane scope), gasLimit/baseFeePerGas from new EvmGasLimit plumbing on proxy, RPC core, and evmOnlyApplication. Several header fields stay documented zero/empty gaps (logsBloom not aggregated). Shared decodeBlockTx is extracted for block and tx RPC paths. Autobahn README documents the new methods and cast block.

Reviewed by Cursor Bugbot for commit ee8de7b. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions

github-actions Bot commented Sep 16, 2026

Copy link
Copy Markdown

The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedSep 17, 2026, 5:12 AM

@codecov

codecov Bot commented Sep 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.74576% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.56%. Comparing base (7e3ca24) to head (ee8de7b).
⚠️ Report is 17 commits behind head on giga-1.

Files with missing lines Patch % Lines
giga/evmonly/rpc/block.go 87.00% 13 Missing ⚠️
giga/evmonly/rpc/tx.go 71.42% 2 Missing ⚠️
giga/evmonly/rpc/server.go 50.00% 1 Missing ⚠️
sei-tendermint/internal/evmonlyapp/app.go 66.66% 1 Missing ⚠️
sei-tendermint/internal/rpc/core/mempool.go 0.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##           giga-1    #4208   +/-   ##
=======================================
  Coverage   65.55%   65.56%           
=======================================
  Files        2081     2083    +2     
  Lines      157460   157387   -73     
=======================================
- Hits       103222   103187   -35     
+ Misses      54097    54059   -38     
  Partials      141      141           
Flag Coverage Δ
sei-chain-pr 55.85% <84.74%> (?)
sei-db ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
sei-tendermint/internal/proxy/proxy.go 91.04% <100.00%> (-0.07%) ⬇️
giga/evmonly/rpc/server.go 23.25% <50.00%> (-18.85%) ⬇️
sei-tendermint/internal/evmonlyapp/app.go 81.56% <66.66%> (-0.22%) ⬇️
sei-tendermint/internal/rpc/core/mempool.go 48.40% <0.00%> (-3.01%) ⬇️
giga/evmonly/rpc/tx.go 84.69% <71.42%> (ø)
giga/evmonly/rpc/block.go 87.00% <87.00%> (ø)

... and 5 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 0fc7e53. Configure here.

Comment thread giga/evmonly/rpc/block.go

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adds eth_getBlockByNumber/eth_getBlockByHash to the EVM-only executor with well-scoped plumbing (Backend.BlockByHash, EvmGasLimit through Proxy/Environment/app) and good test coverage, including a cross-check that block tx hashes match eth_getTransactionByHash. No blockers; three non-blocking issues: an unhandled pruned-height error path that contradicts the "retained indefinitely" claim, a mixHash that disagrees with the executor's actual PREVRANDAO, and an O(N) receipt read in the fullTx path.

Findings: 0 blocking | 3 non-blocking | 3 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • None at the file/PR level.
  • 3 suggestion(s)/nit(s) flagged inline on specific lines.

Comment thread giga/evmonly/rpc/block.go Outdated
height = &h
}
block, err := api.backend.Block(ctx, &coretypes.RequestBlockInfo{Height: height})
if errors.Is(err, coretypes.ErrHeightExceedsChainHead) || errors.Is(err, coretypes.ErrZeroOrNegativeHeight) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] ErrHeightNotAvailable is not handled here, so a height below the retention watermark surfaces as a JSON-RPC error rather than null. Under Autobahn env.Block routes to gigaRouterCommon.BlockByNumber, which maps atypes.ErrPruned to coretypes.WrapErrHeightNotAvailable(...) (sei-tendermint/internal/p2p/giga_router_common.go:146); env.getHeight can also emit it via the height < base branch. This also makes the surrounding claims inaccurate: the godoc above ("block data is retained indefinitely, so every other explicit height ... is looked up directly") and the README ("block and receipt data is retained indefinitely, so any past height works the same as latest", integration_test/autobahn/README.md:503) both promise something the block store does not guarantee once pruning kicks in. Suggest adding coretypes.ErrHeightNotAvailable to the null-mapping set (matching the spirit of evmrpc's blockByNumberOrNullForJSONRPC) and softening the two doc claims to "retained for the node's retention window".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed, this aligns with EIP-4444 behaviro where non-retained blocks return null instead of an error

Comment thread giga/evmonly/rpc/block.go
"hash": blockHash,
"parentHash": common.BytesToHash(block.Block.LastBlockID.Hash),
"nonce": ethtypes.BlockNonce{}, // inapplicable to Sei
"mixHash": common.Hash{}, // inapplicable to Sei

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] mixHash is hardcoded to zero and labelled "inapplicable to Sei", but on this execution path it is applicable: evmOnlyApplication executes every block with PrevRandao: evmOnlyPrevRandao(timestamp) (sei-tendermint/internal/evmonlyapp/app.go:436), which buildBlockContext feeds to vm.BlockContext.Random (giga/evmonly/executor.go:368), so PREVRANDAO inside a contract returns keccak256(be64(timestamp)). A forked backend (forge script --broadcast, the use case this PR targets) reads mixHash from the header and will simulate PREVRANDAO with a different value than the node executes. Since the PR deliberately enumerates and pins the real header gaps, this one is worth returning correctly: derive it from the block timestamp the same way, and pin it in TestEncodeBlockDocumentedHeaderGaps (or a sibling test) against the executor's derivation rather than duplicating the constant. If it is intentionally left zero, the // inapplicable to Sei comment should say why instead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current code aligns with v2 performance. No changes will be made in this PR.

Comment thread giga/evmonly/rpc/block.go
if err != nil {
return nil, err
}
stored, err := api.receiptFor(ctx, ethtx.Hash())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] The fullTx branch performs one receipt-store read per transaction, but the only consumers are replaceFrom (a documented edge case for legacy shapes where signature recovery yields the zero address) and lastReceipt. For a load-test-sized Autobahn block this turns eth_getBlockByNumber(latest, true) into thousands of sequential store lookups. It also contradicts the rationale stated at line 156 and in the encodeBlock godoc, which justifies omitting logsBloom and summed gasUsed precisely by "a receipt per transaction" being too expensive — a cost this branch already pays. Consider reading the receipt lazily, only when result.From == (common.Address{}), plus the one read for the last transaction, so both branches cost a single lookup in the common case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Punting, we don't have an efective bulk receipt query method. Will pursue later,

Godocs and test comments here read as design narration (which convention this
matches, why an internal function was picked, what the underlying Autobahn
translation does and doesn't populate). Cut to what each function returns and
what a reader needs to know; the git history and this PR's description carry
the rest.

@masih masih left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shemnon is on fire 🔥

@wen-coding wen-coding left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, seidroid inline comments might be worth addressing

- resolveBlockByNumber now maps ErrHeightNotAvailable to null, matching
  the null treatment already given to future/zero heights
- fix stale encodeBlock/README claims that block data is retained
  indefinitely and that parentHash/stateRoot/etc are always zero
- note gasUsed/cumulativeGasUsed is scoped to one Autobahn lane, not
  every lane executing concurrently; revisit with superblocks
- defer the per-tx receipt read in the fullTx path pending a bulk
  receipt-load API

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@shemnon
shemnon added this pull request to the merge queue Sep 16, 2026
Merged via the queue into giga-1 with commit 9e57774 Sep 16, 2026
90 of 94 checks passed
@shemnon
shemnon deleted the shemnon/giga-evmonly-block-by branch September 16, 2026 20:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants