Skip to content

Wire eth_subscribe(newHeads) for Autobahn (CON-257)#3419

Open
wen-coding wants to merge 4 commits into
mainfrom
wen/add_eth_subscribe_newhead_for_autobahn
Open

Wire eth_subscribe(newHeads) for Autobahn (CON-257)#3419
wen-coding wants to merge 4 commits into
mainfrom
wen/add_eth_subscribe_newhead_for_autobahn

Conversation

@wen-coding
Copy link
Copy Markdown
Contributor

@wen-coding wen-coding commented May 11, 2026

Summary

  • Under Autobahn, eth_subscribe("newHeads") is currently broken: the legacy fan-out subscribes to the Tendermint event bus on tm.event = 'NewBlockHeader', but Autobahn drives app.FinalizeBlock from giga_router rather than CometBFT consensus, so PublishEventNewBlockHeader never fires.
  • This PR adds a direct in-process notifier from giga_router's post-commit point into evmrpc.SubscriptionAPI's fan-out goroutine, scoped strictly to Autobahn mode (notifier is only constructed when tmConfig.AutobahnConfigFile != ""). The legacy CometBFT path is untouched.
  • Notifier semantics: single-producer, single-consumer, capacity 1, overwrite-on-full — newHeads subscribers care about the latest head, not a backlog, so stale heads are dropped in favor of new ones.

Design notes

A simpler alternative was to keep using the event bus and just have giga_router call eventBus.PublishEventNewBlockHeader(...) after commit. That would have been a ~20-line diff with zero evmrpc changes (the existing consumer would just work). We opted not to because (1) the pubsub layer isn't doing anything load-bearing for this use case — one fixed query, one subscriber — and (2) we plan to drop the cosmos event bus entirely once external consumers move off tm.event='Tx' and friends. A direct channel is the end-state design; this PR lands it now to avoid carrying the stopgap.

hash on the published header is the autobahn lane-block header hash (the same value the EVM receipt store records as blockHash), not a hash over the synthesized Tendermint header. parentHash / receiptsRoot / transactionsRoot are zero under Autobahn — the lane-block path doesn't compute a Tendermint-style hash chain, and surfacing fakes would be worse than zeros. Subscribers that chain-validate the head stream will need a different mechanism going forward; this is called out in the encoder's doc.

Test plan

  • gofmt -s -l clean on all modified files; go build ./... clean.
  • go test ./evmrpc/ — 249 PASS, 8 SKIP, 0 FAIL (22s). Includes:
    • existing TestSubscribeNewHeads (legacy event-bus path) — unchanged behaviour.
    • new TestSubscribeNewHeadsAutobahn (notifier path, in-process WS server end-to-end).
    • new unit tests for BlockHeaderNotifier (deliver, overwrite-on-full, nil-safe) and encodeCommittedBlock (happy path + nil ConsensusParamUpdates).
  • evm_rpc_tests.sh (HTTP-method fixtures) against a real non-Autobahn cluster — 160/160 pass, no regressions on the request/response RPC surface.
  • New integration_test/evm_module/ws_test/ — consensus-mode-agnostic, gated on SEI_EVM_WS_RUN_INTEGRATION=1, wired into evm_rpc_tests.sh. Run end-to-end against both cluster modes:
    • Non-Autobahn cluster: PASS (number=0x2b, hash 0xe0dd089b...)
    • Autobahn cluster: PASS (number=0xf5, hash 0xc4d1bc59..., GigaRouter initialized confirmed in all 4 node logs)
  • Reviewer: confirm non-app-hash-breaking label is correct — this PR only adds a notification side-effect after commit; no state machine or app-hash-relevant logic changes.

🤖 Generated with Claude Code


Note

Medium Risk
Introduces a new post-commit notification hook in the Autobahn execution path and changes how eth_subscribe("newHeads") is sourced under Autobahn, so concurrency/backpressure or payload mismatches could affect WebSocket subscribers (legacy CometBFT path remains unchanged).

Overview
Fixes eth_subscribe("newHeads") under Autobahn by bypassing the Tendermint event bus and instead pushing committed-block header data through a new in-process BlockHeaderNotifier (overwrite-on-full, capacity 1).

Wires this notifier from Autobahn’s giga_router post-Commit hook into app.App (implements types.BlockHeaderListener) and into the EVM WS server’s SubscriptionAPI, which now selects between the notifier path (Autobahn) and the existing event-bus subscription (legacy).

Adds Autobahn-specific newHeads encoding (encodeCommittedBlock, with explicit Autobahn hash and zeroed parentHash/roots), plus unit + end-to-end WS tests and a new integration test package invoked by evm_rpc_tests.sh.

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

