Skip to content
Magma Devs
Back to blog
Security·August 9, 2026·9 min read

RPC Cross-Validation: How Quorum Stops Bad RPC Data

Your application treats whatever the RPC endpoint returns as fact. Cross-validation sends critical reads to several independent providers and returns a result only on quorum, so a single poisoned or stale node gets outvoted instead of believed.

By Magma Team

RPC Cross-Validation: How Quorum Stops Bad RPC Data

Your application treats whatever the RPC endpoint returns as fact. Cross-validation is the layer that checks.

Every blockchain application carries one assumption it almost never states out loud: that the answer coming back from an RPC endpoint is true, whether that answer is a balance, a log, a contract read, or a transaction receipt. The chain is the source of truth, but your application never talks to the chain. It talks to a node, over HTTP, and acts on what that node says. RPC cross-validation removes that assumption by sending critical requests to several independent providers at once and returning a result only when a quorum of them agree.

That sounds like belt-and-braces engineering until you look at what happens when the assumption breaks. On April 18, 2026, attackers drained 116,500 rsETH, roughly $292M, from KelpDAO. No smart contract bug was involved. The attackers poisoned the RPC nodes that LayerZero's verifier network relied on, DDoSed the clean nodes to force failover onto the compromised ones, and had fabricated cross-chain messages approved as genuine. The contracts did exactly what they were written to do, on data that was wrong. We covered the full sequence in our teardown of the KelpDAO exploit.

Why the RPC layer is the target

Attacking a smart contract means finding a flaw in audited, public, immutable code that a lot of people have stared at, while attacking the RPC layer means influencing a single HTTP response.

The RPC layer is attractive for three structural reasons. It sits outside the audit perimeter: your auditors reviewed your contracts, and your penetration testers reviewed your application, and neither of them reviewed the third-party endpoint your application believes. It is concentrated: most teams route the majority of their traffic through one or two providers, so one compromised or misbehaving upstream reaches a large share of requests. And it is invisible in the usual monitoring, because a poisoned response is a well-formed HTTP 200 that arrives quickly, so liveness probes pass and latency graphs stay flat. We have written before about why availability alone is not a security property; poisoning is the sharpest version of that argument.

Bad data does not require an attacker, either. Nodes lag behind the chain head and serve stale state, nodes on either side of a reorg return different views of the same block, and disagreement on eth_getLogs ordering is routine. The failure mode is the same in every case: your application acts on an answer that no second source would have confirmed.

How RPC cross-validation works

The mechanism is quorum. Instead of sending a request to one selected upstream, the router fans it out to several independent upstreams in parallel and compares the responses.

Two parameters control it in Smart Router:

  • MaxParticipants: how many nodes to query in parallel.
  • AgreementThreshold: how many of them must return matching responses before the result is accepted.

Both default to 1, which means cross-validation is off until you turn it on. Set MaxParticipants: 3 and AgreementThreshold: 2 and the request goes to three upstreams; as soon as two return matching responses, that response is returned to your application. Comparison is shape-aware rather than byte-exact, so formatting differences between healthy nodes do not register as disagreement. The third response is no longer needed, so the request is not gated on the slowest node in the set. If the nodes disagree past the timeout window, the relay either fails with a reason or returns the most-agreed answer, depending on how you configure it.

The important property is what a single compromised endpoint can now do. Under a 2-of-3 policy it can return anything it likes and change nothing, because its answer never becomes the majority. To move your application's view of the chain, an attacker has to compromise two independent providers at the same time and make them return matching forged data for the same request, which is a substantially harder operation than poisoning one node and waiting for it to be selected.

Group diversity, so quorum means what you think

Three upstreams that resolve to the same underlying infrastructure share a single failure domain, so quorum across them does not give you the independence it appears to.

Two configuration fields close that gap. min-groups requires that the agreeing nodes come from at least that many distinct provider groups, which you label yourself. per-group-quorum goes further and requires each of those groups to independently reach the agreement threshold. Label your upstreams by what actually correlates their failures, for example by vendor, by cloud region, or by "external provider" against "our own nodes", and the quorum starts carrying real information.

This is where running your own nodes alongside external providers pays off. A policy that requires agreement between an external provider and an on-prem node you operate is difficult to defeat from outside your perimeter.

Operator policy, not client discretion

Clients can request cross-validation per request through headers, and the router reports the verdict back in response headers: whether validation passed, how many providers agreed, and on disagreement, the reason. That is useful for testing, and for application code that knows which of its own calls are sensitive.

For anything that matters, set it as operator policy in the config file, with floors and caps per chain and per method:

