linera-io/linera-protocol · error

failed to query chain ID from RPC endpoint

Error message

failed to query chain ID from RPC endpoint

What it means

validate_rpc_endpoint, run during instantiate and SetRpcEndpoint, dials the configured Ethereum RPC endpoint from inside the contract and expects eth_chainId to succeed. Any transport-level failure — DNS, TCP/TLS, HTTP error status, JSON-RPC error, timeout, rate limit — panics and aborts the transaction, leaving the previously stored endpoint unchanged. This is fail-closed by design: a live bridge cannot be repointed at a dead or wrong-chain endpoint.

Source

Thrown at linera-bridge/contracts/evm-bridge/src/contract.rs:154

impl EvmBridge {
    /// Validates a configured RPC endpoint. An empty endpoint is accepted and
    /// disables finality verification. A non-empty endpoint must be reachable
    /// and report the configured `source_chain_id`; otherwise this panics,
    /// aborting the calling operation (`instantiate` / `SetRpcEndpoint`) and
    /// leaving the previously stored endpoint unchanged. Shared by both so a
    /// live bridge cannot be repointed at a wrong-chain endpoint without the
    /// same check applied at genesis.
    async fn validate_rpc_endpoint(&mut self, rpc_endpoint: &str) {
        if rpc_endpoint.is_empty() {
            return;
        }
        let source_chain_id = self.runtime.application_parameters().source_chain_id;
        let client = ContractEthereumClient::new(rpc_endpoint.to_string());
        let chain_id = client
            .get_chain_id()
            .await
            .expect("failed to query chain ID from RPC endpoint");
        assert_eq!(
            chain_id, source_chain_id,
            "RPC endpoint chain ID {chain_id} does not match configured source_chain_id {source_chain_id}"
        );
    }

    async fn verify_block_hash(&mut self, block_hash: [u8; 32]) {
        let rpc_endpoint = self.state.rpc_endpoint.get();
        assert!(
            !rpc_endpoint.is_empty(),
            "rpc_endpoint must be configured to verify block hashes"
        );

        // Use our own service as an oracle so that the underlying EVM JSON-RPC
        // calls (two of them) collapse into one deterministic boolean in the
        // block's oracle responses.
        let application_id = self.runtime.application_id();
        let hash_hex = hex::encode(block_hash);

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Pre-flight the endpoint from the deploying machine: curl -s $RPC -X POST -H 'content-type: application/json' -d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}'
  2. Confirm the returned chain ID (hex) equals the bridge's configured source_chain_id
  3. Fix scheme/host/auth issues and verify validators (not just your laptop) can reach the endpoint
  4. If connectivity cannot be proven yet, instantiate with an empty endpoint (disables finality verification) and set it later via SetRpcEndpoint

Example fix

# before
linera publish-and-create ... --application-parameters '{"rpc_endpoint":"https://mainnet.infura.io/v3/BADKEY", ...}'
# -> transaction aborts: failed to query chain ID from RPC endpoint

# after
RPC=https://mainnet.infura.io/v3/GOODKEY
curl -s $RPC -X POST -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}'
# expect {"jsonrpc":"2.0","id":1,"result":"0x1"} matching source_chain_id,
# then submit with the verified endpoint.
Defensive patterns

Strategy: validation

Validate before calling

// Validate the endpoint before instantiate / SetRpcEndpoint:
async fn endpoint_ok(url: &str, expected_chain_id: u64) -> bool {
    let body = r#"{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}"#;
    let resp = reqwest::Client::new().post(url).header("content-type", "application/json")
        .body(body).send().await.ok()?;
    let v: serde_json::Value = resp.json().await.ok()?;
    v["result"].as_str().and_then(|s| u64::from_str_radix(s.trim_start_matches("0x"), 16).ok())
        == Some(expected_chain_id)
}

Try / catch

// The transaction aborts on unreachable endpoints; retry with backoff at the
// orchestration layer after re-validating the endpoint off-chain:
for attempt in 0..5 {
    if endpoint_ok(&url, source_chain_id).await { break; }
    tokio::time::sleep(Duration::from_secs(2u64 << attempt)).await;
}

Prevention

When it happens

Trigger: Instantiating the bridge or calling SetRpcEndpoint with a typo'd URL, an endpoint unreachable from validators, a provider that rate-limits (429) or returns 5xx, an endpoint requiring auth headers, or an HTTPS endpoint with a certificate the validator environment rejects; also a JSON-RPC proxy that does not implement eth_chainId.

Common situations: Local dev pointing at an anvil/hardhat node that has been stopped; production pointing at an Infura/Alchemy URL with an expired key or exhausted quota; network policies that let the deploy laptop reach the RPC but not the validator hosts.

Related errors


AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22). Data as JSON: /api/errors/fffe178270f5b8a2. Report an issue: GitHub.