Reference
Error taxonomy
AI agents: fetch https://docs.erpc.cloud/reference/errors.llms.txt for the complete machine-readable version of this page (full configuration schema, defaults, worked examples, and source links). Append `.llms.txt` to any docs URL for the same treatment.AIFor agents: /reference/errors.llms.txt

Error taxonomy

A catalog of all ~73 typed errors eRPC can emit, organized by category. Each entry shows whether the error retries on the same upstream, retries on a different upstream, what HTTP status and JSON-RPC code it produces on the wire, and whether it is live or dead code. Every vendor's idiosyncratic message is normalized into one of these types before retry logic or metrics ever see it.

Agent reference

Copy one of these prompts into your AI agent session (Claude Code, Cursor, …) — each one points the agent at this page's machine-readable reference so it can do the work correctly:

Prompt Example #1: debug why requests are exhausting all upstreams
My eRPC instance frequently returns ErrUpstreamsExhausted to clients and I don't know
which upstream error type is dominating. Read the error taxonomy, then inspect my
my eRPC config config and tell me which metrics and log fields to check, and how to adjust
retry or circuit-breaker settings based on the dominant child error. Reference:
https://docs.erpc.cloud/reference/errors.llms.txt
Prompt Example #2: set up production alerting on error severity
I want Prometheus alerts that fire on critical errors only and ignore expected
noise like hedge discards and client disconnects. Using the severity classification in
the eRPC error taxonomy, write PromQL alert expressions for my eRPC config deployment
and explain which error codes map to each severity level. Reference:
https://docs.erpc.cloud/reference/errors.llms.txt
Prompt Example #3: trace a -32003 response on eth_sendRawTransaction
Some of my eth_sendRawTransaction calls return JSON-RPC code -32003 even though the
transactions go through. Explain which normalizer rules apply to sendRawTransaction,
why eRPC may have retried, and how to distinguish already_known from nonce_too_low
using the error details fields. Work with my existing eRPC config. Reference:
https://docs.erpc.cloud/reference/errors.llms.txt
Error taxonomy — full agent referenceExpand for every option, default, and edge case — or copy this entire section into your AI assistant.

How it works

Two-layer taxonomy. Go errors are BaseError-rooted structs with a string ErrorCode constant. Upstream-attributed errors additionally carry an UpstreamAwareError accessor. ErrJsonRpcExceptionExternal is deliberately NOT a StandardError — it is the raw vendor JSON-RPC object used as input to normalizers only; it never propagates beyond the client layer.

Normalization pipeline. HTTP responses go through ExtractJsonRpcError (architecture/evm/error_normalizer.go), which runs a vendor-specific hook first, then evaluates 22 ordered rules (first match wins). gRPC/BDS responses go through ExtractGrpcErrorFromGrpcStatus (common/grpc_errors.go), which checks BDS error codes first, then standard gRPC codes. Both paths produce a typed internal error wrapping ErrJsonRpcExceptionInternal that carries both the original vendor code and the normalized eRPC code.

Retryability is dual-axis. IsRetryableTowardsUpstream returns false if any of 10 specific codes appear anywhere in the cause chain (including joined multi-errors). IsRetryableTowardNetwork defaults to true and returns false only when: (a) ErrUpstreamsExhausted has a nil cause, (b) a multi-error cause has ALL non-retryable children, or (c) a linear cause chain carries Details["retryableTowardNetwork"]=false. The flag walk stops at multi-error wrappers to avoid order-dependent bugs.

Wire translation. TranslateToJsonRpcException converts internal errors to a JSON-RPC numeric code. For ErrUpstreamsExhausted, it selects the "dominant" child error (highest occurrence count, skipping ErrCodeUpstreamRequestSkipped and ErrCodeEndpointUnsupported). HTTP status is determined by two identical switch functions in erpc/http_server.go — not by per-type ErrorStatusCode() methods, which are dead code.

Error label for metrics. Both error metrics use common.ErrorFingerprint(err) as the error label value. ErrorFingerprint calls ErrorSummary then redacts hashes/addresses/IPs/numbers and caps at 256 characters. The mode of ErrorSummary is controlled by metrics.errorLabelMode, defaulting to compact (code-only labels, preventing high cardinality). verbose mode is human-readable but creates unbounded time series — use only in development.

Severity classification. ClassifySeverity(err) categorizes errors for the severity Prometheus label: info for nil/client errors/execution exceptions; warning for non-retryable-toward-upstream errors, ErrEndpointRequestCanceled, context.Canceled; critical for everything else.

ErrUpstreamsExhausted aggregation. This is a multi-error aggregator: its Cause is an errors.Join of all per-upstream errors. SummarizeCauses() buckets children into 19 categories and appends a human-readable suffix to the message: "(2 upstream timeout, 1 upstream rateLimit)". When buildErrorResponseBody sees a single-child ErrUpstreamsExhausted, it unwraps it — clients see the real upstream error, not the wrapper.

Error class hierarchy:

