linera-io/linera-protocol · error · InboxError
{bundle:?} is out of order. Block and height should be at le
Error message
{bundle:?} is out of order. Block and height should be at least: {next_cursor:?} What it means
Raised by InboxStateView::remove_bundle when the bundle's cursor is strictly below the inbox's next_cursor_to_remove (InboxError::IncorrectOrder, surfaced as ChainError::CannotSkipMessage). Bundles must be consumed from an inbox in increasing cursor order (height, then index within the block); a bundle arriving below the next expected cursor means the caller's view of the inbox sequence is stale or reordered. Bundles below restored_cursor are treated as no-ops instead, so this error specifically means the cursor is between restored_cursor and next_cursor_to_remove.
Source
Thrown at linera-chain/src/inbox.rs:223
Ok(())
}
/// Consumes a bundle from the inbox.
///
/// Returns `true` if the bundle was already known, i.e. it was present in `added_bundles`.
pub(crate) async fn remove_bundle(
&mut self,
bundle: &MessageBundle,
) -> Result<bool, InboxError> {
// Record the latest cursor.
let cursor = bundle.cursor();
if cursor < *self.restored_cursor.get() {
// Bundle's effects are already in the restored execution state; treat the
// consumption as a no-op without touching `removed_bundles` so the queue
// doesn't fill with bundles a sender will never push again.
return Ok(true);
}
ensure!(
cursor >= *self.next_cursor_to_remove.get(),
InboxError::IncorrectOrder {
bundle: bundle.clone(),
next_cursor: *self.next_cursor_to_remove.get(),
}
);
// Discard added bundles with lower cursors (if any).
while let Some(previous_bundle) = self.added_bundles.front().await? {
if previous_bundle.cursor() >= cursor {
break;
}
ensure!(
previous_bundle.is_skippable(),
InboxError::UnskippableBundle {
bundle: previous_bundle
}
);
self.added_bundles.delete_front();View on GitHub (pinned to 6c226ddcb3)
Solutions
- Confirm the block being executed matches the chain's current inbox state (correct block height / no re-execution of already-applied blocks).
- If the bundle's effects are covered by a restored checkpoint, deliver it at a cursor >= restored_cursor or rely on the restored_cursor no-op path instead of forcing removal.
- Order bundles by cursor (height, then index) before calling remove_bundles_from_inboxes.
- If the inbox state itself is wrong after a restore, re-run restore_from_checkpoint with the matching cursor before retrying.
Example fix
// before
for bundle in bundles {
inbox.remove_bundle(&bundle).await?; // may arrive below next cursor
}
// after
let mut ordered: Vec<_> = bundles.into_iter().collect();
ordered.sort_by_key(|b| b.cursor());
for bundle in ordered {
inbox.remove_bundle(&bundle).await?;
} Defensive patterns
Strategy: validation
Validate before calling
// before removing, check the bundle is not below the inbox's next cursor
let next = *inbox.next_cursor_to_remove.get();
if bundle.cursor() < next {
return Err(anyhow::anyhow!(
"bundle {:?} below next expected cursor {:?} — stale or reordered certificate",
bundle.cursor(),
next
));
} Try / catch
match inbox.remove_bundle(&bundle).await {
Err(InboxError::IncorrectOrder { bundle, next_cursor }) => {
// do not retry the same bundle; re-sync execution state to the chain's current inbox
Err(anyhow::anyhow!("inbox out of sync: expected cursor {:?}, got {:?}", next_cursor, bundle.cursor()))
}
other => other,
} Prevention
- Process blocks strictly in height order so inbox cursors never move backwards.
- After a checkpoint restore, re-run restore_from_checkpoint with the matching cursor before executing.
- Sort bundles by (height, index) before calling remove_bundles_from_inboxes.
When it happens
Trigger: Calling an execution path that routes to remove_bundle (e.g. remove_bundles_from_inboxes during block execution) with message bundles whose cursor.height/index is lower than the inbox's next_cursor_to_remove. Typical cause: processing a block that re-receives or skips messages already consumed in a previous block, or running operations against an inbox state view that has advanced further than the certificates being applied.
Common situations: Replaying or re-executing an old block against a chain state that already consumed later bundles; rollback/checkpoint-restore flows where next_cursor_to_remove was reset differently from the caller's assumption; desynchronized inbox state after a crash between persistence steps; a malicious or buggy sender delivering an outdated cross-chain update.
Related errors
- {bundle:?} cannot be skipped: it must be received before the
- Cannot reconcile {bundle:?} with {previous_bundle:?}
- InvalidCrossChainRequest
- Cannot vote for block proposal of chain {chain_id} because {
- UnexpectedBlockHeight
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/f011404cfd11d2e2.
Report an issue: GitHub.