Skip to content

fix(websocket): refuse newHeads when no live ingress#7

Closed
snowkide wants to merge 1 commit into
feat/websocket-supportfrom
fix/ws-refuse-dead-newheads
Closed

fix(websocket): refuse newHeads when no live ingress#7
snowkide wants to merge 1 commit into
feat/websocket-supportfrom
fix/ws-refuse-dead-newheads

Conversation

@snowkide

Copy link
Copy Markdown

Summary

  • Minimal restore of the fail-closed newHeads gate dropped in slim 1886390: refuse with retryable ErrNoLiveSubscriptionSource (HTTP 503) when no ingress reports Healthy(), after a short bootstrap wait via IngressHealth / HealthReporter.
  • Aligns ClientStaysConnectedOnUpstreamDrop with self-heal (client WS stays up on upstream drop; 1001 GoingAway remains shutdown-only).
  • vs fix(websocket): restore newHeads refusal gate + align upstream-drop test with self-heal #6: deliberately omits LastHead timestamp metric, healthcheck networks[].subscriptions, and SubscriptionHealth API — those are observability, not required for MultiNode failover.

Context

Chainlink MultiNode + eRPC WS: head-less pods still returned subscription IDs that never delivered heads. Core self-heal (ping/pong + resubscribe) remains; this only makes dead pods fail closed again.

Supersedes the approach in #6 (same bug, smaller surface). Related: #1.

Test plan

  • go test ./indexer/ -count=1 -timeout 60s -run 'IngressHealth'
  • go test ./erpc/ -count=1 -timeout 180s -run 'WaitForLive|SubscriptionRecovery|UpstreamDiesUngracefully|GracefulShutdown'
  • After merge: canary — head-less pod returns ErrNoLiveSubscriptionSource on eth_subscribe newHeads; upstream blip does not emit client 1001

Made with Cursor

Restore only the fail-closed gate dropped in the post-merge slim:
ErrNoLiveSubscriptionSource + IngressHealth/HealthReporter + a short
bootstrap wait. Head-less pods return retryable 503 instead of zombie
subscription IDs. Align ClientStaysConnectedOnUpstreamDrop with self-heal
(1001 GoingAway remains shutdown-only). Skip LastHead metric and
healthcheck subscriptions surface.

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

Copy link
Copy Markdown
File Lines Key changes Risk
🟠 errors.go +26/-0 ErrNoLiveSubscriptionSource, ErrorStatusCode ⚠ ErrNoLiveSubscriptionSource, ErrCodeNoLiveSubscriptionSource
🟠 subscription_manager.go +48/-0 waitForLiveHeadSource ⚠ ErrNoLiveSubscriptionSource
🔵 indexer.go +34/-0 HealthReporter, IngressHealth
3 test files +184

xray — see through AI slop with deterministic architecture PR diff reviews

@snowkide

Copy link
Copy Markdown
Author

Closing: wrong layer for our deployment model.

Chainlink nodes intentionally have a single primary pointing at eRPC. eRPC owns upstream failover/self-heal; MultiNode must not be used as the recovery mechanism (refuse → Unreachable → No live RPC with nothing else to select).

Required behaviour: eRPC keeps the client WS alive, fails over/re-subscribes upstreams internally, resumes newHeads, and only then MultiNode can mark the sole primary Alive again after redial/probe. A subscribe refusal gate pushes failure into Chainlink instead of absorbing it in eRPC.

@snowkide

Copy link
Copy Markdown
Author

Closed: eRPC must absorb upstream failure; Chainlink stays single-RPC to eRPC.

@snowkide snowkide closed this Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant