{"record":{"id":"e97682a36a262095","repo":"linera-io/linera-protocol","slug":"the-received-chain-info-response-is-invalid","errorCode":null,"errorMessage":"The received chain info response is invalid","messagePattern":"The received chain info response is invalid","errorType":"validation","errorClass":"NodeError","httpStatus":null,"severity":"error","filePath":"linera-core/src/remote_node.rs","lineNumber":161,"sourceCode":"                    certificate_hash = %certificate.hash(),\n                    kind = ?C::Value::KIND,\n                    \"validator forgot a certificate value that they signed before\",\n                );\n                None\n            }\n            other => Some(other),\n        }\n    }\n\n    fn check_and_return_info(\n        &self,\n        response: ChainInfoResponse,\n        chain_id: ChainId,\n    ) -> Result<Box<ChainInfo>, NodeError> {\n        let manager = &response.info.manager;\n        let proposed = manager.requested_proposed.as_ref();\n        let locking = manager.requested_locking.as_ref();\n        ensure!(\n            proposed.is_none_or(|proposal| proposal.content.block.chain_id == chain_id)\n                && locking.is_none_or(|cert| cert.chain_id() == chain_id)\n                && response.check(self.public_key).is_ok(),\n            NodeError::InvalidChainInfoResponse\n        );\n        Ok(response.info)\n    }\n\n    #[instrument(level = \"trace\")]\n    pub(crate) async fn download_certificate_for_blob(\n        &self,\n        blob_id: BlobId,\n    ) -> Result<ConfirmedBlockCertificate, NodeError> {\n        let certificate = self.node.blob_last_used_by_certificate(blob_id).await?;\n        if !certificate.block().requires_or_creates_blob(&blob_id) {\n            info!(\n                address = self.address(),\n                %blob_id,","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-core/src/remote_node.rs#L143-L179","documentation":"Raised by RemoteNode::check_and_return_info, which validates every ChainInfoResponse coming back from a validator in handle_chain_info_query, handle_block_proposal, handle_timeout_certificate, handle_confirmed_certificate, handle_validated_certificate and handle_lite_certificate. The response is rejected unless the requested proposed block and requested locking certificate belong to the queried chain and response.check(validator_public_key) passes (signature verification against that validator's key). Failure means the validator's answer is malformed, unrelated to the requested chain, or not properly signed by the key we configured.","triggerScenarios":"Validator (or a proxy in front of it) returns chain info for a different chain_id in requested_proposed/requested_locking; response signature does not verify against the public key in the wallet's validator config; a misconfigured load balancer routing requests to the wrong backend; a byzantine validator fabricating responses.","commonSituations":"Wrong validator public key in the wallet (typo, stale committee after rotation); an nginx/gRPC proxy rewriting or replaying responses; hybrid test setups mixing mainnet and devnet keys. A single faulty validator is tolerated by quorum aggregation; seeing this error usually means you are inspecting a per-validator failure or too many validators misbehave.","solutions":["Verify the wallet's validator list: addresses and public keys must match the current committee","Retry against a different validator (or let quorum aggregation skip the faulty one) and confirm the error is isolated to one node","If behind a proxy, bypass it temporarily to check whether it corrupts responses","Take the misbehaving validator out of rotation and report it"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"// Before relying on a validator, verify its advertised info is signed correctly for the chain you track.\nlet response = validator_node.handle_chain_info_query(chain_info_query_for(chain_id)).await?;\nassert!(response.check(&validator_public_key).is_ok(), \"validator key/response mismatch\");","typeGuard":"fn is_invalid_chain_info_response(err: &NodeError) -> bool {\n    matches!(err, NodeError::InvalidChainInfoResponse)\n}","tryCatchPattern":"let results = futures::future::join_all(validators.iter().map(|node| async {\n    node.handle_chain_info_query(query.clone()).await\n}));\n// Quorum pattern: proceed with the answers that verify; ignore InvalidChainInfoResponse ones\n// as long as enough valid responses remain.","preventionTips":["Keep validator public keys in the wallet exactly in sync with committee rotations","Health-check validators periodically: a response failing signature check flags the node or its key config","Do not put response-mangling proxies in front of validator gRPC endpoints"],"tags":["linera","validators","signature","response-validation","security"],"backgroundTag":"invalid-validator-response","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}