{"record":{"id":"9b798320ef590d22","repo":"linera-io/linera-protocol","slug":"hasincompatibleconfirmedvote","errorCode":"HasIncompatibleConfirmedVote","errorMessage":"Already voted to confirm a different block for height {0:?} at round number {1:?}","messagePattern":"Already voted to confirm a different block for height (.+?) at round number (.+?)","errorType":"validation","errorClass":"ChainError","httpStatus":null,"severity":"error","filePath":"linera-chain/src/manager.rs","lineNumber":354,"sourceCode":"                locking_block.round() < new_round,\n                ChainError::MustBeNewerThanLockingBlock(new_block.height, locking_block.round())\n            );\n        }\n        // If we have voted to confirm a block, we may only vote to validate a *different* block\n        // if a validated block certificate justifies it from a round strictly after our\n        // confirmation. The validation vote will then sign the unlocking round `certificate.round`,\n        // and since our confirmation is in an earlier round, the claim \"I have not voted to confirm\n        // a different block in any round at or above the unlocking round\" stays truthful.\n        //\n        // Re-validating the very block we confirmed is also allowed, but the certificate must\n        // still be at least as recent as our confirmation. The unlocking round only constrains\n        // switching blocks, yet the round we sign is a claim about *ourselves*: an earlier\n        // confirmation of a different block could fall at or above an older certificate's round\n        // and turn the claim into a lie we could be slashed for. Our confirmed vote sits in the\n        // highest round we ever confirmed in, so `vote.round <= certificate.round` guarantees no\n        // different-block confirmation lies in the unlocking window `[certificate.round, round)`.\n        if let Some(vote) = self.confirmed_vote() {\n            ensure!(\n                match proposal.original_proposal.as_ref() {\n                    None => false,\n                    Some(OriginalProposal::Regular { certificate }) =>\n                        if vote.value().matches_proposed_block(new_block) {\n                            vote.round <= certificate.round\n                        } else {\n                            vote.round < certificate.round\n                        },\n                    Some(OriginalProposal::Fast(_)) => {\n                        vote.round.is_fast() && vote.value().matches_proposed_block(new_block)\n                    }\n                },\n                ChainError::HasIncompatibleConfirmedVote(new_block.height, vote.round)\n            );\n        }\n        Ok(Outcome::Accept)\n    }\n","sourceCodeStart":336,"sourceCodeEnd":372,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-chain/src/manager.rs#L336-L372","documentation":"Slashing-safety guard in check_proposed_block: once the manager has voted to confirm a block, it may only vote to validate a different block if the proposal carries an original validated-block certificate from a strictly later round (which unlocks the switch), or, when re-validating the very block it confirmed, a certificate at least as recent as the confirmation. Fast-variant original proposals only pass if the confirmation was in the fast round for the same block.","triggerScenarios":"Submitting a conflicting block proposal without proposal.original_proposal (None), or with an OriginalProposal::Regular certificate whose round is <= the confirmed vote's round for a different block (or < for the same block), after manager.confirmed_vote() is set.","commonSituations":"Leader re-proposes a conflicting block after another block was already confirmed at that height; client forgot to attach the original proposal with the unlocking validated certificate when re-proposing; mixing fast and regular proposals across a confirmation.","solutions":["Attach an OriginalProposal::Regular whose validated certificate round is strictly greater than the confirmed vote's round when switching blocks (>= when re-proposing the same block)","Do not submit conflicting proposals after a confirmation at that height — wait for finality and propose at the next height"],"exampleFix":"// before: conflicting re-proposal without justification\nlet proposal = BlockProposal::regular(Round::MultiLeader(1), block); // HasIncompatibleConfirmedVote\n\n// after: carry the validated certificate that unlocks the switch\nlet proposal = BlockProposal::new_regular_with_certificate(\n    Round::MultiLeader(1),\n    block,\n    validated_cert, // OriginalProposal::Regular { certificate }: round must exceed the confirmed vote's\n);","handlingStrategy":"validation","validationCode":"// Before submitting, check the confirmation constraint yourself.\nlet info = client.chain_info(chain_id).await?;\nif let Some(vote) = info.manager.confirmed_vote() {\n    let same_block = vote.value().matches_proposed_block(&block);\n    let cert_round = match &proposal.original_proposal {\n        Some(OriginalProposal::Regular { certificate }) => Some(certificate.round),\n        _ => None,\n    };\n    let ok = cert_round.is_some_and(|r| if same_block { vote.round <= r } else { vote.round < r });\n    if !ok {\n        return Err(ClientError::WouldConflictWithConfirmedVote);\n    }\n}","typeGuard":"fn is_incompatible_confirmed_vote(e: &ChainError) -> bool {\n    matches!(e, ChainError::HasIncompatibleConfirmedVote(_, _))\n}","tryCatchPattern":null,"preventionTips":["Never submit a conflicting block at a height where a confirmation already exists","Always attach the unlocking validated certificate (OriginalProposal::Regular) when re-proposing after confirmations","Remember fast-variant original proposals only pass for the exact fast-confirmed block"],"tags":["consensus","confirmed-vote","slashing-protection","locking-block","linera"],"backgroundTag":"consensus-lock-conflict","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}