linera-io/linera-protocol · error · ChainError
JustificationUnlockingRoundMismatch
JustificationUnlockingRoundMismatch
Error message
Certificate unlocking round does not match the top of its justification chain
What it means
Thrown by check_cited_quorum for a Validated-kind vote when the opening does not ground the vote's unlocking round: the vote must sign unlocking_round == Some(opening.round) and the opening's round must be strictly lower than the vote's round (ChainError::JustificationUnlockingRoundMismatch). A validated vote's unlocking round is defined by the quorum it cites in a lower round; an opening at the same or higher round, or a mismatched unlocking round, does not honestly ground the claim.
Source
Thrown at linera-chain/src/justification/mod.rs:620
/// the round the vote's payload grounds on, and its signatures form a genuine quorum of
/// `committee` over the reconstructed `ValidatedBlock` payload. These are exactly the checks a
/// voter performs before signing the opening's commitment, so their failure on a signed opening
/// convicts the signer.
fn check_cited_quorum(
header: &BlockHeader,
round: Round,
kind: CertificateKind,
unlocking_round: Option<Round>,
opening: &CommittedQuorum,
committee: &Committee,
) -> Result<(), ChainError> {
ensure!(
opening.value_hash == CryptoHash::new(header),
ChainError::JustificationCommitmentMismatch
);
match kind {
// A validated vote cites the quorum grounding its unlocking round, in a lower round.
CertificateKind::Validated => ensure!(
unlocking_round == Some(opening.round) && opening.round < round,
ChainError::JustificationUnlockingRoundMismatch
),
// A confirmed vote cites the quorum that validated the block in the same round.
CertificateKind::Confirmed => ensure!(
opening.round == round,
ChainError::JustificationUnlockingRoundMismatch
),
// Timeout votes cite nothing; any commitment is dishonest.
CertificateKind::Timeout => ensure!(false, ChainError::JustificationCommitmentMismatch),
}
// A quorum with an unlocking round cites a quorum itself, and vice versa: its own commitment
// and unlocking round come from one chain, so they are both present or both absent.
ensure!(
opening.unlocking_round.is_some() == opening.previous.is_some(),
ChainError::JustificationUnlockingRoundMismatch
);
let value = VoteValue(View on GitHub (pinned to 6c226ddcb3)
Solutions
- As a voter, set the signed unlocking_round from the top link of the exact chain you verified (chain.top_unlocking_round()) and cite that chain's commitment.
- Ensure the cited quorum's round is strictly below the vote's round — re-validate with a lower round chain.
- As a verifier, report the mismatch as an InvalidJustification fault with the opening as evidence.
- Check for version skew between voter and verifier if the grounding rule changed.
Example fix
// before let value = VoteValue(hash, round, Kind::Validated, Some(other_round), false, Some(commitment)); // other_round != opening.round -> check_cited_quorum fails // after let unlocking = chain.top_unlocking_round(); // == opening's round, < round let value = VoteValue(hash, round, Kind::Validated, unlocking, false, chain.commitment(hash));
Defensive patterns
Strategy: validation
Validate before calling
// voter-side: ground the unlocking round in the cited chain's top link let unlocking = chain.top_unlocking_round(); ensure!(unlocking == Some(opening.round) && opening.round < round, "opening does not ground the unlocking round");
Try / catch
match check_cited_quorum(header, round, kind, unlocking_round, &opening, committee) {
Err(ChainError::JustificationUnlockingRoundMismatch) => {
// attributable fault: report InvalidJustification with the opening as evidence
Err(anyhow::anyhow!("unlocking round not grounded by cited quorum"))
}
other => other,
} Prevention
- Derive unlocking_round from chain.top_unlocking_round() and cite that same chain's commitment.
- Ensure the cited quorum's round is strictly below the vote's round.
- Add invariant checks in the voter before signing a VoteValue with a commitment.
When it happens
Trigger: check_cited_quorum is called with kind == CertificateKind::Validated where unlocking_round != Some(opening.round) or opening.round >= round. Arises when a voter signs an unlocking round not equal to the round of the quorum it cites, or cites a same-round/higher-round quorum; when seen via InvalidJustification proof checking, it is the fault being proven.
Common situations: Misimplemented voters that set unlocking_round from a different chain link than the cited opening; certificate builders mixing links from different validation attempts; protocol-version drift changing which link defines the unlocking round.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- Justification chain must lie in rounds strictly below the ce
- Justification chain rounds must be strictly increasing
- EquivocationProofNoLockViolation
- EquivocationProofNoFirstRoundViolation
- EquivocationProofValidJustification
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/caafa666f3c24550.
Report an issue: GitHub.