fix(health): restore RPC pool eligibility once a live probe recovers#4958
Merged
Conversation
overlayRpcPoolEligibility (src/health-serving.mjs) overlays the 15-minute live-cron-prober's health onto the once-daily static rpc/pools.json build. It correctly refreshed the displayed status field from the live probe, but pool_eligible/pool_eligibility_reasons were only ever narrowed from the static build's boolean -- `Boolean(endpoint.pool_eligible) && !wrongChain && !sustainedDown` -- so once an endpoint was marked ineligible at build time, no live recovery could ever restore it before the next daily rebuild (publish-cloudflare.yml, once/day). This was live and actively causing an outage: investigated directly against production Cloudflare/GitHub infrastructure (no external prober system exists -- src/health-prober.mjs, on a 15-minute Cron Trigger via workers/api.mjs's scheduled handler, is the only thing that writes KV_HEALTH_RPC_POOL). All 4 finney-rpc endpoints were served with status:"ok" (health_source:"live-cron-prober", genuinely fresh) but pool_eligible:false / pool_eligibility_reasons:["status-degraded"] -- self-contradictory served data -- and POST /rpc/v1/finney genuinely 503'd with "No eligible public RPC endpoint is available for proxy routing", because orderSafeRpcEndpoints filters candidates on this same stale pool_eligible field. Fix: recompute pool_eligible/pool_eligibility_reasons on every overlay from the freshly-refreshed status, not the frozen static boolean. Deliberately does NOT delegate to this file's existing endpointPoolEligibility helper (used correctly elsewhere by withPoolEligibility for /api/v1/endpoints) -- that helper's `status !== "ok"` check has no hysteresis, which would regress the pre-existing sustained-down tolerance (a single transient non-ok probe must not evict an endpoint; only 2+ CONSECUTIVE failures do). Only the structural checks (kind/auth_required/public_safe -- static properties that never change between prober runs) are reused; health eligibility is judged solely by wrong-chain/sustained-down, exactly as before, just no longer gated behind a boolean that could only ever get worse. score/score_reasons are left static (documented, out of scope): a stale score never excludes an eligible endpoint since weightedPickEndpoint falls back to weight 1 for score<=0, only deprioritises it in the failover order. Live-verified the fix directly: replaying the exact production finney-rpc static+live snapshot through the patched overlayRpcPoolEligibility now yields pool_eligible:true, pool_eligibility_reasons:["eligible"] instead of the frozen false.
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4958 +/- ##
=======================================
Coverage 97.94% 97.94%
=======================================
Files 163 163
Lines 19420 19431 +11
Branches 7376 7381 +5
=======================================
+ Hits 19020 19031 +11
Misses 53 53
Partials 347 347
🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Investigated the RPC-usage anomaly a live-wiring verification sweep flagged:
GET /api/v1/rpc/usage(30d window) showed a real per-endpoint breakdown across 6 finney RPC providers, but a directPOST /rpc/v1/finneycall genuinely returnedHTTP 503 {"error":{"code":"rpc_endpoint_unavailable","message":"No eligible public RPC endpoint is available for proxy routing."}}.No external prober system exists for this — confirmed directly against Cloudflare (
workers_list) and this repo:src/health-prober.mjs, invoked fromworkers/api.mjs'sscheduled()handler on a 15-minute Cron Trigger, is the only thing that writesKV_HEALTH_RPC_POOL. Reading it end-to-end:overlayRpcPoolEligibility(src/health-serving.mjs) merges that 15-minute live snapshot onto the once-daily staticrpc/pools.jsonbuild (publish-cloudflare.yml,cron: "17 7 * * *").statusfield from the live probe, butpool_eligible/pool_eligibility_reasonswere only ever narrowed from the static build's boolean:Boolean(endpoint.pool_eligible) && !wrongChain && !sustainedDown. Once an endpoint was marked ineligible at build time, no live recovery could ever restore it before the next daily rebuild — up to ~24h.curl https://api.metagraph.sh/api/v1/rpc/pools: all 4 finney-rpc endpoints were served withstatus:"ok"(health_source:"live-cron-prober", genuinely fresh) butpool_eligible:false/pool_eligibility_reasons:["status-degraded"]— self-contradictory served data — andorderSafeRpcEndpointsfilters routing candidates on that same stalepool_eligiblefield, producing the real 503 outage.Fix
Recompute
pool_eligible/pool_eligibility_reasonson every overlay from the freshly-refreshed status, not the frozen static boolean.Deliberately does not delegate to this file's existing
endpointPoolEligibilityhelper (used correctly elsewhere bywithPoolEligibilityfor/api/v1/endpoints) — that helper'sstatus !== "ok"check has no hysteresis, which would regress the pre-existing sustained-down tolerance (a single transient non-ok probe must not evict an endpoint; only 2+ consecutive failures do, per this function's own header comment). Only the structural checks (kind/auth_required/public_safe— static properties that never change between prober runs) are reused; health eligibility is judged solely by wrong-chain / sustained-down, exactly as before, just no longer gated behind a boolean that could only ever get worse.score/score_reasonsare deliberately left static (documented in the code): a stale score never excludes an eligible endpoint, sinceweightedPickEndpointfalls back to weight 1 forscore<=0— it only deprioritises it in the failover order, a much lower-severity issue than the outright routing outage this fixes.Test plan
status:"ok",pool_eligible:false,["status-degraded"]) through the patchedoverlayRpcPoolEligibilityin a real Node process against this repo's own module — now yieldspool_eligible:true,pool_eligibility_reasons:["eligible"]instead of the frozenfalse.tests/health-serving.test.mjs), plus tests for: hysteresis is preserved (a single non-ok probe below the sustained-down threshold keeps eligibility), structural policy (auth_required) still disqualifies regardless of live health, and reasons are re-derived (not just the boolean).tests/health-serving.test.mjsandtests/mcp-server.test.mjsto carry realistickind/auth_required/public_safefields — the prior minimal stubs (missing these) would now (correctly) fail structural eligibility under the fixed logic.npm run lint/npm run format:checkcleannpm test— 256/256 files, 7582/7582 tests green (full suite)validate:client-sdk-syncnot applicable