linera-io/linera-protocol · warning
The new proposal's round must be greater than the original's
Error message
The new proposal's round must be greater than the original's
What it means
BlockProposal::check_invariants (linera-chain/src/data_types/mod.rs:996) validates retry-proposal shape. When original_proposal is OriginalProposal::Fast (a retry of a failed fast-track proposal) and no execution outcome is carried, the new round must be strictly greater than Round::Fast (chain.rs 999-1002): a fast proposal can only be retried in a regular round. The failure is a &'static str that the worker wraps into WorkerError::InvalidBlockProposal (linera-core/src/chain_worker/state.rs:2511-2513).
Source
Thrown at linera-chain/src/data_types/mod.rs:999
}
/// Returns the IDs of the blobs that are required or created by this proposal.
pub fn expected_blob_ids(&self) -> impl Iterator<Item = BlobId> + '_ {
self.content.block.published_blob_ids().into_iter().chain(
self.content.outcome.iter().flat_map(|outcome| {
outcome
.oracle_blob_ids()
.into_iter()
.chain(outcome.iter_created_blobs_ids())
}),
)
}
/// Checks that the original proposal, if present, matches the new one and has a higher round.
pub fn check_invariants(&self) -> Result<(), &'static str> {
match (&self.original_proposal, &self.content.outcome) {
(None, None) => {}
(Some(OriginalProposal::Fast(_)), None) => ensure!(
self.content.round > Round::Fast,
"The new proposal's round must be greater than the original's"
),
(None, Some(_))
| (Some(OriginalProposal::Fast(_)), Some(_))
| (Some(OriginalProposal::Regular { .. }), None) => {
return Err("Must contain a validation certificate if and only if \
it contains the execution outcome from a previous round");
}
(Some(OriginalProposal::Regular { certificate }), Some(outcome)) => {
ensure!(
self.content.round > certificate.round,
"The new proposal's round must be greater than the original's"
);
let block = outcome.clone().with(self.content.block.clone());
let value = ValidatedBlock::new(block);
ensure!(
certificate.check_value(&value),View on GitHub (pinned to 6c226ddcb3)
Solutions
- Build retries with BlockProposal::new_retry_fast, which sets a valid higher round
- When constructing manually, move to a regular round after fast fails, e.g. Round::MultiLeader(0) or the round certified by the timeout
- Validate the round invariant client-side before sending, mirroring check_invariants
Example fix
// before: retrying fast with the same round
let proposal = BlockProposal { content: ProposalContent { block, round: Round::Fast, outcome: None },
original_proposal: Some(OriginalProposal::Fast(fast_sig)), signature };
// after: retry in a strictly higher (regular) round
let proposal = BlockProposal::new_retry_fast(owner, Round::MultiLeader(0), block, fast_sig, &signer).await?; Defensive patterns
Strategy: validation
Validate before calling
// Before sending a fast-retry proposal, mirror the invariant:
if matches!(proposal.original_proposal, Some(OriginalProposal::Fast(_)))
&& proposal.content.outcome.is_none()
&& proposal.content.round <= Round::Fast
{
anyhow::bail!("retry round must be > Round::Fast; use a regular round");
}
// or simply: proposal.check_invariants()?; Type guard
fn fast_retry_round_valid(p: &BlockProposal) -> bool {
!matches!(&p.original_proposal, Some(OriginalProposal::Fast(_)))
|| p.content.outcome.is_some()
|| p.content.round > Round::Fast
} Try / catch
match result {
Err(WorkerError::InvalidBlockProposal(msg)) if msg.contains("greater than the original") => {
// bump the round past the original (Fast -> regular round) and re-sign
}
other => other?,
} Prevention
- Build retries only via BlockProposal::new_retry_fast / new_retry_regular
- Advance rounds using the timeout machinery, never hardcoded round numbers
- Call proposal.check_invariants() locally before sending
When it happens
Trigger: Constructing a retry of a fast proposal whose content.round is still Round::Fast (or otherwise not greater); hand-building BlockProposal instead of using BlockProposal::new_retry_fast; deserialization of a proposal with an inconsistent round field.
Common situations: Retry loops that reuse the original round instead of advancing after a leader timeout; proposal-building code that copies fields from the failed proposal wholesale; protocol tests constructing proposals manually.
Related errors
- Must contain a validation certificate if and only if it cont
- Protocol error within chain client: A quorum voted with an u
- Unexpected quorum: validators voted for block hash {hash} in
- CannotRejectMessage
- Certificate justification commitment does not match its just
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/9cfb8d231d2ce7e8.
Report an issue: GitHub.