StandardError interface
└── BaseError (embedded in all domain errors)
    ├── UpstreamAwareError (embedded in upstream-attributed errors)

    ├── Server / request lifecycle
    │   ├── ErrInvalidRequest, ErrInvalidUrlPath, ErrInvalidConfig
    │   ├── ErrRequestTimeout, ErrInternalServerError, ErrNotImplemented

    ├── Auth
    │   ├── ErrAuthUnauthorized, ErrAuthRateLimitRuleExceeded

    ├── Project / Network
    │   ├── ErrProjectNotFound, ErrProjectAlreadyExists
    │   ├── ErrNetworkNotFound, ErrNetworkNotSupported, ErrNetworkInitializing
    │   ├── ErrUnknownNetworkID, ErrUnknownNetworkArchitecture
    │   ├── ErrInvalidEvmChainId, ErrFinalizedBlockUnavailable, ErrNetworkRequestTimeout

    ├── Upstream lifecycle
    │   ├── ErrUpstreamClientInitialization, ErrUpstreamInitialization
    │   ├── ErrNoUpstreamsLeftToSelect, ErrNoUpstreamsDefined, ErrNoUpstreamsFound

    ├── Upstream request routing
    │   ├── ErrUpstreamRequest (wrapper), ErrUpstreamRequestSkipped
    │   ├── ErrUpstreamBlockUnavailable, ErrUpstreamMethodIgnored
    │   ├── ErrUpstreamSyncing, ErrUpstreamShadowing, ErrUpstreamNotAllowed
    │   ├── ErrUpstreamHedgeCancelled, ErrUpstreamMalformedResponse
    │   ├── ErrUpstreamsExhausted (multi-error aggregator)

    ├── Rate limiting
    │   ├── ErrRateLimitBudgetNotFound, ErrProjectRateLimitRuleExceeded
    │   ├── ErrNetworkRateLimitRuleExceeded, ErrUpstreamRateLimitRuleExceeded

    ├── Endpoint (upstream HTTP/gRPC responses)
    │   ├── ErrEndpointUnauthorized, ErrEndpointUnsupported
    │   ├── ErrEndpointClientSideException, ErrEndpointExecutionException
    │   ├── ErrEndpointTransportFailure, ErrEndpointServerSideException
    │   ├── ErrEndpointRequestTimeout, ErrEndpointRequestCanceled
    │   ├── ErrEndpointCapacityExceeded, ErrEndpointBillingIssue
    │   ├── ErrEndpointMissingData, ErrEndpointRequestTooLarge
    │   ├── ErrEndpointContentValidation, ErrEndpointNonceException

    ├── Failsafe policies
    │   ├── ErrFailsafeConfiguration, ErrFailsafeTimeoutExceeded
    │   ├── ErrFailsafeRetryExceeded, ErrFailsafeCircuitBreakerOpen

    ├── Consensus
    │   ├── ErrConsensusDispute, ErrConsensusLowParticipants

    ├── getLogs / client range errors
    │   ├── ErrGetLogsExceededMaxAllowedRange
    │   ├── ErrGetLogsExceededMaxAllowedAddresses, ErrGetLogsExceededMaxAllowedTopics

    ├── JSON-RPC (carriers)
    │   ├── ErrJsonRpcRequestUnmarshal, ErrJsonRpcRequestUnresolvableMethod
    │   ├── ErrJsonRpcRequestPreparation
    │   └── ErrJsonRpcExceptionInternal (normalized carrier)

    └── Store / cache
        ├── ErrInvalidConnectorDriver, ErrRecordNotFound, ErrRecordExpired

Not StandardError (standalone types):
├── ErrJsonRpcExceptionExternal — raw upstream JSON-RPC error; normalizer input only
├── TaskFatalError              — initializer stop signal; IsTaskFatal()=true
└── ErrDynamicTimeoutExceeded   — sentinel for context.WithTimeoutCause

Config schema

YAML pathTypeDefaultBehavior / footguns
metrics.errorLabelModestring"compact" (common/defaults.go:L762-L764 (opens in a new tab))Controls ErrorSummary output format. "compact": code-only labels (low cardinality). "verbose": CodeChain: cleanedDeepestMessage (high cardinality; development only). Only "compact" and "verbose" are accepted (common/validation.go:L147-L148 (opens in a new tab)). Applied globally via common.SetErrorLabelMode. Source: common/config.go:L2536-2550

No other error-specific config fields. Vendor normalization is unconditional for any upstream with a matching Vendor().

Complete error-code table

Legend:

