Skip to content

fix(giga): fail-fast broadcast_tx_commit and feed newBlockFilter from the notifier - #4012

Open
shemnon wants to merge 2 commits into
mainfrom
shemnon/remove-dash-b-block-support
Open

fix(giga): fail-fast broadcast_tx_commit and feed newBlockFilter from the notifier#4012
shemnon wants to merge 2 commits into
mainfrom
shemnon/remove-dash-b-block-support

Conversation

@shemnon

@shemnon shemnon commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Autobahn BroadcastTxCommit (-b block) returns ErrBroadcastTxCommitUnsupported before InsertTx instead of waiting on an empty EventBus/KV indexer (CON-352 hang). Comet is unchanged.
  • eth_newBlockFilter / eth_getFilterChanges take Autobahn block hashes from BlockHeaderNotifier (same FinalizeBlock hash as newHeads / eth_getBlockBy*), not /events. HTTP FilterAPI now gets the notifier.
  • CosmJS-style cosmos /tx, tx_search, TM subscribe, and /events fail-fast is not in this PR (fork-gated).

Linear: CON-409
Related: CON-352 (do not merge EventBus PR 3998 as the fix)

Test plan

  • go test ./sei-tendermint/internal/rpc/core/ -count=1 -run 'TestBroadcastTxCommit' — Autobahn fail-fast; Comet still hits mempool, not the sentinel
  • go test ./evmrpc/ -count=1 -run 'TestBlockHeaderNotifier_Subscribe|TestFilterAPI_NewBlockFilter|TestFilterBlockFilter' — notifier fan-out, Autobahn block filter hashes, Comet Events path unchanged
  • Confirm seid tx … -b block against an Autobahn node errors immediately; -b sync still works
  • Confirm eth_newBlockFilter + eth_getFilterChanges on Autobahn returns the overlay block hash, not 0x000…0

Made with Cursor

… the notifier

Autobahn has no EventBus wait for inclusion, so BroadcastTxCommit returns
unsupported before InsertTx. eth_newBlockFilter reads committed Autobahn
hashes from BlockHeaderNotifier instead of /events.

Co-authored-by: Cursor <cursoragent@cursor.com>

@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 2 potential issues.

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 8b5dedf. Configure here.

Comment thread evmrpc/notifier.go
Comment thread evmrpc/notifier.go
for _, fn := range listeners {
defer recoverAndLog()
fn(evt)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Panic recovery skips later listeners

Medium Severity

defer recoverAndLog() inside the listener loop does not isolate panics per callback. A panic in one listener aborts the loop; remaining listeners never run. TestBlockHeaderNotifier_SubscribePanicDoesNotSkipLaterListeners expects per-listener recovery, which needs an immediately invoked function around each call.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8b5dedf. Configure here.

# Conflicts:
#	sei-tendermint/internal/rpc/core/mempool.go
@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown

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

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedAug 25, 2026, 9:12 PM

@codecov

codecov Bot commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 58.61%. Comparing base (64e9bc6) to head (4651d51).
⚠️ Report is 7 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #4012      +/-   ##
==========================================
- Coverage   59.67%   58.61%   -1.07%     
==========================================
  Files        2261     2162      -99     
  Lines      194324   182703   -11621     
==========================================
- Hits       115967   107088    -8879     
+ Misses      67631    65830    -1801     
+ Partials    10726     9785     -941     
Flag Coverage Δ
sei-db 69.80% <ø> (ø)
sei-db-state-db ?

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

Files with missing lines Coverage Δ
app/abci.go 61.45% <ø> (ø)
app/app.go 71.42% <ø> (+0.08%) ⬆️
evmrpc/filter.go 73.52% <ø> (-0.39%) ⬇️
evmrpc/notifier.go 95.23% <ø> (ø)
evmrpc/server.go 85.09% <ø> (ø)
evmrpc/tests/utils.go 71.64% <ø> (ø)
sei-tendermint/internal/rpc/core/mempool.go 43.85% <ø> (+2.85%) ⬆️

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

@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.

The broadcast_tx_commit fail-fast change is sound, but the notifier fan-out added in evmrpc/notifier.go is broken in two ways: publish returns before invoking listeners on the (normal) non-full-channel path, so eth_newBlockFilter receives nothing, and the panic guard uses a function-scoped defer inside the listener loop, so a panicking listener silently skips all later ones. Several of the PR's own new tests should be failing as a result.

Findings: 2 blocking | 1 non-blocking | 3 posted inline

Blockers

  • None at the file/PR level.
  • 2 blocking issue(s) flagged inline on specific lines.

Non-blocking

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

Comment thread evmrpc/notifier.go

// publish pushes evt onto the newHeads channel with overwrite-on-full
// semantics, then invokes every subscribe listener. Used by both
// PublishStashed and OnBlockCommitted so the fan-out lives in one place.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[blocker] publish never reaches the new listener fan-out on its happy path. The first select still does case n.ch <- evt: return (line 141), so listeners are only invoked when the newHeads channel is full. In production headNotifier is shared between the WS server (whose SubscriptionAPI goroutine continuously drains recv()) and the HTTP FilterAPI, so the channel essentially never fills and appendBlockHash is never called — eth_newBlockFilter / eth_getFilterChanges return [] forever, which is the exact bug this PR set out to fix. On an HTTP-only node it "works" only after the buffer saturates.

The channel write needs to fall through to the fan-out rather than return, e.g. extract the overwrite-on-full send into a helper and call it before the listener loop:

func (n *BlockHeaderNotifier) publish(evt blockHeaderEvent) {
	n.sendToHeadChan(evt)
	n.notifyListeners(evt)
}

This should be caught by the new tests — TestBlockHeaderNotifier_SubscribeGetsEveryEvent, TestBlockHeaderNotifier_SubscribeDoesNotStealFromRecv, all four tests in filter_block_notifier_test.go, and TestFilterBlockFilterAutobahn all publish into a non-full channel and assert the listener saw the event.

Comment thread evmrpc/notifier.go
copy(listeners, n.listeners)
n.mu.Unlock()
for _, fn := range listeners {
defer recoverAndLog()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[blocker] defer is function-scoped, not block-scoped, so defer recoverAndLog() inside the loop does not isolate each listener. If fn(evt) panics, the panic unwinds all of publish; the deferred recoverAndLog recovers at function exit, but the loop never resumes and every subsequent listener is skipped. It also stacks one defer per listener on the consensus-commit goroutine on every block.

TestBlockHeaderNotifier_SubscribePanicDoesNotSkipLaterListeners asserts precisely the opposite of what this code does and should be failing.

The package already has the right primitive — wrap each call so the recover is scoped to one listener:

for _, fn := range listeners {
	runWithRecovery(func() { fn(evt) })
}

// More: https://docs.tendermint.com/master/rpc/#/Tx/broadcast_tx_commit
func (env *Environment) BroadcastTxCommit(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTxCommit, error) {
if env.gigaRouter().IsPresent() {
return nil, ErrBroadcastTxCommitUnsupported

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] This also changes eth_sendRawTransaction behaviour for operators running with evm.slow = true: evmrpc/send.go:136 routes through BroadcastTxCommit, so on Autobahn every raw EVM transaction now fails with broadcast_tx_commit is not supported on Autobahn and, unlike before, is never submitted (the old path called InsertTx first and only the wait timed out). Worth either routing the slow path to BroadcastTx when Autobahn is present, or calling the change out in release notes so evm.slow isn't left silently dropping transactions.

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.

1 participant