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

  1. Pair only votes with identical chain_id and height when building the proof.
  2. Index collected votes by (validator, chain_id, height) so cross-height/cross-chain pairs can never be selected.
  3. Reject the malformed proof; it does not justify fault attribution.
  4. 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

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


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