#Error codeUNWire HTTPJSON-RPCNotes
1ErrInvalidRequestyesyes400−32602invalid body/headers
2ErrInvalidUrlPathyesyes400−32602malformed URL path
3ErrInvalidConfigyesyes200−32603startup-only
4ErrRequestTimeoutyesyes200−32603pre-upstream timeout
5ErrInternalServerErroryesyes200−32603internal
6ErrAuthUnauthorizedyesyes401−32016auth strategy rejected
7ErrAuthRateLimitRuleExceededno (capacity)yes429−32005auth-level budget
8ErrProjectNotFoundyesyes404−32603project not configured
9ErrProjectAlreadyExistsn/an/astartup duplicate
10ErrNetworkNotFoundyesyes404−32603network not configured
11ErrUnknownNetworkIDyesyes200−32603
12ErrUnknownNetworkArchitectureyesyes200−32603
13ErrNotImplementedyesyes200−32603
14ErrInvalidEvmChainIdyesyes200−32603
15ErrFinalizedBlockUnavailableyesyes200−32603finality check
16ErrUpstreamClientInitializationyesyesnot fatal unless wrapped in TaskFatalError
17ErrUpstreamRequestinheritedinheritedinheritedwrapper; carries durationMs/attempts/retries/hedges
18ErrUpstreamMalformedResponseyesyes200−32603
19ErrUpstreamsExhaustedrecursiverecursive200from dominant childmulti-error aggregator
20ErrNoUpstreamsLeftToSelectyesyes200−32603per-upstream states in details
21ErrNoUpstreamsDefinedyesyes200−32603Dead code — zero production call sites
22ErrNoUpstreamsFoundyesyes200−32603
23ErrNetworkInitializingyesyes200−32603retry shortly
24ErrNetworkNotSupportedyesyes404−32603no provider covers network
25ErrUpstreamNetworkNotDetectedyesyesDead code — zero production call sites
26ErrUpstreamInitializationyesyes
27ErrUpstreamRequestSkippednoyes200−32603permanent skip
28ErrUpstreamBlockUnavailableyesyes200−32603transient lag
29ErrUpstreamMethodIgnorednoyes200−32601ignoreMethods config
30ErrUpstreamSyncingyesyes200−32603node still syncing
31ErrUpstreamShadowingyesyesshadow mode
32ErrUpstreamNotAllowedyesyesuse-upstream directive
33ErrUpstreamHedgeCancelledyesyeshedge discard
34ErrResponseWriteLockyesyesDead code — superseded by ErrUpstreamHedgeCancelled
35ErrJsonRpcRequestUnmarshalnono400−32700retryableTowardNetwork=false always
36ErrJsonRpcRequestUnresolvableMethodyesno200−32603retryableTowardNetwork=false; note: gets −32603, not −32700
37ErrJsonRpcRequestPreparationyesno200−32603Dead code — N=false default is caller-overridable (unique in taxonomy)
38ErrFailsafeConfigurationn/an/astartup
39ErrFailsafeTimeoutExceededyesyes200−32603scope in message
40ErrFailsafeRetryExceededinheritedinheritedinheritedscope in message
41ErrFailsafeCircuitBreakerOpennoyes200−32603
42ErrRateLimitBudgetNotFoundn/an/aconfig
43ErrRateLimitRuleNotFoundn/an/aDead code — zero production call sites
44ErrProjectRateLimitRuleExceededno (capacity)yes429−32005
45ErrNetworkRateLimitRuleExceededno (capacity)yes429−32005
46ErrNetworkRequestTimeoutyesyes200−32603network-scope timeout
47ErrUpstreamRateLimitRuleExceededno (capacity)yes200−32005NOT in wire 429 list despite method returning 429
48ErrUpstreamExcludedByPolicyyesyesDead code — zero production call sites; exclusion is structural
49ErrEndpointUnauthorizednoyes401−32016provider 401/403
50ErrEndpointUnsupportednoyes200−32601
51ErrEndpointClientSideExceptionyesyes (default)200from causeN:no set by some normalizer branches (rules 17, 18d)
52ErrEndpointExecutionExceptionnono2003 or −32003N:yes overridden for eth_sendRawTransaction reverts
53ErrEndpointTransportFailureyesyes−32603URL stripped from cause msg (credential hygiene)
54ErrEndpointServerSideExceptionyesyes200from causeoriginalStatusCode stored in details
55ErrEndpointRequestTimeoutyesyes200−32015
56ErrEndpointRequestCanceledyesyesseverity=warning
57ErrEndpointCapacityExceededno (capacity)yes429−32005
58ErrEndpointBillingIssuenoyes200−32005
59ErrEndpointMissingDatayesyes200−32014details: latestBlock/finalizedBlock/maxAvailableRecentBlocks
60ErrUpstreamNodeTypeMismatchyesyes−32603archive vs full node
61ErrEndpointRequestTooLargenoyes200−32012complaint: evm_block_range or evm_addresses
62ErrJsonRpcExceptionInternalwrapperwrappernormalizedCode fieldinternal carrier for normalized codes
63ErrJsonRpcExceptionExternaln/an/araw upstream JSON-RPC; normalizer input only
64ErrInvalidConnectorDrivern/an/aconfig
65ErrRecordNotFoundyesyescache miss
66ErrRecordExpiredyesyescache TTL exceeded
67ErrConsensusDisputemultimulti200−32603retryable if ANY child is retryable
68ErrConsensusLowParticipantsmultimulti200−32603same any-child rule
69ErrGetLogsExceededMaxAllowedRangeyesyes200−32012eRPC cap, not upstream
70ErrGetLogsExceededMaxAllowedAddressesyesyes200−32012
71ErrGetLogsExceededMaxAllowedTopicsyesyes200−32012
72ErrEndpointContentValidationnoyes200−32603try different upstream
73ErrEndpointNonceExceptionyesno200−32003reason: already_known or nonce_too_low; idempotency

Non-StandardError types: ErrJsonRpcExceptionExternal (normalizer input), TaskFatalError (initializer stop signal), ErrDynamicTimeoutExceeded (sentinel distinguishing failsafe-policy timeout from HTTP server deadline).

JSON-RPC numeric codes

All defined at common/errors.go:L2151-L2174 (opens in a new tab).

