{"record":{"id":"fffe178270f5b8a2","repo":"linera-io/linera-protocol","slug":"failed-to-query-chain-id-from-rpc-endpoint","errorCode":null,"errorMessage":"failed to query chain ID from RPC endpoint","messagePattern":"failed to query chain ID from RPC endpoint","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"linera-bridge/contracts/evm-bridge/src/contract.rs","lineNumber":154,"sourceCode":"\nimpl EvmBridge {\n    /// Validates a configured RPC endpoint. An empty endpoint is accepted and\n    /// disables finality verification. A non-empty endpoint must be reachable\n    /// and report the configured `source_chain_id`; otherwise this panics,\n    /// aborting the calling operation (`instantiate` / `SetRpcEndpoint`) and\n    /// leaving the previously stored endpoint unchanged. Shared by both so a\n    /// live bridge cannot be repointed at a wrong-chain endpoint without the\n    /// same check applied at genesis.\n    async fn validate_rpc_endpoint(&mut self, rpc_endpoint: &str) {\n        if rpc_endpoint.is_empty() {\n            return;\n        }\n        let source_chain_id = self.runtime.application_parameters().source_chain_id;\n        let client = ContractEthereumClient::new(rpc_endpoint.to_string());\n        let chain_id = client\n            .get_chain_id()\n            .await\n            .expect(\"failed to query chain ID from RPC endpoint\");\n        assert_eq!(\n            chain_id, source_chain_id,\n            \"RPC endpoint chain ID {chain_id} does not match configured source_chain_id {source_chain_id}\"\n        );\n    }\n\n    async fn verify_block_hash(&mut self, block_hash: [u8; 32]) {\n        let rpc_endpoint = self.state.rpc_endpoint.get();\n        assert!(\n            !rpc_endpoint.is_empty(),\n            \"rpc_endpoint must be configured to verify block hashes\"\n        );\n\n        // Use our own service as an oracle so that the underlying EVM JSON-RPC\n        // calls (two of them) collapse into one deterministic boolean in the\n        // block's oracle responses.\n        let application_id = self.runtime.application_id();\n        let hash_hex = hex::encode(block_hash);","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-bridge/contracts/evm-bridge/src/contract.rs#L136-L172","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\nlinera publish-and-create ... --application-parameters '{\"rpc_endpoint\":\"https://mainnet.infura.io/v3/BADKEY\", ...}'\n# -> transaction aborts: failed to query chain ID from RPC endpoint\n\n# after\nRPC=https://mainnet.infura.io/v3/GOODKEY\ncurl -s $RPC -X POST -H 'content-type: application/json' \\\n  -d '{\"jsonrpc\":\"2.0\",\"method\":\"eth_chainId\",\"params\":[],\"id\":1}'\n# expect {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x1\"} matching source_chain_id,\n# then submit with the verified endpoint.","handlingStrategy":"validation","validationCode":"// Validate the endpoint before instantiate / SetRpcEndpoint:\nasync fn endpoint_ok(url: &str, expected_chain_id: u64) -> bool {\n    let body = r#\"{\"jsonrpc\":\"2.0\",\"method\":\"eth_chainId\",\"params\":[],\"id\":1}\"#;\n    let resp = reqwest::Client::new().post(url).header(\"content-type\", \"application/json\")\n        .body(body).send().await.ok()?;\n    let v: serde_json::Value = resp.json().await.ok()?;\n    v[\"result\"].as_str().and_then(|s| u64::from_str_radix(s.trim_start_matches(\"0x\"), 16).ok())\n        == Some(expected_chain_id)\n}","typeGuard":null,"tryCatchPattern":"// The transaction aborts on unreachable endpoints; retry with backoff at the\n// orchestration layer after re-validating the endpoint off-chain:\nfor attempt in 0..5 {\n    if endpoint_ok(&url, source_chain_id).await { break; }\n    tokio::time::sleep(Duration::from_secs(2u64 << attempt)).await;\n}","preventionTips":["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"],"tags":["linera","bridge","ethereum","json-rpc","rpc-endpoint","network","contract","panic"],"backgroundTag":"rpc-endpoint-unreachable","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}