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
- 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}'
- Confirm the returned chain ID (hex) equals the bridge's configured source_chain_id
- Fix scheme/host/auth issues and verify validators (not just your laptop) can reach the endpoint
- 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
- Run eth_chainId preflights in the deploy pipeline, not just manually
- Use paid or self-hosted RPC tiers for validators to avoid rate limits
- Monitor RPC availability continuously; instantiate with an empty endpoint only as a deliberate, documented exception
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
- SetRpcEndpoint requires an authenticated signer
- failed to check block finality — block may not exist
- RegisterFungibleBridge requires an authenticated signer
- invalid block header RLP
- receipt inclusion proof failed
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/fffe178270f5b8a2.
Report an issue: GitHub.