ConstantValueCategory
JsonRpcErrorUnknown−99999eRPC internal — unclassified fallback
JsonRpcErrorCallException−32000standard — generic call exception
JsonRpcErrorTransactionRejected−32003standard — transaction rejected; also nonce exceptions
JsonRpcErrorClientSideException−32600standard — invalid request
JsonRpcErrorUnsupportedException−32601standard — method not found/unsupported
JsonRpcErrorInvalidArgument−32602standard — invalid params
JsonRpcErrorServerSideException−32603standard — internal error; eRPC default for unmapped errors
JsonRpcErrorParseException−32700standard — JSON parse error
JsonRpcErrorEvmReverted3de-facto — EVM execution reverted (positive code)
JsonRpcErrorCapacityExceeded−32005eRPC-normalized — maps all rate-limit / capacity types
JsonRpcErrorEvmLargeRange−32012eRPC-normalized — request range too large
JsonRpcErrorMissingData−32014eRPC-normalized — data not on this node
JsonRpcErrorNodeTimeout−32015eRPC-normalized — node-level timeout
JsonRpcErrorUnauthorized−32016eRPC-normalized — unauthorized/forbidden

Wire HTTP status mapping

Wire HTTPTriggered by (HasErrorCode anywhere in chain)
400ErrInvalidUrlPath, ErrJsonRpcRequestUnmarshal, ErrInvalidRequest
401ErrAuthUnauthorized, ErrEndpointUnauthorized
404ErrProjectNotFound, ErrNetworkNotFound, ErrNetworkNotSupported
429ErrAuthRateLimitRuleExceeded, ErrProjectRateLimitRuleExceeded, ErrNetworkRateLimitRuleExceeded, ErrEndpointCapacityExceeded
200everything else

ErrUpstreamRateLimitRuleExceeded returns 200 on the wire despite its ErrorStatusCode() method returning 429. It is not in the wire 429 switch.

HTTP/JSON-RPC vendor normalization rules

Entry point: architecture/evm/error_normalizer.go:L20. Triggered when jr.Error != nil OR HTTP status > 299. Rules evaluated in order — first match wins. originalCode (vendor numeric) is preserved; normalizedCode is set to the eRPC value.

#TriggerNormalized errorJSON-RPCUN
0Vendor hook returns non-nilvendor-definedvendor-defined
1Block-range-too-large strings ("Try with this block range", "max block range", "range too large", "too many results", "try paginating", etc.)ErrEndpointRequestTooLarge (evm_block_range)−32012noyes
2Address-count strings ("specify less number of address", "addresses or topics per search position", "filters"+"current limit is")ErrEndpointRequestTooLarge (evm_addresses)−32012noyes
3"sender is over rate limit" (OP sequencer)ErrEndpointCapacityExceeded−32005nono (all providers hit same sequencer)
4status 402, "reached the free tier", "Monthly capacity limit", "/billing"ErrEndpointBillingIssue−32005noyes
5status 429, "Too many requests", "rate limit", "has exceeded", "limit exceeded", etc.ErrEndpointCapacityExceeded−32005noyes
6Block-tag unsupported ("pending block is not available", "safe block not found", "finalized is not a supported", etc.)ErrEndpointClientSideException−32600yesyes (deliberately retryable)
7Missing-data patterns ("missing trie node", "unknown block", "header not found", "transaction not found", "no historical rpc", etc.)ErrEndpointMissingData−32014yesyes
8"execution timeout"ErrEndpointServerSideException−32015yesyes
9Reverts: "reverted", "VM execution error", "VM Exception", "intrinsic gas too high"ErrEndpointExecutionException3nono (yes for eth_sendRawTransaction)
10"EVM error: InvalidJump" (Berachain)ErrEndpointExecutionException3nono (yes for eth_sendRawTransaction)
11Duplicate-tx: "already known", "tx already in mempool", "transaction already exists", etc. — must precede rule 14ErrEndpointNonceException (already_known)−32003yesno
12Nonce conflict: "nonce too low", "nonce has already been used"ErrEndpointNonceException (nonce_too_low)−32003yesno
13"insufficient funds", "insufficient balance"ErrEndpointExecutionException−32003noyes (trace_*/debug_*/eth_trace* only — state-reconstruction artifact; non-retried for writes & live simulations)
14code == −32003, "out of gas", "gas too low", "IntrinsicGas"ErrEndpointExecutionException−32003nono (yes for eth_sendRawTransaction)
15a"not found"/"not available" AND ("Method"|"module")ErrEndpointUnsupported−32601noyes
15bsame outer AND ("header"|"block"|"transaction"|"state")ErrEndpointMissingData−32014yesyes
15csame outer, neither 15a nor 15bErrEndpointClientSideException−32600yesyes
16status 415/405, code −32601/−32004/−32001, "Unsupported method", "not supported", "method is not whitelisted" — must follow "not found" rulesErrEndpointUnsupported−32601noyes
17Malformed tx: "rlp: expected input list", "typed transaction too short", "invalid transaction"ErrEndpointClientSideException−32602yesno
18a"tx of type", Envio type errorsErrEndpointClientSideException−32601yesyes
18bcode == −32600 AND data contains "validation errors in batch"ErrEndpointClientSideException−32000yesyes
18ccode == −32602 or −32600 (generic)ErrEndpointClientSideException−32602yesyes
18d"param is required", "Invalid Request", "invalid argument", "invalid params"ErrEndpointClientSideException−32602yesno
19status 401/403, "invalid api key", "key is inactive", "unauthorized"ErrEndpointUnauthorized−32016noyes
20Fallback (everything else)ErrEndpointServerSideExceptionpassthrough vendor codeyesyes
21200-OK result begins with 0x08c379a0 at dt[1:11] (index 0 is JSON quote)ErrEndpointExecutionException3nono
22trace_*/debug_*/eth_trace* AND raw body contains "execution timeout" (string scan, not JSON)ErrEndpointServerSideException−32015yesyes

