linera-io/linera-protocol · error · ChainError
EquivocationProofDifferentChainOrHeight
EquivocationProofDifferentChainOrHeight
Error message
Equivocation proof references blocks on different chains or at different heights
What it means
Thrown by EquivocationProof::check for a LockViolation proof when the confirmed and validated votes concern different chains or different heights (ChainError::EquivocationProofDifferentChainOrHeight). The lock relationship only exists between a confirmation vote and a validation vote for blocks at the same height on the same chain; a validator may legitimately confirm a block at one height and validate a different block elsewhere. check therefore requires chain_id and height equality between the two headers.
Source
Thrown at linera-chain/src/justification/mod.rs:435
confirmed_attested,
confirmed_commitment,
confirmed_signature,
validated_header,
validated_round,
validated_unlocking_round,
validated_commitment,
validated_signature,
} => {
let confirmed_block_hash = CryptoHash::new(confirmed_header);
let validated_block_hash = CryptoHash::new(validated_header);
ensure!(
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(View on GitHub (pinned to 6c226ddcb3)
Solutions
- Pair only votes with identical chain_id and height when building the proof.
- Index collected votes by (validator, chain_id, height) so cross-height/cross-chain pairs can never be selected.
- Reject the malformed proof; it does not justify fault attribution.
- Re-extract the headers from the original certificates to rule out labeling mistakes.
Example fix
// before
let proof = LockViolation { confirmed_header, validated_header, .. }; // heights differ
proof.check(&committee)?;
// after
ensure!(
confirmed_header.chain_id == validated_header.chain_id
&& confirmed_header.height == validated_header.height,
"lock violation requires same chain and height"
);
let proof = LockViolation { confirmed_header, validated_header, .. }; Defensive patterns
Strategy: validation
Validate before calling
ensure!(
confirmed_header.chain_id == validated_header.chain_id
&& confirmed_header.height == validated_header.height,
"lock violation must be within one chain and height"
); Try / catch
match proof.check(committee) {
Err(ChainError::EquivocationProofDifferentChainOrHeight) => {
// not a fault: scope mismatch, discard the proof
Ok(())
}
other => other,
} Prevention
- Index collected votes by (validator, chain_id, height).
- Never pair votes across chains or heights even when rounds match.
- Log scope fields when a proof fails this check to find collector bugs.
When it happens
Trigger: Submitting an EquivocationProof::LockViolation where confirmed_header.chain_id/height differ from validated_header.chain_id/height. Common when proof-collection logic pairs votes from different chain IDs or heights because they share a round number, or when headers were mislabeled while extracting evidence.
Common situations: Evidence scrapers keying votes by round instead of (chain_id, height); multi-chain deployments where the same validator votes on many chains and pairs get crossed; malformed slashing reports.
Related errors
- Equivocation proof must reference two different blocks
- EquivocationProofNoLockViolation
- EquivocationProofSameBlock
- EquivocationProofNoFirstRoundViolation
- EquivocationProofValidJustification
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/7c0b9c8540ceac8a.
Report an issue: GitHub.