Features Catalog Pricing Blog Docs
Get Started
comparison debugging

When providers disagree: Debugging RPC divergence

OnchainProbe Team ·

You have two RPC providers. You send the same eth_getTransactionReceipt call to both. One returns effectiveGasPrice as a hex string. The other returns it as a decimal number. Your application handles one format but not the other. Welcome to RPC divergence.

It’s one of the most common issues in multi-provider setups. Different EVM clients (geth, nethermind, erigon, besu) implement the same JSON-RPC specification, but the spec has enough ambiguity that responses can differ in subtle, application-breaking ways.

Why clients diverge

The Ethereum JSON-RPC specification defines what each method should return, but not always how. This leaves room for implementation differences in several areas:

Encoding ambiguity

The spec says quantities should be hex-encoded. But what about:

  • Leading zeros. Is 0x01 the same as 0x1? Both are valid hex, but string comparison says they’re different.
  • Null vs. absent. When a field has no value, should it be null or omitted entirely?
  • Empty data. Is empty bytes represented as 0x or 0x0?

Each client makes its own choice, and each choice is arguably correct per the spec.

Field presence

Different client versions include different optional fields:

  • Geth added blobGasUsed to receipts before other clients
  • Nethermind includes type in all transaction responses; some clients omit it for legacy transactions
  • Erigon’s trace output has a different structure than geth’s debug_traceTransaction

Behavioral differences

Beyond formatting, clients can disagree on actual values:

  • eth_estimateGas. Different estimation algorithms produce different results for the same call. One client might return 21,500 gas while another returns 22,000. Both are valid estimates.
  • eth_call error messages. When a call reverts, the error message format varies by client. Some return the revert reason in the data field, others in message.
  • Pending state. eth_getTransactionCount with "pending" can return different values depending on how each client’s mempool handles ordering.

A systematic debugging approach

When you encounter a divergence, resist the urge to immediately patch your application. First, understand what’s actually different and why.

Step 1: Isolate the exact divergence

Send the identical request to both providers and capture the full responses. Don’t just compare the field you think is different. Compare everything.

// Request
{
  "method": "eth_getTransactionReceipt",
  "params": ["0xabc..."],
  "id": 1
}

Diff the complete responses field by field. You might find that the field you noticed is just one of several differences.

Step 2: Classify the difference

Is it a formatting difference or a value difference?

  • Formatting: Same data, different representation (0x1 vs 0x01). Fix in your parser.
  • Value: Different data for the same query. This needs deeper investigation.

Formatting differences are annoying but harmless. Normalize on ingest. Value differences indicate that one provider might be wrong, or that the behavior is genuinely client-dependent.

Step 3: Check the spec

For value differences, check the Ethereum JSON-RPC specification and relevant EIPs. Common findings:

  • The spec doesn’t define the field → Client-specific extension. Neither is “wrong.”
  • The spec defines the field but not the edge case → Implementation discretion. Both may be valid.
  • The spec is clear and one client doesn’t match → Bug. File an issue.

Step 4: Test across multiple blocks

A divergence on one block doesn’t necessarily mean it happens everywhere. Test the same method across:

  • Recent blocks (last 100)
  • Older historical blocks
  • Blocks at fork boundaries
  • Blocks with unusual transactions (contract creations, self-destructs, large batch transactions)

This tells you whether the divergence is systematic or block-specific.

Step 5: Check client versions

Client behavior changes with versions. Verify:

  • Both providers are running the same client version (or document the versions)
  • The divergence isn’t fixed in a newer release
  • The divergence isn’t a known issue in the client’s bug tracker

Common divergences and how to handle them

Here are the divergences we see most frequently:

Gas estimation spread

Symptom: eth_estimateGas returns different values across clients.

Root cause: Each client uses a different binary search algorithm with different bounds and iterations.

Fix: Don’t rely on exact gas estimates. Apply a buffer (10-20%) and treat estimates as approximate. If precision matters, use eth_call to simulate and calculate gas programmatically.

Receipt field encoding

Symptom: Fields like effectiveGasPrice, cumulativeGasUsed, or status are encoded differently.

Root cause: Hex encoding ambiguity in the spec.

Fix: Normalize all quantity fields on ingest. Parse hex strings with parseInt(value, 16) rather than string comparison.

Missing fields in legacy transactions

Symptom: Pre-EIP-1559 transactions have maxFeePerGas and maxPriorityFeePerGas on one provider but not another.

Root cause: Some clients backfill EIP-1559 fields for legacy transactions (using gasPrice), others don’t.

Fix: Check for field presence before accessing. Don’t assume all transactions have the same shape.

Trace output structure

Symptom: debug_traceTransaction returns differently structured output.

Root cause: Tracing is not part of the core JSON-RPC spec. Each client implements its own tracing API.

Fix: Abstract trace parsing behind a client-specific adapter. If you need cross-client trace consistency, use a standardized tracer config (like callTracer or prestateTracer which have more consistent output).

Preventing divergence issues

The best approach is catching divergences before they reach production:

  1. Run the same test suite against all your providers. If you use geth and nethermind, run the same RPC calls against both and compare responses systematically.

  2. Test after every client upgrade. A new client version can introduce new divergences or fix existing ones.

  3. Monitor continuously. Divergences can appear when one provider upgrades and another doesn’t, or when chain state triggers client-specific behavior.

OnchainProbe’s cross-client comparison suites automate this. Point it at two or more endpoints, and it will run the same test calls against each, diff the responses field by field, and alert you when they disagree. You get a clear report showing exactly which fields diverge and by how much before your users find out.

The bigger picture

RPC divergence is a consequence of multiple independent teams implementing a specification that leaves room for interpretation. It’s not going away.

The pragmatic response is to build your infrastructure assuming providers will disagree, normalize aggressively on ingest, and continuously verify that your critical RPC methods return consistent results across your provider stack.