linera-io/linera-protocol · error · NodeError
The received chain info response is invalid
Error message
The received chain info response is invalid
What it means
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.
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
Defensive patterns
Strategy: fallback
Validate before calling
// Before relying on a validator, verify its advertised info is signed correctly for the chain you track. let response = validator_node.handle_chain_info_query(chain_info_query_for(chain_id)).await?; assert!(response.check(&validator_public_key).is_ok(), "validator key/response mismatch");
Type guard
fn is_invalid_chain_info_response(err: &NodeError) -> bool {
matches!(err, NodeError::InvalidChainInfoResponse)
} Try / catch
let results = futures::future::join_all(validators.iter().map(|node| async {
node.handle_chain_info_query(query.clone()).await
}));
// Quorum pattern: proceed with the answers that verify; ignore InvalidChainInfoResponse ones
// as long as enough valid responses remain. Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Node returned a BlobsNotFound error with an empty list of…
- Unexpected certificate value
- Failed to download certificates and update local node to…
- Local error handling validator response: validator still…
- Missing certificates for chain
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/e97682a36a262095.
Report an issue: GitHub.
Appendix: source
Thrown at linera-core/src/remote_node.rs:161
certificate_hash = %certificate.hash(),
kind = ?C::Value::KIND,
"validator forgot a certificate value that they signed before",
);
None
}
other => Some(other),
}
}
fn check_and_return_info(
&self,
response: ChainInfoResponse,
chain_id: ChainId,
) -> Result<Box<ChainInfo>, NodeError> {
let manager = &response.info.manager;
let proposed = manager.requested_proposed.as_ref();
let locking = manager.requested_locking.as_ref();
ensure!(
proposed.is_none_or(|proposal| proposal.content.block.chain_id == chain_id)
&& locking.is_none_or(|cert| cert.chain_id() == chain_id)
&& response.check(self.public_key).is_ok(),
NodeError::InvalidChainInfoResponse
);
Ok(response.info)
}
#[instrument(level = "trace")]
pub(crate) async fn download_certificate_for_blob(
&self,
blob_id: BlobId,
) -> Result<ConfirmedBlockCertificate, NodeError> {
let certificate = self.node.blob_last_used_by_certificate(blob_id).await?;
if !certificate.block().requires_or_creates_blob(&blob_id) {
info!(
address = self.address(),
%blob_id,View on GitHub (pinned to 6c226ddcb3)