linera-io/linera-protocol · error · NodeError

Unexpected certificate value

Error message

Unexpected certificate value

What it means

Raised in RemoteNode::download_certificates_by_heights while validating the batch a validator returned for a download_certificates_by_heights(chain_id, heights) call: every certificate whose inner chain_id differs from the requested chain_id triggers this error. It is a response-integrity check — a well-behaved validator must only return certificates of the chain that was asked for.

Source

Thrown at linera-core/src/remote_node.rs:261

        &self,
        chain_id: ChainId,
        heights: Vec<BlockHeight>,
    ) -> Result<Vec<ConfirmedBlockCertificate>, NodeError> {
        let mut expected_heights = VecDeque::from(heights.clone());
        let certificates = self
            .node
            .download_certificates_by_heights(chain_id, heights)
            .await?;

        if certificates.len() > expected_heights.len() {
            return Err(NodeError::TooManyCertificatesReturned {
                chain_id,
                remote_node: Box::new(self.public_key),
            });
        }

        for certificate in &certificates {
            ensure!(
                certificate.inner().chain_id() == chain_id,
                NodeError::UnexpectedCertificateValue
            );
            if let Some(expected_height) = expected_heights.pop_front() {
                ensure!(
                    expected_height == certificate.inner().height(),
                    NodeError::UnexpectedCertificateValue
                );
            } else {
                return Err(NodeError::UnexpectedCertificateValue);
            }
        }

        ensure!(
            expected_heights.is_empty(),
            NodeError::MissingCertificatesByHeights {
                chain_id,
                heights: expected_heights.into_iter().collect(),

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Exclude the offending validator from the download and retry with the remaining ones
  2. Check the validator's and any proxy's configuration for cross-chain response mixing
  3. Verify the requested chain_id and heights are what you intended (copy/paste mistakes make legitimate responses look wrong)
  4. Report the validator if it reproducibly returns foreign certificates
Defensive patterns

Strategy: fallback

Type guard

fn is_unexpected_certificate_value(err: &NodeError) -> bool {
    matches!(err, NodeError::UnexpectedCertificateValue)
}

Try / catch

for node in validator_nodes {
    match remote_node.download_certificates_by_heights(chain_id, heights.clone()).await {
        Ok(certs) => return Ok(certs),
        Err(NodeError::UnexpectedCertificateValue) => continue, // skip bad validator
        Err(e) => return Err(e.into()),
    }
}
return Err(NodeError::UnexpectedCertificateValue.into());

Prevention

When it happens

Trigger: Validator (or proxy) returns certificates belonging to another chain; corrupted deserialization routing responses between tenants; byzantine validator serving unrelated data. Only bites per-validator responses inspected directly; the client's scheduler hedges across validators and can drop a bad source.

Common situations: Shared validator infrastructure mixing responses across chains after a misconfiguration; a stale proxy cache serving another chain's certificates; adversarial testing of validator honesty.

Understand the failure class

Related errors


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