linera-io/linera-protocol · warning · ChainError

InsufficientRound

InsufficientRound

Error message

Round number should be at least {0:?}

What it means

In check_proposed_block, once past the fast round, a MultiLeader or SingleLeader(0) proposal from a round older than the manager's current round is obsolete. The manager only considers proposals at or above its current round, which advances via timeout certificates and newer proposals.

Source

Thrown at linera-chain/src/manager.rs:313

        // When a block is certified, incrementing its height must succeed.
        ensure!(
            new_block.height < BlockHeight::MAX,
            ChainError::BlockHeightOverflow
        );
        let current_round = self.current_round();
        match new_round {
            // The proposal from the fast round may still be relevant as a locking block, so
            // we don't compare against the current round here.
            Round::Fast => {}
            Round::MultiLeader(_) | Round::SingleLeader(0) => {
                // If the fast round has not timed out yet, only a super owner is allowed to open
                // a later round by making a proposal.
                ensure!(
                    self.is_super(&proposal.owner()) || !current_round.is_fast(),
                    ChainError::WrongRound(current_round)
                );
                // After the fast round, proposals older than the current round are obsolete.
                ensure!(
                    new_round >= current_round,
                    ChainError::InsufficientRound(new_round)
                );
            }
            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)
            );

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Fetch fresh chain info and build a new proposal at (or above) the current round
  2. Discard the stale proposal instead of retrying it unchanged
Defensive patterns

Strategy: validation

Validate before calling

let info = client.chain_info(chain_id).await?;
if proposal.content.round < info.manager.current_round {
    // Stale proposal: rebuild it at the current round instead of submitting.
    return rebuild_proposal(&client, chain_id).await;
}

Type guard

fn is_insufficient_round(e: &ChainError) -> bool {
    matches!(e, ChainError::InsufficientRound(_))
}

Prevention

When it happens

Trigger: Re-delivering or retrying a block proposal whose content.round is lower than manager.current_round() after the round moved forward (timeout certificate processed or a newer proposal seen).

Common situations: Retrying a failed proposal submission after the round advanced; client caching proposals across rounds; leader reusing a proposal computed before a timeout.

Related errors


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