Conversation
…block When a request is pinned to a block above every routed upstream's known head, try the tier:fallback upstreams whose head already reached it first, then the routed list. A fallback's newHeads subscription keeps its poller current, so it is often the source that announced the block to clients. Without this the routed upstreams answer missing data first and the request only reaches the fallback through the escape, if at all. Uses the per-request escalation, so the escape does not fire again. No-op when any routed head is unknown or already at the block, for consensus, and when failover is off. Counted in erpc_network_tip_leader_route_total.
…calation The hedge keeper kept an ErrUpstreamsExhausted whose causes are all missing data, on the basis that no sibling leg can do better. Once the request has escalated to the fallbacks that no longer holds: the escalated leg may still be waiting on a fallback that has the data, while a later leg, with the escalation already spent, only re-sweeps the routed upstreams, misses again, and cancels it. Seen as "missing data" errors with hedges:1 on chains where a fallback announces heads first. Keep racing when the request escalated and no fallback appears among the causes yet. If every leg finishes this way the hedge still returns the last result.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Leader selection can bypass routing constraints or leave fallbacks untried, and a hedge can still cancel a successful fallback attempt.
Review effort: Balanced
Findings: 4
Open (4)
What changed in this PR
This PR aims to serve block-pinned EVM requests from a fallback that has reached the requested block before trying lagging routed upstreams. It also changes hedge handling so a missing-data result does not prematurely end a fallback attempt.
Changes:
- Add tip-leader routing and a metric for its use.
- Adjust hedge handling and add failover tests.
- Document the routing behavior and metric.
| File | Description |
|---|---|
telemetry/metrics.go |
Defines the tip-leader routing counter. |
erpc/networks.go |
Selects ahead-of-tip fallbacks before routed upstreams. |
erpc/networks_tip_leader_test.go |
Tests leader routing and hedge behavior. |
erpc/networks_failover_escape_test.go |
Updates the escape fixture and test scenario. |
erpc/network_executor.go |
Changes which missing-data hedge results are kept. |
docs/pages/reference/metrics.mdx |
Lists the new counter. |
docs/pages/config/projects/networks.mdx |
Describes tip-leader routing. |
common/request.go |
Exposes the request’s fallback-escalation state. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- Hedge keeper: once the request escalated, never keep an all-missing result. ErrorsByUpstream is request-wide, so a fallback that already missed does not mean none is still working; the hedge still returns the last result if every leg misses. - Tip-leader routing no longer closes the escape: the sweep that took the escalation for its leaders still escapes, once, to the fallbacks it has not tried (e.g. an HTTP fallback whose 5m poller trails the block). - Check every routed upstream's head, fallback-tier ones included, and drop the default-tier count guard. - Only fallbacks allowed by the request's use-upstream selector can lead.
With served-tip on, tryShortCircuitFutureBlock answers a numbered eth_getBlockByNumber above every eligible head with null before the sweep. A fallback the policy keeps out of that set can already have the block (the tip-leader case), so with failover on, skip the short-circuit when a selector-allowed fallback's head has reached it. Shares the fallback lookup with tip-leader routing. Docs: the sweep that routes to tip leaders still escapes once to untried fallbacks; only other legs and retries do not.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The future-block exception can bypass a valid null for consensus requests or for fallbacks whose configured availability excludes the block.
Review effort: Balanced
Findings: 2
Open (2)
Resolved since last review (2)
| } | ||
| // A fallback the sweep can still reach may already have it while the | ||
| // policy keeps it out of the eligible set. | ||
| if n.cfg.Failover.Enabled() && len(n.fallbacksAtBlock(ctx, req, method, bn, nil)) > 0 { |
| if _, ok := skip[fb.Id()]; ok || upstreamLatestBlock(fb) < bn { | ||
| continue | ||
| } |


Closed.