Skip to content

rpc: route -32601 (method not found) to a capable endpoint instead of crashing - #81

Open
SQD-Trevor-Agent wants to merge 1 commit into
masterfrom
alert-fix/D4bUGT-rpc-method-not-found-failover
Open

rpc: route -32601 (method not found) to a capable endpoint instead of crashing#81
SQD-Trevor-Agent wants to merge 1 commit into
masterfrom
alert-fix/D4bUGT-rpc-method-not-found-failover

Conversation

@SQD-Trevor-Agent

Copy link
Copy Markdown

Motivation

An eth-ingest process for a network with traces: true and two configured endpoints crash-looped in production (metis-mainnet). One endpoint (nodies) gates tracing methods behind a paid plan and returns -32601 for debug_traceBlockByHash; the other (Alchemy) serves it. When a trace request was scheduled onto nodies, the RpcError propagated all the way up:

RpcError({'code': -32601, 'message': 'Method debug_traceBlockByHash requires a paid plan...'}, ...)
program crashed  ->  sys.exit(1)   # sqa/util/asyncio.py

-32601 is not in _is_retryable_error's list, so _send's callback took the else branch and called item.future.set_exception(ex), which surfaced in the ingest coroutine and killed the whole process — even though a fully capable endpoint (Alchemy) was in the pool. The container then crash-looped every ~5 min and was never scraped.

Change

In RpcClient._send's done-callback, when the error is -32601 and at least one other connection can handle the request, mark the method unsupported on the failing connection (new RpcConnection.unsupported_methods, consulted by _can_handle alongside the static missing_methods) and re-queue the item so it lands on a capable endpoint. If no other endpoint can serve it, behaviour is unchanged (the future still fails), so a genuinely-unsupported-everywhere method still surfaces loudly rather than spinning.

This is the runtime, auto-detected complement to the existing operator-configured --endpoint-missing-method / missing_methods mechanism.

Test

tests/test_rpc_client.py (new): two endpoints via httpx.MockTransport, the first returns -32601 for debug_traceBlockByHash, the second a valid result. Asserts the call resolves via the capable endpoint and that the method is recorded in the failing connection's unsupported_methods.

  • Pre-fix: await client.call('debug_traceBlockByHash', ...) raises RpcError(-32601)FAILED (reproduces the crash).
  • Post-fix: routes to the good endpoint → OK.

Run: python -m unittest tests.test_rpc_client.

Falsification

Wrong if re-queuing could loop: it can't — the failing connection is excluded via unsupported_methods before re-push, and re-push only happens when another connection already satisfies _can_handle (so _reg_in_queue's handler assertion holds). If every endpoint returns -32601 for a required method, the future still fails as before.

When an endpoint returns JSON-RPC error -32601 (method not found / not
supported) for a request, and another endpoint in the pool can serve that
method, mark the method unsupported on the failing connection and retry it
there instead of propagating the error to the top-level coroutine, which
crashes the whole ingest process. Previously a single endpoint that gated a
method (e.g. a provider putting debug_traceBlockByHash behind a paid plan)
would crash-loop the ingester even though a capable endpoint was configured.
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