linera-io/linera-protocol · error · ChainError
EquivocationProofNoLockViolation
EquivocationProofNoLockViolation
Error message
Equivocation proof does not violate the lock claim
What it means
Thrown by EquivocationProof::check for a LockViolation proof when the vote pair does not actually contradict the unlocking-round claim (ChainError::EquivocationProofNoLockViolation). The validation vote's claim covers only rounds in [unlocking_round, validated_round); the confirmation contradicts it only if confirmed_round < validated_round and confirmed_round >= unlocking_round (None means 0). A confirmation at or after validated_round is a legitimate later switch, so such a pair is not a fault.
Source
Thrown at linera-chain/src/justification/mod.rs:447
confirmed_block_hash != validated_block_hash,
ChainError::EquivocationProofSameBlock
);
// The two votes must concern the same height on the same chain; otherwise there
// is no lock relationship between them — a validator may freely confirm a block at
// one height and validate a different one at another height or on another chain.
ensure!(
confirmed_header.chain_id == validated_header.chain_id
&& confirmed_header.height == validated_header.height,
ChainError::EquivocationProofDifferentChainOrHeight
);
// The unlocking-round claim — "no confirmation of a different block in any round
// at or above the unlocking round" — is made while validating in
// `validated_round`, so it covers only the rounds the voter had already acted in:
// the window `[unlocking_round, validated_round)` (an unlocking round of `None`
// means `0`). The confirmation contradicts it only if it falls in that window,
// i.e. `unlocking_round ≤ confirmed_round < validated_round`. A confirmation at or
// after `validated_round` is a legitimate later switch, not a violation.
ensure!(
*confirmed_round < *validated_round
&& validated_unlocking_round
.is_none_or(|unlocking_round| *confirmed_round >= unlocking_round),
ChainError::EquivocationProofNoLockViolation
);
let confirmed = VoteValue(
confirmed_block_hash,
*confirmed_round,
CertificateKind::Confirmed,
None,
*confirmed_attested,
*confirmed_commitment,
);
confirmed_signature.check(&confirmed, *validator)?;
let validated = VoteValue(
validated_block_hash,
*validated_round,
CertificateKind::Validated,View on GitHub (pinned to 6c226ddcb3)
Solutions
- Before submitting, assert confirmed_round < validated_round and (validated_unlocking_round is None or confirmed_round >= validated_unlocking_round).
- If confirmed_round >= validated_round, discard the pair — it is honest behavior, not a violation.
- If the unlocking round excludes the confirmation, select a different confirmation vote inside the window.
- Log the three round values when the check fails to quickly classify the pairing bug.
Example fix
// before
let proof = LockViolation { confirmed_round: r3, validated_round: r1, .. };
proof.check(&committee)?; // Err: confirmation after validation is legal
// after
ensure!(*confirmed_round < *validated_round, "confirmation is a later switch, not a violation");
if let Some(u) = validated_unlocking_round {
ensure!(*confirmed_round >= *u, "confirmation predates the locked window");
}
let proof = LockViolation { .. }; Defensive patterns
Strategy: validation
Validate before calling
ensure!(confirmed_round < validated_round, "confirmation after validation is legal");
if let Some(u) = validated_unlocking_round {
ensure!(confirmed_round >= u, "confirmation predates the locked window");
} Try / catch
match proof.check(committee) {
Err(ChainError::EquivocationProofNoLockViolation) => {
// the vote pair is compatible: no fault, drop the proof
Ok(())
}
other => other,
} Prevention
- Check the round window [unlocking_round, validated_round) contains the confirmation before reporting.
- Order the pair so the validation vote is the later round.
- Log confirmed_round, validated_round and unlocking_round on failure.
When it happens
Trigger: Submitting a LockViolation proof where confirmed_round >= validated_round (the confirmation came after the validation — allowed), or where the validation vote signed an unlocking_round greater than confirmed_round (confirmation predates the locked window). Produced by pairing two honest votes in the wrong temporal order.
Common situations: Evidence collectors pairing a validator's early confirmation with a later validation without comparing rounds; misunderstanding of the locking window after protocol changes; tests constructing proofs from vote records without round-order assertions.
Related errors
- EquivocationProofNoFirstRoundViolation
- Equivocation proof must reference two different blocks
- EquivocationProofDifferentChainOrHeight
- EquivocationProofSameBlock
- Justification chain must lie in rounds strictly below the ce
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/45bf5ab95ea04933.
Report an issue: GitHub.