{"record":{"id":"e41c7ddf7c6acaf9","repo":"linera-io/linera-protocol","slug":"equivocation-proof-must-reference-two-different-bl","errorCode":null,"errorMessage":"Equivocation proof must reference two different blocks","messagePattern":"Equivocation proof must reference two different blocks","errorType":"validation","errorClass":"ChainError","httpStatus":null,"severity":"error","filePath":"linera-chain/src/justification/mod.rs","lineNumber":428,"sourceCode":"    /// and signed by the named validator.\n    pub fn check(&self, committee: &Committee) -> Result<(), ChainError> {\n        match self {\n            EquivocationProof::LockViolation {\n                validator,\n                confirmed_header,\n                confirmed_round,\n                confirmed_attested,\n                confirmed_commitment,\n                confirmed_signature,\n                validated_header,\n                validated_round,\n                validated_unlocking_round,\n                validated_commitment,\n                validated_signature,\n            } => {\n                let confirmed_block_hash = CryptoHash::new(confirmed_header);\n                let validated_block_hash = CryptoHash::new(validated_header);\n                ensure!(\n                    confirmed_block_hash != validated_block_hash,\n                    ChainError::EquivocationProofSameBlock\n                );\n                // The two votes must concern the same height on the same chain; otherwise there\n                // is no lock relationship between them — a validator may freely confirm a block at\n                // one height and validate a different one at another height or on another chain.\n                ensure!(\n                    confirmed_header.chain_id == validated_header.chain_id\n                        && confirmed_header.height == validated_header.height,\n                    ChainError::EquivocationProofDifferentChainOrHeight\n                );\n                // The unlocking-round claim — \"no confirmation of a different block in any round\n                // at or above the unlocking round\" — is made while validating in\n                // `validated_round`, so it covers only the rounds the voter had already acted in:\n                // the window `[unlocking_round, validated_round)` (an unlocking round of `None`\n                // means `0`). The confirmation contradicts it only if it falls in that window,\n                // i.e. `unlocking_round ≤ confirmed_round < validated_round`. A confirmation at or\n                // after `validated_round` is a legitimate later switch, not a violation.","sourceCodeStart":410,"sourceCodeEnd":446,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-chain/src/justification/mod.rs#L410-L446","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["When building a LockViolation proof, verify CryptoHash::new(&confirmed_header) != CryptoHash::new(&validated_header) first.","Search the validator's vote history for votes over genuinely different block hashes at the same height on the same chain.","Reject the proof as malformed if it fails this check — it is not actionable evidence.","Add a unit assertion in proof-generation code that the two headers differ."],"exampleFix":"// before\nlet proof = EquivocationProof::LockViolation { /* headers cloned from same block */ .. };\nproof.check(&committee)?; // Err: same block\n\n// after\nif CryptoHash::new(&confirmed_header) == CryptoHash::new(&validated_header) {\n    anyhow::bail!(\"not a lock violation: same block\");\n}\nlet proof = EquivocationProof::LockViolation { .. };","handlingStrategy":"validation","validationCode":"if CryptoHash::new(&confirmed_header) == CryptoHash::new(&validated_header) {\n    return Err(anyhow::anyhow!(\"lock violation needs two different blocks\"));\n}","typeGuard":null,"tryCatchPattern":"match proof.check(committee) {\n    Err(ChainError::EquivocationProofSameBlock) => {\n        // malformed evidence: drop it, nothing is provable from identical blocks\n        Ok(()) // ignore this proof\n    }\n    other => other,\n}","preventionTips":["Pair only votes over distinct block hashes when assembling proofs.","Deduplicate evidence by block hash.","Unit-test proof builders to assert header inequality."],"tags":["consensus","equivocation","proof-validation","slashing"],"backgroundTag":"invalid-equivocation-proof","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}