linera-io/linera-protocol · error · ChainError

Equivocation proof must reference two different blocks

Error message

Equivocation proof must reference two different blocks

What it means

Thrown by EquivocationProof::check for a LockViolation proof when the confirmed block and the validated block have the same hash (ChainError::EquivocationProofSameBlock). A lock violation requires the validator to have confirmed one block and validated a different block; if both votes reference the identical block there is no contradiction, so the proof is malformed and check rejects it before signature verification.

Source

Thrown at linera-chain/src/justification/mod.rs:428

    /// and signed by the named validator.
    pub fn check(&self, committee: &Committee) -> Result<(), ChainError> {
        match self {
            EquivocationProof::LockViolation {
                validator,
                confirmed_header,
                confirmed_round,
                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.

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. When building a LockViolation proof, verify CryptoHash::new(&confirmed_header) != CryptoHash::new(&validated_header) first.
  2. Search the validator's vote history for votes over genuinely different block hashes at the same height on the same chain.
  3. Reject the proof as malformed if it fails this check — it is not actionable evidence.
  4. Add a unit assertion in proof-generation code that the two headers differ.

Example fix

// before
let proof = EquivocationProof::LockViolation { /* headers cloned from same block */ .. };
proof.check(&committee)?; // Err: same block

// after
if CryptoHash::new(&confirmed_header) == CryptoHash::new(&validated_header) {
    anyhow::bail!("not a lock violation: same block");
}
let proof = EquivocationProof::LockViolation { .. };
Defensive patterns

Strategy: validation

Validate before calling

if CryptoHash::new(&confirmed_header) == CryptoHash::new(&validated_header) {
    return Err(anyhow::anyhow!("lock violation needs two different blocks"));
}

Try / catch

match proof.check(committee) {
    Err(ChainError::EquivocationProofSameBlock) => {
        // malformed evidence: drop it, nothing is provable from identical blocks
        Ok(()) // ignore this proof
    }
    other => other,
}

Prevention

When it happens

Trigger: Constructing EquivocationProof::LockViolation with confirmed_header and validated_header that hash equally and submitting it to an API that verifies proofs (EquivocationProof::check). Typically from proof-building code that picks two votes of the same block, or from a report where the evidence pairs were mismatched.

Common situations: Off-chain evidence collectors pairing a validator's confirmed vote with its validated vote for the same block; copy-paste errors when assembling proof structs; adversarial reports trying to slash a validator with fabricated/misattributed evidence.

Related errors


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