gRPC/BDS normalization rules

Entry point: common/grpc_errors.go:L12. BDS error codes checked first, then gRPC codes.

TriggerNormalized errorJSON-RPCUN
BDS UNSUPPORTED_BLOCK_TAG, UNSUPPORTED_METHODErrEndpointUnsupported−32601noyes
BDS RANGE_OUTSIDE_AVAILABLEErrEndpointMissingData−32014yesyes
BDS INVALID_PARAMETER, INVALID_REQUESTErrEndpointClientSideException−32602yesno
BDS RATE_LIMITEDErrEndpointCapacityExceeded−32005noyes
BDS TIMEOUT_ERRORErrEndpointRequestTimeout−32015yesyes
BDS RANGE_TOO_LARGEErrEndpointRequestTooLarge (evm_block_range)−32012noyes
BDS INTERNAL_ERRORErrEndpointServerSideException−32603yesyes
gRPC CanceledErrEndpointRequestCanceledyesyes
gRPC UnimplementedErrEndpointUnsupported−32601noyes
gRPC InvalidArgumentErrEndpointClientSideException−32602yesno
gRPC ResourceExhaustedErrEndpointCapacityExceeded−32005noyes
gRPC DeadlineExceededErrEndpointRequestTimeout−32015yesyes
gRPC Unauthenticated, PermissionDeniedErrEndpointUnauthorized−32016noyes
gRPC NotFound, OutOfRangeErrEndpointMissingData−32014yesyes
gRPC Internal, Unknown, Unavailable, defaultErrEndpointServerSideException−32603yesyes

IsNonRetryableWriteMethod — write-method guard

architecture/evm/util.go:L7-L17 (opens in a new tab). Returns true for methods that MUST NOT be retried or hedged because they mutate state non-idempotently:

eth_sendTransaction
eth_createAccessList
eth_submitTransaction
eth_submitWork
eth_newFilter
eth_newBlockFilter
eth_newPendingTransactionFilter

eth_sendRawTransaction is deliberately excluded. The comment explains: it supports idempotency handling (the ErrEndpointNonceException already_known/nonce_too_low path lets eRPC detect and safely retry/hedge sendRawTransaction without double-submission risk).

Call sites — used in two places in upstream/upstream_executor.go (opens in a new tab):

  1. Retry guard (L243): if the method is a non-retryable write, isRetryableTowardsUpstream returns false even if the error would otherwise qualify.
  2. Hedge guard (L281): if the method is a non-retryable write, the hedge path is bypassed entirely.

Interaction with ErrEndpointExecutionException N-flag override. For eth_sendRawTransaction, the normalizer overrides retryableTowardNetwork=true for revert/out-of-gas results (rules 9 and 14). This is safe precisely because eth_sendRawTransaction is NOT in IsNonRetryableWriteMethod — the two predicates are complementary.

ErrUpstreamsExhausted.SummarizeCauses() — bucket categories

common/errors.go:L955-L1101 (opens in a new tab). Iterates joined children and buckets into 19 categories. Category labels appended to error message as "(N upstream X, M upstream Y)":

Category labelCodes detected
unsupportedErrCodeEndpointUnsupported
missingErrCodeEndpointMissingData
rateLimitErrCodeEndpointCapacityExceeded, ErrCodeUpstreamRateLimitRuleExceeded
billingErrCodeEndpointBillingIssue
cbOpenErrCodeFailsafeCircuitBreakerOpen
timeoutcontext.DeadlineExceeded, ErrCodeEndpointRequestTimeout, ErrCodeNetworkRequestTimeout, ErrCodeFailsafeTimeoutExceeded
serverErrorErrCodeEndpointServerSideException
cancelledErrCodeUpstreamHedgeCancelled
clientErrCodeEndpointClientSideException, ErrCodeJsonRpcRequestUnmarshal, ErrCodeInvalidRequest, ErrCodeInvalidUrlPath
transportErrCodeEndpointTransportFailure
unsyncedErrCodeUpstreamSyncing, ErrCodeUpstreamBlockUnavailable
excludedErrCodeUpstreamExcludedByPolicy
nodeTypeMismatchErrCodeUpstreamNodeTypeMismatch
ignoresErrCodeUpstreamMethodIgnored
skipsErrCodeUpstreamRequestSkipped
tooLargeErrCodeEndpointRequestTooLarge, ErrCodeGetLogsExceededMaxAllowedRange, ErrCodeGetLogsExceededMaxAllowedAddresses, ErrCodeGetLogsExceededMaxAllowedTopics
authErrCodeEndpointUnauthorized
validationErrCodeEndpointContentValidation
otheranything not matched above

TranslateToJsonRpcException — ordered translation steps

Entry: common/json_rpc.go:L1501 (opens in a new tab). Steps applied in order — first match wins:

