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

  1. Advance to a strictly higher round (via timeout certificates) before re-proposing
  2. 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

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


AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22). Data as JSON: /api/errors/1b6a589125711504. Report an issue: GitHub.