{"record":{"id":"7c0b9c8540ceac8a","repo":"linera-io/linera-protocol","slug":"equivocationproofdifferentchainorheight","errorCode":"EquivocationProofDifferentChainOrHeight","errorMessage":"Equivocation proof references blocks on different chains or at different heights","messagePattern":"Equivocation proof references blocks on different chains or at different heights","errorType":"validation","errorClass":"ChainError","httpStatus":null,"severity":"error","filePath":"linera-chain/src/justification/mod.rs","lineNumber":435,"sourceCode":"                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.\n                ensure!(\n                    *confirmed_round < *validated_round\n                        && validated_unlocking_round\n                            .is_none_or(|unlocking_round| *confirmed_round >= unlocking_round),\n                    ChainError::EquivocationProofNoLockViolation\n                );\n                let confirmed = VoteValue(","sourceCodeStart":417,"sourceCodeEnd":453,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-chain/src/justification/mod.rs#L417-L453","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nlet proof = LockViolation { confirmed_header, validated_header, .. }; // heights differ\nproof.check(&committee)?;\n\n// after\nensure!(\n    confirmed_header.chain_id == validated_header.chain_id\n        && confirmed_header.height == validated_header.height,\n    \"lock violation requires same chain and height\"\n);\nlet proof = LockViolation { confirmed_header, validated_header, .. };","handlingStrategy":"validation","validationCode":"ensure!(\n    confirmed_header.chain_id == validated_header.chain_id\n        && confirmed_header.height == validated_header.height,\n    \"lock violation must be within one chain and height\"\n);","typeGuard":null,"tryCatchPattern":"match proof.check(committee) {\n    Err(ChainError::EquivocationProofDifferentChainOrHeight) => {\n        // not a fault: scope mismatch, discard the proof\n        Ok(())\n    }\n    other => other,\n}","preventionTips":["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."],"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"}