cross-validation:
  policies:
    - chain-id: "ETH1"
      api-interface: "jsonrpc"
      method: "eth_getLogs"
      enabled: true
      agreement-threshold: { floor: 2 }
      max-participants: { floor: 3, cap: 5 }

Clients can raise participation above the floor but cannot go below it, so validation policy belongs to whoever owns the infrastructure rather than to whichever service last shipped a config change. Those floors are also what you hand an auditor when they ask how critical reads are protected. The full option set lives in the cross-validation documentation.

What to validate, and what not to

Validation is a policy applied selectively, for two reasons. Some methods are not deterministic across nodes, so demanding agreement on them produces noise instead of signal, and every validated request multiplies upstream cost. The docs give a working split:

Request typeCross-validate
Critical writes (transfers, contract calls)Yes
eth_getLogs resultsYes, nodes commonly disagree on log ordering
eth_call at a fixed blockYes, deterministic and directly comparable
debug_* tracesYes where trust in the upstream is low
eth_call on the pending blockNo, non-deterministic by construction
eth_blockNumber and latest-block readsNo, nodes naturally disagree by a block

The trade-offs are explicit rather than hidden. A request with MaxParticipants: 3 costs three upstream calls instead of one, and the response is gated on the slowest of the agreeing nodes. That is why cross-validation is a policy applied to the requests that move money or drive decisions, paired with block-aware caching and hedging to keep the cost and the tail latency in range. Finalized reads served from cache never hit the upstreams at all.

How cross-validation differs from integrity checks and failover

Three mechanisms get conflated, and they catch different failures.

Failover answers "did the node respond". It retries, hedges, and routes around upstreams that error or time out. It assumes the responses it does get are correct.

Integrity checks answer "is this node current". Before a node is selected, its recent block height is compared against the freshest height seen across the pool, and nodes lagging past a per-chain threshold are skipped. This catches staleness cheaply, on every request. It does not catch wrong data when the node is perfectly in sync. The thresholds are documented in the integrity check reference.

Cross-validation answers "is this response corroborated". It is the only one of the three that can catch a node that is live, current, fast, and lying.

Run all three. Integrity and failover carry the ordinary traffic at ordinary cost, and cross-validation covers the requests where being wrong is expensive.

Isolating the endpoint that disagreed

Catching bad data matters less than knowing which upstream produced it, so every validated request records its verdict. smartrouter_cross_validation_provider_agreements_total and smartrouter_cross_validation_provider_disagreements_total carry a provider_address label, smartrouter_cross_validation_failures_total explains requests that never reached quorum by reason (no-agreement, diversity-unmet, group-quorum-unmet, and others), and smartrouter_cross_validation_mismatch_total counts outlier response groups with a finality label.

That finality label is what makes the signal actionable. Disagreement about a recent block can be an honest race between nodes on either side of a reorg, but disagreement about finalized data cannot be. Once a block is final, every honest node returns identical data for it, so a post-finality mismatch is strong evidence that a specific provider served wrong data about settled chain state. Alert on it at the highest severity you have, identify the provider through the disagreement counter, and treat it as untrusted until you understand the cause. Our guide to RPC monitoring metrics covers the full series and the thresholds.

The same records are exportable evidence. For regulated institutions, being able to show that critical reads were corroborated across independent providers, and that disagreements were logged and acted on, supports the resilience and third-party risk obligations your auditors are asking about.

Smart Router runs this layer on top of the providers you already use, with no migration and no change to your provider contracts. It is open source, so you can read exactly how quorum is evaluated before you trust it with anything. If you want to walk through a validation policy for your own methods and chains, book a demo or start with the RPC security overview.

How exposed is your RPC stack?

Take the 2-minute Secure RPC Assessment and get a personalized risk report.

Run the assessment

Frequently Asked Questions

What is RPC cross-validation?
RPC cross-validation sends the same request to several independent RPC providers in parallel and returns a response to your application only when a configured number of them return matching results. A single provider that returns stale, buggy, or deliberately falsified data is outvoted rather than believed, and the disagreement is recorded against that provider.
How does cross-validation prevent RPC data poisoning?
Data poisoning works by getting one endpoint your application trusts to return a false answer. Quorum removes that single point of trust: under a 2-of-3 policy, an attacker has to compromise two independent providers at once and make them return identical forged data for the same request. Group diversity rules raise the bar further by requiring the agreeing nodes to come from genuinely separate infrastructure, such as an external provider and a node you run yourself.
Does cross-validation slow down my application?
It costs more upstream calls and gates the response on the slowest of the agreeing nodes, so it is applied as a policy to specific methods rather than to all traffic. Reads that are deterministic and consequential get validated; latest-block and pending-state reads do not, because nodes legitimately differ on those. Pairing validation with block-aware caching and hedging keeps the added cost and tail latency contained.