linera-io/linera-protocol · warning · ChainError
MustBeNewerThanLockingBlock
MustBeNewerThanLockingBlock
Error message
Proposal for height {0:?} is not newer than locking block in round {1:?} What it means
The locking block is the last block this manager voted to validate, backed by a quorum certificate. check_proposed_block requires every new proposal to be in a round strictly later than the locking block's round; otherwise the proposal is irrelevant and cannot displace the locked value.
Source
Thrown at linera-chain/src/manager.rs:335
}
Round::SingleLeader(_) | Round::Validator(_) => {
// After the first single-leader round, only proposals from the current round are relevant.
ensure!(
new_round == current_round,
ChainError::WrongRound(current_round)
);
}
}
// The round of our validation votes is only allowed to increase.
if let Some(vote) = self.validated_vote() {
ensure!(
new_round > vote.round,
ChainError::InsufficientRoundStrict(vote.round)
);
}
// A proposal that isn't newer than the locking block is not relevant anymore.
if let Some(locking_block) = self.locking_block.get() {
ensure!(
locking_block.round() < new_round,
ChainError::MustBeNewerThanLockingBlock(new_block.height, locking_block.round())
);
}
// If we have voted to confirm a block, we may only vote to validate a *different* block
// if a validated block certificate justifies it from a round strictly after our
// confirmation. The validation vote will then sign the unlocking round `certificate.round`,
// and since our confirmation is in an earlier round, the claim "I have not voted to confirm
// a different block in any round at or above the unlocking round" stays truthful.
//
// Re-validating the very block we confirmed is also allowed, but the certificate must
// still be at least as recent as our confirmation. The unlocking round only constrains
// switching blocks, yet the round we sign is a claim about *ourselves*: an earlier
// confirmation of a different block could fall at or above an older certificate's round
// and turn the claim into a lie we could be slashed for. Our confirmed vote sits in the
// highest round we ever confirmed in, so `vote.round <= certificate.round` guarantees no
// different-block confirmation lies in the unlocking window `[certificate.round, round)`.
if let Some(vote) = self.confirmed_vote() {View on GitHub (pinned to 6c226ddcb3)
Solutions
- Advance to a strictly higher round (via timeout certificates) before re-proposing
- If re-proposing the same locked block in a new round, attach the unlocking validated certificate as OriginalProposal::Regular so the vote can be justified
Defensive patterns
Strategy: validation
Validate before calling
let info = client.chain_info(chain_id).await?;
if let Some(lock) = info.manager.locking_block() {
if proposal.content.round <= lock.round() {
// Must re-propose strictly above the locking round.
return advance_round_via_timeouts(&client, chain_id).await;
}
} Type guard
fn is_must_be_newer_than_locking_block(e: &ChainError) -> bool {
matches!(e, ChainError::MustBeNewerThanLockingBlock(_, _))
} Prevention
- Track the locking block's round in client state alongside your own proposals
- Attach unlocking validated certificates when re-proposing a locked block in a new round
When it happens
Trigger: Submitting a proposal whose round is <= locking_block.round() while the manager holds a lock from a validated certificate — typically a conflicting proposal trying to replace the locked block without a higher-round justification.
Common situations: Leader retries its old block after a different block got locked in the same or a higher round; client missed the validated certificate that moved the lock; same-height fork racing.
Related errors
- The new proposal's round must be greater than the original's
- WrongRound
- InsufficientRound
- InsufficientRoundStrict
- HasIncompatibleConfirmedVote
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/1b6a589125711504.
Report an issue: GitHub.