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

  1. Confirm the block being executed matches the chain's current inbox state (correct block height / no re-execution of already-applied blocks).
  2. 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.
  3. Order bundles by cursor (height, then index) before calling remove_bundles_from_inboxes.
  4. 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

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


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