Wire eth_subscribe(newHeads) for Autobahn (CON-257)#3419
Conversation
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>
|
The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ 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
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
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>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ 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.
| var gasLimit uint64 | ||
| if cp := evt.response.ConsensusParamUpdates; cp != nil && cp.Block != nil { | ||
| gasLimit = uint64(cp.Block.MaxGas) //nolint:gosec | ||
| } |
There was a problem hiding this comment.
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)
Reviewed by Cursor Bugbot for commit 714479a. Configure here.


Summary
eth_subscribe("newHeads")is currently broken: the legacy fan-out subscribes to the Tendermint event bus ontm.event = 'NewBlockHeader', but Autobahn drivesapp.FinalizeBlockfromgiga_routerrather than CometBFT consensus, soPublishEventNewBlockHeadernever fires.giga_router's post-commit point intoevmrpc.SubscriptionAPI's fan-out goroutine, scoped strictly to Autobahn mode (notifier is only constructed whentmConfig.AutobahnConfigFile != ""). The legacy CometBFT path is untouched.newHeadssubscribers 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_routercalleventBus.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 offtm.event='Tx'and friends. A direct channel is the end-state design; this PR lands it now to avoid carrying the stopgap.hashon the published header is the autobahn lane-block header hash (the same value the EVM receipt store records asblockHash), not a hash over the synthesized Tendermint header.parentHash/receiptsRoot/transactionsRootare 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 -lclean on all modified files;go build ./...clean.go test ./evmrpc/— 249 PASS, 8 SKIP, 0 FAIL (22s). Includes:TestSubscribeNewHeads(legacy event-bus path) — unchanged behaviour.TestSubscribeNewHeadsAutobahn(notifier path, in-process WS server end-to-end).BlockHeaderNotifier(deliver, overwrite-on-full, nil-safe) andencodeCommittedBlock(happy path + nilConsensusParamUpdates).evm_rpc_tests.sh(HTTP-method fixtures) against a real non-Autobahn cluster — 160/160 pass, no regressions on the request/response RPC surface.integration_test/evm_module/ws_test/— consensus-mode-agnostic, gated onSEI_EVM_WS_RUN_INTEGRATION=1, wired intoevm_rpc_tests.sh. Run end-to-end against both cluster modes:number=0x2b, hash0xe0dd089b...)number=0xf5, hash0xc4d1bc59...,GigaRouter initializedconfirmed in all 4 node logs)non-app-hash-breakinglabel 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-processBlockHeaderNotifier(overwrite-on-full, capacity 1).Wires this notifier from Autobahn’s
giga_routerpost-Commithook intoapp.App(implementstypes.BlockHeaderListener) and into the EVM WS server’sSubscriptionAPI, which now selects between the notifier path (Autobahn) and the existing event-bus subscription (legacy).Adds Autobahn-specific
newHeadsencoding (encodeCommittedBlock, with explicit Autobahnhashand zeroedparentHash/roots), plus unit + end-to-end WS tests and a new integration test package invoked byevm_rpc_tests.sh.Reviewed by Cursor Bugbot for commit 714479a. Bugbot is set up for automated code reviews on this repo. Configure here.