StepInput conditionOutput code / behavior
1ErrUpstreamsExhaustedScan children for dominant code (highest count; ErrCodeUpstreamRequestSkipped and ErrCodeEndpointUnsupported excluded; earliest per code wins ties). Replace with dominant child. When NO children exist, translate ErrUpstreamsExhausted itself (→ −32603). When all children are skipped/unsupported, client sees the first child's code (typically −32601 or −32603).
2Chain already contains ErrCodeJsonRpcExceptionInternalReturn as-is (upstream-derived normalized code preserved).
3ErrCodeAuthRateLimitRuleExceeded / ErrCodeProjectRateLimitRuleExceeded / ErrCodeNetworkRateLimitRuleExceeded / ErrCodeUpstreamRateLimitRuleExceeded−32005 CapacityExceeded, message "rate-limit exceeded"
4ErrCodeAuthUnauthorized−32016 Unauthorized, message "unauthorized"
5ErrCodeUpstreamMethodIgnored−32601 UnsupportedException, message "method ignored by upstream: <deepest msg>"
6ErrCodeJsonRpcRequestUnmarshal−32700 ParseException, message "failed to parse json-rpc request"
7ErrCodeInvalidRequest / ErrCodeInvalidUrlPath−32602 InvalidArgument, message "invalid request url and/or body"
8ErrCodeGetLogsExceededMaxAllowedRange / ...Addresses / ...Topics−32012 EvmLargeRange, message "getLogs request exceeded max allowed range"
9Fallback (anything else)−32603 ServerSideException with deepest message

Helper predicates

PredicateReturns false / behaviorSource
HasErrorCode(err, codes...)Traverses StandardError cause chain AND joined multi-errors; a non-retryable code anywhere poisons the whole chaincommon/errors.go:L2333-L2359 (opens in a new tab)
IsCapacityIssue(err)Returns true for: ErrCodeProjectRateLimitRuleExceeded, ErrCodeNetworkRateLimitRuleExceeded, ErrCodeUpstreamRateLimitRuleExceeded, ErrCodeAuthRateLimitRuleExceeded, ErrCodeEndpointCapacityExceededcommon/errors.go:L2500-L2509 (opens in a new tab)
IsClientError(err)Returns true for: ErrCodeEndpointClientSideException, ErrCodeJsonRpcRequestUnmarshal, ErrCodeGetLogsExceededMaxAllowedRange, ErrCodeGetLogsExceededMaxAllowedAddresses, ErrCodeGetLogsExceededMaxAllowedTopicscommon/errors.go:L2511-L2520 (opens in a new tab)
IsClientDisconnect(err)Returns true for context.Canceled, context.DeadlineExceeded, or error message containing: "use of closed network connection", "broken pipe", "connection reset by peer", "ECONNRESET", "EPIPE"common/errors.go:L129-L150 (opens in a new tab)
IsRetryableTowardsUpstream(err)Returns false if HasErrorCode finds any of: ErrCodeFailsafeCircuitBreakerOpen, ErrCodeUpstreamRequestSkipped, ErrCodeUpstreamMethodIgnored, ErrCodeEndpointUnsupported, ErrCodeEndpointBillingIssue, ErrCodeJsonRpcRequestUnmarshal, ErrCodeEndpointExecutionException, ErrCodeEndpointUnauthorized, ErrCodeEndpointRequestTooLarge, ErrCodeEndpointContentValidation, or any IsCapacityIssue code. For ErrUpstreamsExhausted recurses over children — retryable iff ANY child is retryable.common/errors.go:L2438-L2498 (opens in a new tab)

Extractor plumbing

ComponentRoleSource
JsonRpcErrorExtractor interfaceInjects arch-specific normalization into HTTP clients; avoids import cyclescommon/error_extractor.go:L10-L12 (opens in a new tab)
JsonRpcErrorExtractorFunchttp.HandlerFunc-style adaptercommon/error_extractor.go:L17-L21 (opens in a new tab)
evm.JsonRpcErrorExtractorEVM impl delegating to ExtractJsonRpcErrorarchitecture/evm/extractor.go:L11-L17 (opens in a new tab)
Injection siteevm.NewJsonRpcErrorExtractor() passed to client registryupstream/registry.go:L79 (opens in a new tab)
HTTP invocationc.errorExtractor.Extract(r, nr, jr, c.upstream)clients/http_json_rpc_client.go:L930 (opens in a new tab)
gRPC invocationcommon.ExtractGrpcErrorFromGrpcStatus(st, c.upstream)clients/grpc_bds_client.go:L925 (opens in a new tab)
Vendor hookgetVendorSpecificErrorIfAnyvn.GetVendorSpecificErrorIfAny(...) fires BEFORE all generic rulesarchitecture/evm/error_normalizer.go:L878-L900 (opens in a new tab)

Worked examples

1. Diagnosing why a request exhausted all upstreams. You see erpc_network_failed_request_total spike with error="ErrUpstreamsExhausted/ErrEndpointRequestTimeout". The compound label (compact mode) tells you the inner code. Check erpc_upstream_request_errors_total filtered by error containing Timeout to see which upstream timed out most. Switch metrics.errorLabelMode to "verbose" temporarily in a staging environment to read the full message — never in production.