Under Autobahn, app.FinalizeBlock is driven by giga_router rather than
CometBFT consensus, so the legacy Tendermint event-bus subscription
that backs eth_subscribe("newHeads") never fires. This adds a direct
in-process notifier from giga_router's commit path into evmrpc's
SubscriptionAPI fan-out (overwrite-on-full, capacity 1, so the latest
head always wins if a consumer lags). The legacy path is untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 11, 2026

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

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedMay 12, 2026, 8:20 AM

@wen-coding wen-coding changed the title Wire eth_subscribe(newHeads) for Autobahn via in-process notifier Wire eth_subscribe(newHeads) for Autobahn (CON-257) May 11, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 11, 2026

Codecov Report

❌ Patch coverage is 70.68966% with 34 lines in your changes missing coverage. Please review.
✅ Project coverage is 59.26%. Comparing base (09d0d60) to head (714479a).

Files with missing lines Patch % Lines
evmrpc/subscribe.go 80.00% 9 Missing and 6 partials ⚠️
sei-tendermint/internal/p2p/giga_router.go 0.00% 8 Missing and 1 partial ⚠️
app/app.go 0.00% 6 Missing and 1 partial ⚠️
evmrpc/notifier.go 88.88% 2 Missing ⚠️
sei-tendermint/node/setup.go 0.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main    #3419   +/-   ##
=======================================
  Coverage   59.26%   59.26%           
=======================================
  Files        2110     2111    +1     
  Lines      174242   174252   +10     
=======================================
+ Hits       103259   103270   +11     
+ Misses      62053    62051    -2     
- Partials     8930     8931    +1     
Flag Coverage Δ
sei-chain-pr 68.50% <70.68%> (?)
sei-db 70.41% <ø> (ø)

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

Files with missing lines Coverage Δ
evmrpc/server.go 88.46% <100.00%> (ø)
sei-tendermint/node/node.go 65.28% <100.00%> (+0.09%) ⬆️
sei-tendermint/node/public.go 67.44% <100.00%> (+3.33%) ⬆️
sei-tendermint/node/seed.go 48.48% <ø> (ø)
sei-tendermint/node/setup.go 69.35% <0.00%> (-0.23%) ⬇️
evmrpc/notifier.go 88.88% <88.88%> (ø)
app/app.go 69.41% <0.00%> (+0.18%) ⬆️
sei-tendermint/internal/p2p/giga_router.go 66.02% <0.00%> (-2.98%) ⬇️
evmrpc/subscribe.go 68.18% <80.00%> (+6.32%) ⬆️

... and 35 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.

Comment thread evmrpc/subscribe.go
wen-coding and others added 2 commits May 11, 2026 13:29
encodeCommittedBlock was mirroring the legacy encodeTmHeader, which
emits both the typo'd "withdrawlsRoot" and the correctly-spelled
"withdrawalsRoot". Since the Autobahn encoder is new code there is no
back-compat reason to carry the typo; legacy encodeTmHeader is left
untouched.

Reported by Cursor Bugbot on #3419.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Tighten hash field doc in blockHeaderEvent: receipt store records
  zero blockHash on disk and evmrpc overlays the autobahn hash at read
  time. The prior wording implied the receipt store stored it directly.
- Clarify single-producer assumption in OnBlockCommitted and document
  multi-producer behaviour (still safe, latest-wins is acceptable).
- Move SubscriptionManager construction inside the legacy event-bus
  branch in NewSubscriptionAPI so the field is nil under Autobahn
  instead of dead state.
- Document the non-nil header/response contract on BlockHeaderListener,
  and add a defensive guard in runNewHeadsFromNotifier so a single
  malformed event does not kill the fan-out goroutine for all
  subscribers.
- Annotate the listener call site in giga_router.executeBlock so the
  fire-after-Commit-before-mempool-update ordering is explicit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@cursor cursor Bot left a comment

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 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 714479a. Configure here.

Comment thread evmrpc/subscribe.go
var gasLimit uint64
if cp := evt.response.ConsensusParamUpdates; cp != nil && cp.Block != nil {
gasLimit = uint64(cp.Block.MaxGas) //nolint:gosec
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

gasLimit always zero because ConsensusParamUpdates is nil most blocks

High Severity

encodeCommittedBlock sources gasLimit from evt.response.ConsensusParamUpdates, but as block.go lines 481–489 explicitly document, this field is only populated when the app proposes a consensus-param update — which is nil for the vast majority of blocks. The result is gasLimit will be 0 in nearly every eth_subscribe("newHeads") notification under Autobahn. The ctx from ctxProvider is already available in runNewHeadsFromNotifier and its ConsensusParams() method gives the correct active gas limit, matching the approach in block.go.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 714479a. Configure here.

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