linera-io/linera-protocol · error · ChainError
EquivocationProofSameBlock
EquivocationProofSameBlock
Error message
Equivocation proof must reference two different blocks
What it means
Thrown by EquivocationProof::check for a DoubleVote proof when both votes target the same block hash (ChainError::EquivocationProofSameBlock). A double vote requires two votes for different blocks in the same round and kind; two signatures over the identical block are redundant, not contradictory. check computes both header hashes and rejects the proof before verifying signatures.
Source
Thrown at linera-chain/src/justification/mod.rs:490
}
EquivocationProof::DoubleVote {
validator,
round,
kind,
first_header,
first_unlocking_round,
first_attested,
first_commitment,
first_signature,
second_header,
second_unlocking_round,
second_attested,
second_commitment,
second_signature,
} => {
let first_block_hash = CryptoHash::new(first_header);
let second_block_hash = CryptoHash::new(second_header);
ensure!(
first_block_hash != second_block_hash,
ChainError::EquivocationProofSameBlock
);
// Both votes must concern the same height on the same chain: a validator voting
// for different blocks at different heights or on different chains in the same
// round number is not double-voting.
ensure!(
first_header.chain_id == second_header.chain_id
&& first_header.height == second_header.height,
ChainError::EquivocationProofDifferentChainOrHeight
);
let first = VoteValue(
first_block_hash,
*round,
*kind,
*first_unlocking_round,
*first_attested,
*first_commitment,View on GitHub (pinned to 6c226ddcb3)
Solutions
- Deduplicate the validator's votes by (block hash, round, kind) before pairing; a true double vote needs two distinct block hashes.
- Assert CryptoHash::new(&first_header) != CryptoHash::new(&second_header) when building the proof.
- Treat the failing proof as malformed evidence and drop it.
- Check whether the 'second' vote differs only in non-payload metadata — that is a retransmission, not a double vote.
Example fix
// before
let proof = DoubleVote { first_header: h.clone(), second_header: h.clone(), .. };
proof.check(&committee)?; // Err: same block
// after
let distinct: BTreeSet<CryptoHash> = votes.iter().map(|v| v.block_hash).collect();
ensure!(distinct.len() >= 2, "no distinct blocks: no double vote");
let (a, b) = pick_two_distinct(&votes);
let proof = DoubleVote { first_header: a, second_header: b, .. }; Defensive patterns
Strategy: validation
Validate before calling
ensure!(
CryptoHash::new(&first_header) != CryptoHash::new(&second_header),
"double vote requires two different blocks"
); Try / catch
match proof.check(committee) {
Err(ChainError::EquivocationProofSameBlock) => {
// retransmission, not a double vote: discard
Ok(())
}
other => other,
} Prevention
- Deduplicate a validator's votes by (block_hash, round, kind) before pairing.
- Treat identical re-signed votes as retransmissions.
- Assert hash inequality in proof builders.
When it happens
Trigger: Constructing EquivocationProof::DoubleVote with first_header and second_header that hash equally — e.g. the same certificate's signature captured twice, or the same vote re-signed under a different unlocking round — and calling check on it.
Common situations: Evidence deduplication failures where the same vote enters the pair twice; re-broadcast of an identical vote mistaken for a second vote; proof builders that pair a vote with a retransmission instead of a genuinely different block vote.
Related errors
- Equivocation proof must reference two different blocks
- EquivocationProofDifferentChainOrHeight
- EquivocationProofNoLockViolation
- EquivocationProofNoFirstRoundViolation
- EquivocationProofValidJustification
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/e313232b1c1cfa22.
Report an issue: GitHub.