2. eth_sendRawTransaction returning −32003 but the tx went through. The normalizer sets N=yes for reverts and out-of-gas on eth_sendRawTransaction (rules 9 and 14). eRPC retried on a different upstream. The first upstream returned ErrEndpointExecutionException with code 3 (revert), which is kept and surfaced to the caller — this is expected behavior. Insufficient-funds stays N=no even for sendRawTransaction; no retry occurs in that case.

3. All upstreams returning ErrEndpointMissingData — request retried despite "exhausted". ErrUpstreamsExhausted wrapping only missing-data children is still N=true (any retryable child makes the whole error retryable). Outer retry logic will try again. If data is genuinely absent from all nodes (e.g. pruned historical state), retry will keep failing — configure a dedicated archive upstream or catch −32014 in your client.

4. Alert fires on severity="warning" for hedge discards. ErrUpstreamHedgeCancelled is severity=warning. This is normal — it means a sibling hedge leg won the race. Alert only on severity="critical" in production; warning includes hedge discards and client disconnects that are expected at scale.

Best practices

  • Alert on severity="critical" only — severity="info" covers client errors and EVM reverts; severity="warning" covers hedge discards and client disconnects.
  • Keep metrics.errorLabelMode at "compact" in production. "verbose" creates a new Prometheus time series per unique error message and will exhaust memory.
  • When ErrUpstreamsExhausted appears in logs, check SummarizeCauses bucket in the message (e.g. "2 upstream timeout, 1 upstream rateLimit") to identify the dominant failure mode before tuning retry or circuit-breaker config.
  • ErrEndpointTransportFailure strips endpoint URLs from cause messages — if you need the raw URL for debugging, check the upstream config or traces, not the error log.
  • ErrUpstreamClientInitialization is not fatal by default. If you see it at startup for an upstream that should have resolved (chain-ID mismatch, wrong type), check whether callers wrap it in TaskFatalError for permanent failures.
  • ErrUpstreamRateLimitRuleExceeded returns HTTP 200, not 429, on the wire. If your client is polling on 429 to detect upstream budget exhaustion, it will miss this case — filter on JSON-RPC code −32005 instead.
  • For eth_sendRawTransaction, a −32003 response may be a successful idempotent retry (nonce-duplicate or out-of-gas handled by normalizer rule 11/14). Check the reason field in error details (already_known vs nonce_too_low) to distinguish.

