Skip to content

fix(network): route block-pinned requests to fallbacks that have the block - #29

Closed
shpookas wants to merge 4 commits into
feat/websocket-supportfrom
fix/fallback-tip-leader
Closed

shpookas wants to merge 4 commits into
feat/websocket-supportfrom
fix/fallback-tip-leader

Conversation

@shpookas

Copy link
Copy Markdown

Summary

  • Tip-leader routing: a request pinned to a block above every routed upstream's known head is tried first on the tier:fallback upstreams whose head already reached it, then on the routed list. When a fallback's newHeads leads, clients call at blocks the routed upstreams don't have yet, and today those calls fail on the routed upstreams first. Counted in erpc_network_tip_leader_route_total.
  • Hedge keeper: once a request has escalated, an all-missing ErrUpstreamsExhausted is no longer kept, so a hedge leg that only re-swept the routed upstreams can't cancel the leg still waiting on a fallback.
  • Future-block short-circuit (served-tip): no synthetic null for a block a reachable fallback already has.

Still one escalation per request. No-op for consensus, with failover off, or when a routed head is unknown or at the block. Leaders respect use-upstream and their availability bounds.

Test plan

  • TestFailover_TipLeaderRouting and related tests (15 scenarios), each failing without its fix; TestFailover_* passes under -race
  • go test ./erpc/ ./common/ ./upstream/ ./telemetry/: only TestNetwork_Forward/ForwardLlamaRPCEndpointRateLimitResponseSingle fails, identically on 05126d0
  • Canary

…block

- Tip-leader routing: a request pinned to a block above every routed
  upstream's known head is tried first on the tier:fallback upstreams whose
  head already reached it (their newHeads keep their pollers current), then
  on the routed list. It takes the per-request escalation, so other hedge
  legs and retries do not escape; the sweep that took it still escapes once
  to the fallbacks it has not tried. No-op when a routed head is unknown or
  at the block, for consensus, and with failover off. Leaders must match the
  use-upstream selector and their enforced availability bounds. Counted in
  erpc_network_tip_leader_route_total.
- Hedge keeper: once a request has escalated, an all-missing
  ErrUpstreamsExhausted is not kept, so a leg that only re-swept the routed
  upstreams cannot cancel a leg still waiting on a fallback. If every leg
  misses, the hedge returns the last result.
- Future-block short-circuit (served-tip): skip the synthetic null when a
  reachable fallback already has the block, except for consensus.
jleeh and others added 3 commits October 5, 2026 18:55
…tion

Same outcome as the previous commit, with fewer moving parts:

- Fallbacks whose polled head has the block join the request's upstream
  list, and tiering now runs before the has-the-block partition, so an
  upstream that has the block goes first whatever its tier. This is
  ordering, not an escalation: drops leaderRouted, the shared
  MarkEscalatedToFallbacks handling, the maxLoopIterations bump and
  NormalizedRequest.EscalatedToFallbacks. The once-per-request escape is
  unchanged; use-upstream is enforced by NextUpstream as for any upstream.
- The future-block short-circuit is skipped when a tip leader was added,
  instead of rescanning the fallbacks.
- The hedge keeper no longer keeps an all-missing ErrUpstreamsExhausted
  at all, rather than only after escalation: a sibling leg may be on an
  upstream this leg never tried, which already happens in the plain
  escape path with no tip leader (TestFailover_HedgeKeepsEscalatedSibling
  and TestFailover_HedgeKeepsSlowFallbackAfterFastFallbackMiss fail on
  05126d0). If every leg ends that way the hedge still returns the last
  one to the retry layer.

Tests unchanged; TestFailover_* pass, including under -race.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
… fix

Fallbacks are only for when the primaries are down. Routing block-pinned
reads to a fallback whose head leads sends traffic to it while the
primaries are healthy, so the routing, its metric and the future-block
short-circuit exception go. The race that motivated it is fixed where it
starts instead: eRPC delivering a fallback's head before any primary has
the block (next commit).

The hedge keeper fix stays: an all-missing ErrUpstreamsExhausted is no
longer kept, because a sibling leg may be on an upstream this leg never
tried. That already happens in the plain escape path, with no tip leader
involved (both TestFailover_Hedge* tests fail without it).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
newHeads is subscribed on every WebSocket upstream and each head goes
out from whichever source announces it first. With failover on, a
fallback-tier upstream whose WebSocket is faster told clients about a
block no primary had yet; their follow-up reads then missed on the
primaries, or escaped to the fallback while the primaries were healthy.

A fallback's head now reaches clients, and feeds the delivered-head
floor, only while no upstream outside that tier which the selection
policy routes to has a live newHeads subscription of its own. "Down"
is the policy's verdict, as for reads, so custom policies that keep
fallbacks in the ordered list behave the same. A network whose only
WebSocket upstreams are fallbacks, or whose primaries' subscriptions
are dead, keeps receiving fallback heads. A held-back head still
updates that fallback's state poller.

NetworkHandle.SuggestLatestBlock now reports whether the head may be
delivered; the indexer drops it before dedup otherwise.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@jleeh

jleeh commented Oct 6, 2026

Copy link
Copy Markdown

Superseded by #31. Instead of routing reads to a fallback whose head leads, #31 stops eRPC delivering a fallback's heads while a primary is streaming, so clients aren't told about blocks the primaries don't have yet. It applies the same tier rule to logs/newPendingTransactions subscriptions too. Fallbacks should only serve when the primaries are down. Your hedge-keeper fix and its tests are carried over (credited in the first commit). Thanks @shpookas!

@jleeh jleeh closed this Oct 6, 2026
snowkide pushed a commit that referenced this pull request Oct 7, 2026
…cket-support

Re-applies the net change of #24 (#20 + #21 + #23) onto the 2026-09-28
rewrite of feat/websocket-support (stacked on #29):

- auth: forwardedClientId strategy; secret accepted as path /<secret>,
  ?apikey= query and header (NewPayloadFromHttp takes the URL path)
- architecture: jsonrpc passthrough for non-EVM HAProxy upstreams, now
  coexisting with upstream's svm architecture and registered as a no-op
  ArchitectureHandler (required by the new architecture registry); its
  error extractor defers to the EVM one, as before
- json-rpc: treat "error": null as success; omit empty params

Co-Authored-By: Claude Opus 5.5 <[email protected]>
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.

2 participants