linera-io/linera-protocol · critical · ChainError
Certificate unlocking round does not match the top of its ju
Error message
Certificate unlocking round does not match the top of its justification chain
What it means
For a validated certificate, LiteCertificate::check requires at linera-chain/src/certificate/lite.rs:177 that the signed unlocking_round equals the top unlocking round of the carried justification chain (top_unlocking_round()). The signed value must describe exactly the chain being presented; a mismatch means the quorum signed an unlocking round that is not the head of the attached chain — an inconsistent or manipulated certificate.
Source
Thrown at linera-chain/src/certificate/lite.rs:177
ensure!(
self.justification_commitment == derived_commitment,
ChainError::JustificationCommitmentMismatch
);
let value = VoteValue(
self.value.value_hash,
self.round,
self.value.kind,
self.unlocking_round,
self.first_round,
self.justification_commitment,
);
check_signatures(&value, &self.signatures, committee)?;
let top = self.justification.top_unlocking_round();
match self.value.kind {
CertificateKind::Validated => {
// The signed unlocking round must be the top of the chain, which must lie strictly
// below the certified round.
ensure!(
self.unlocking_round == top,
ChainError::JustificationUnlockingRoundMismatch
);
ensure!(
top.is_none_or(|top| top < self.round),
ChainError::JustificationChainNotBelowCertificate
);
}
CertificateKind::Confirmed => {
// The first-round attestation can only be set in a round that could be a chain's
// first one.
if self.first_round {
ensure!(
matches!(
self.round,
Round::Fast
| Round::MultiLeader(0)
| Round::SingleLeader(0)View on GitHub (pinned to 6c226ddcb3)
Solutions
- Reject the certificate and re-fetch it from the source; consistent certificates verify as a unit.
- If you build certificates in tests, construct them from a single vote set via try_from_votes so round, unlocking_round, and justification come from the same votes.
- Check for software version mismatch between the certificate producer and verifier.
- Persist certificates unmodified once checked; do not rebuild them from parts.
Defensive patterns
Strategy: try-catch
Try / catch
match certificate.check(&committee) {
Ok(value) => value,
Err(ChainError::JustificationUnlockingRoundMismatch) => {
tracing::warn!(
round = ?certificate.round,
signed_unlocking = ?certificate.unlocking_round,
"signed unlocking round inconsistent with justification chain; discarding certificate"
);
self.request_full_certificate(certificate.value.value_hash).await?
}
Err(e) => return Err(e),
} Prevention
- Build certificates only from a single consistent vote set (try_from_votes).
- Never mix fields (round, unlocking_round, justification) from different certificates.
- Treat mismatched-round certificates as Byzantine evidence and count them per peer.
When it happens
Trigger: A certificate assembled from votes citing one justification but carrying a different (e.g., longer or shorter) chain; a buggy validator copying unlocking_round from an older vote; retry proposals where the chain was refreshed but the signed unlocking_round was not regenerated.
Common situations: Version skew in how unlocking_round is computed from chains; test code mixing fields from different certificates; a Byzantine validator crafting certificates to unlock locked values prematurely.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- Certificate justification commitment does not match its just
- Justification chain must lie in rounds strictly below the ce
- Certificate carries the first-round attestation but was not
- FalseFirstRoundAttestation
- CannotRejectMessage
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/b518303e178f46e4.
Report an issue: GitHub.