linera-io/linera-protocol · error · ChainError
EquivocationProofNoFirstRoundViolation
EquivocationProofNoFirstRoundViolation
Error message
Equivocation proof's earlier vote is not below the attested first round
What it means
Thrown by EquivocationProof::check for a FirstRoundViolation proof when the earlier vote's round is not strictly below the attested round (ChainError::EquivocationProofNoFirstRoundViolation). The proof's logic is that the validator attested 'this is the first round at this height' while having already voted in a lower round at that height; if earlier_round >= attested_round there is no contradiction. Note the earlier vote may itself carry an attestation — only the rounds are compared here.
Source
Thrown at linera-chain/src/justification/mod.rs:543
attested_round,
attested_commitment,
attested_signature,
earlier_header,
earlier_round,
earlier_attested,
earlier_commitment,
earlier_signature,
} => {
// Both votes must concern the same height on the same chain: the attestation only
// asserts that no lower round exists at *this* height, so a vote at a lower round
// number elsewhere does not contradict it. The blocks themselves may be equal —
// the contradiction is between the rounds, not the blocks.
ensure!(
attested_header.chain_id == earlier_header.chain_id
&& attested_header.height == earlier_header.height,
ChainError::EquivocationProofDifferentChainOrHeight
);
ensure!(
*earlier_round < *attested_round,
ChainError::EquivocationProofNoFirstRoundViolation
);
let attested = VoteValue(
CryptoHash::new(attested_header),
*attested_round,
CertificateKind::Confirmed,
None,
true,
*attested_commitment,
);
attested_signature.check(&attested, *validator)?;
let earlier = VoteValue(
CryptoHash::new(earlier_header),
*earlier_round,
CertificateKind::Confirmed,
None,
*earlier_attested,View on GitHub (pinned to 6c226ddcb3)
Solutions
- Assert earlier_round < attested_round before constructing the proof.
- When scanning, select as 'earlier' the vote with the strictly smaller round at the same (chain, height).
- If the rounds are equal or reversed, drop the pair — no violation is provable from it.
- Double-check which vote carries first_round == true; the attested one must be the higher round.
Example fix
// before
let proof = FirstRoundViolation { attested_round: r1, earlier_round: r2, .. }; // r2 > r1
proof.check(&committee)?; // Err
// after
if earlier_round >= attested_round {
(attested, earlier) = (earlier, attested); // only if the attestation is on the higher round
}
ensure!(earlier_round < attested_round, "no contradiction between rounds");
let proof = FirstRoundViolation { attested_round, earlier_round, .. }; Defensive patterns
Strategy: validation
Validate before calling
ensure!(
earlier_round < attested_round,
"earlier vote must be strictly below the attested round"
); Try / catch
match proof.check(committee) {
Err(ChainError::EquivocationProofNoFirstRoundViolation) => {
// no contradiction between rounds: discard the proof
Ok(())
}
other => other,
} Prevention
- Select the strictly-lower-round vote as 'earlier' when pairing.
- Confirm the attestation (first_round == true) sits on the higher round.
- Round-compare both votes before assembling the proof.
When it happens
Trigger: Submitting a FirstRoundViolation where earlier_round >= attested_round — e.g. pairing the attested vote with a vote in the same or a higher round, or mixing up which of the two votes carries the attestation during extraction.
Common situations: Evidence collectors pairing an attestation with a later vote instead of an earlier one; mislabeled fields when building the proof struct; tests using equal Round values for both votes.
Related errors
- EquivocationProofNoLockViolation
- Equivocation proof must reference two different blocks
- EquivocationProofDifferentChainOrHeight
- EquivocationProofSameBlock
- Justification chain must lie in rounds strictly below the ce
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/fb9da7c31b7a39cd.
Report an issue: GitHub.