Edge cases & gotchas

  1. ErrUpstreamsExhausted with NO cause is terminal (N=false). An exhausted error wrapping zero children means no upstream was tried; retrying at network scope cannot make progress. Source: common/errors.go:L2393-L2394 (opens in a new tab)
  2. All-missing-data exhausted IS retryable. ErrUpstreamsExhausted wrapping only ErrEndpointMissingData children is N=true because any retryable child makes the whole exhausted error retryable. Source: common/errors_retry_test.go:L51-L74 (opens in a new tab)
  3. ErrEndpointExecutionException is N=false — except eth_sendRawTransaction reverts. The normalizer overrides N=yes for eth_sendRawTransaction reverts and out-of-gas (rules 9 and 14). Insufficient-funds (rule 13) does NOT get the override. Source: architecture/evm/error_normalizer.go:L263-L272 (opens in a new tab)
  4. ErrEndpointClientSideException is N=true by default. Only rules 17 (malformed tx), 18d (invalid params), and gRPC/BDS InvalidArgument set N=false. Rule 6 (block-tag-unsupported) deliberately keeps N=true to allow other upstreams to attempt the tag. Source: common/errors_retry_test.go:L175-L187 (opens in a new tab)
  5. ErrUpstreamRateLimitRuleExceeded returns 200 on the wire. It is NOT in the wire 429 switch despite its ErrorStatusCode() returning 429. Source: erpc/http_server.go:L1484-L1490 (opens in a new tab)
  6. OP-stack "sender is over rate limit" is capacity-exceeded but N=false. All eRPC provider entries proxy the same OP sequencer; retrying on a different provider hits the same limit. All other ErrEndpointCapacityExceeded are N=true. Source: architecture/evm/error_normalizer.go:L125-L139 (opens in a new tab)
  7. Nonce-duplicate detection must run before generic −32003. Some vendors use −32003 for "already known"/"nonce too low". Rules 11/12 appear before rule 14 in the normalizer. "Replacement transaction underpriced" deliberately stays a plain error. Source: architecture/evm/error_normalizer.go:L297-L301 (opens in a new tab)
  8. 200-OK revert detection checks dt[1:11]. Index 0 is the JSON quote character; the ABI selector 0x08c379a0 starts at index 1. Source: architecture/evm/error_normalizer.go:L609-L624 (opens in a new tab)
  9. Trace/debug timeout detection scans raw bytes. Rule 22 does a raw string search to avoid parsing up to ~50MB trace responses. Source: architecture/evm/error_normalizer.go:L626-L629 (opens in a new tab)
  10. buildErrorResponseBody unwraps single-child exhausted. When exactly one upstream tried, the wrapper is removed and clients see the real upstream error. Source: erpc/http_server.go:L1397-L1403 (opens in a new tab)
  11. Compact ErrorSummary compound labels are not universal. Only ErrFailsafeRetryExceeded, ErrUpstreamRequest, and ErrUpstreamRequestSkipped produce OuterCode/InnerCode labels. ErrFailsafeTimeoutExceeded does NOT compound even with a cause. Source: common/errors_summary_test.go:L24-L30 (opens in a new tab)
  12. ErrDynamicTimeoutExceeded distinguishes failsafe from HTTP deadline. Set as context.WithTimeoutCause cause by the timeout policy. context.Cause(ctx) == ErrDynamicTimeoutExceeded tells you the failsafe policy fired, not the HTTP server's global deadline. Source: common/errors.go:L1981-L1984 (opens in a new tab)
  13. TranslateToJsonRpcException skips UpstreamRequestSkipped and EndpointUnsupported when counting dominant error. When all upstreams returned one of these codes, the first child's wire code is used (typically −32601 or −32603). Source: common/json_rpc.go:L1523-L1526 (opens in a new tab)
  14. BDS gRPC legacy extractor diverges from live path. evm.ExtractGrpcError (no call sites) maps TIMEOUT_ERROR/DeadlineExceeded to ErrEndpointServerSideException; live common.ExtractGrpcErrorFromGrpcStatus maps both to ErrEndpointRequestTimeout. If the legacy function is ever restored, BDS timeout retry behavior will regress.
  15. ErrConsensusDispute and ErrConsensusLowParticipants retryability follows the any-child rule. Both aggregate per-upstream participant errors via errors.Join. If ANY single child is retryable, the consensus error is retryable network-wide. Source: common/errors.go:L2397-L2411 (opens in a new tab)
  16. ErrEndpointClientSideException.ErrorStatusCode() returns 200 for revert-class causes. When the inner ErrJsonRpcExceptionInternal has normalizedCode of 3 (JsonRpcErrorEvmReverted), -32000 (JsonRpcErrorCallException), or -32003 (JsonRpcErrorTransactionRejected), the method returns 200 rather than 400 — an EVM revert is a valid execution outcome. Note that ErrorStatusCode() is dead code (zero call sites), but the wire behavior is consistent: determineResponseStatusCode/handleErrorResponse always return 200 for these cases via the default switch branch. Source: common/errors.go:L1894-L1905 (opens in a new tab)
  17. Legacy evm.ExtractGrpcError (dead code) has no Canceled branch. The live common.ExtractGrpcErrorFromGrpcStatus maps gRPC Canceled to ErrEndpointRequestCanceled; the dead legacy function falls through to the default ErrEndpointServerSideException case. Additionally, the legacy function maps both BDS TIMEOUT_ERROR and gRPC DeadlineExceeded to ErrEndpointServerSideException (live path maps both to ErrEndpointRequestTimeout). If the legacy function is ever restored, BDS/gRPC timeout and cancellation retry behavior will regress. Source: architecture/evm/error_normalizer.go:L660-L876 (opens in a new tab) vs common/grpc_errors.go:L86-L97 (opens in a new tab)
  18. buildErrorResponseBody sets error.data only under two conditions. The data field is included only when includeErrorDetails is true AND the method is NOT eth_call. For eth_call, the revert data is intentionally suppressed in the top-level error body. Source: erpc/http_server.go:L1425-L1433 (opens in a new tab)
  19. ErrJsonRpcExceptionInternal prefixes its CodeChain() with the numeric normalized code. The overridden CodeChain() produces "<normalizedCode> &lt;- ErrJsonRpcExceptionInternal" (e.g. "-32014 <- ErrJsonRpcExceptionInternal"). NormalizedCode() returns details["normalizedCode"] as a JsonRpcErrorNumber; OriginalCode() handles both int and float64 types (JSON round-trip issue). Source: common/errors.go:L2202-L2222 (opens in a new tab)

Observability

MetricTypeLabelsWhen it fires
erpc_upstream_request_errors_totalcounterproject, vendor, network, upstream, category, error, severity, composite, finality, user, agent_nameEach upstream attempt returning an error; error label = ErrorFingerprint(err)
erpc_network_failed_request_totalcounterproject, network, category, attempt, error, severity, finality, user, agent_nameRequest failed at network/project level
erpc_cache_set_error_totalcounterproject, network, category, connector, policy, ttl, errorCache set errored
erpc_cache_get_error_totalcounterproject, network, category, connector, policy, ttl, errorCache get errored
erpc_unexpected_panic_totalcounterscope, extra, errorRecovered panic

Alert on severity="critical" only. severity="info" covers client errors and EVM reverts; severity="warning" covers hedge discards and client disconnects.

Tracing. common.ErrorSummary(err) is used as the OTel span status description on error. The error.summary span attribute is set in failsafe operations. Cache errors set cache.error / cache.connector_error span attributes. gRPC send errors set a grpc.send_error attribute. Source: common/tracing_core.go:L181 (opens in a new tab), data/failsafe.go:L240 (opens in a new tab)

Source code entry points

Related pages

  • Retry — retryability flags in this table drive retry decisions.
  • Circuit breakerErrFailsafeCircuitBreakerOpen and capacity errors feed circuit scoring.
  • HedgeErrUpstreamHedgeCancelled is the discard signal for losing hedge legs.
  • Rate limitersErrUpstreamRateLimitRuleExceeded / ErrEndpointCapacityExceeded are the rate-limit error types.
  • AuthErrAuthUnauthorized / ErrAuthRateLimitRuleExceeded are auth-layer errors.