{"record":{"id":"f011404cfd11d2e2","repo":"linera-io/linera-protocol","slug":"bundle-is-out-of-order-block-and-height-shoul","errorCode":null,"errorMessage":"{bundle:?} is out of order. Block and height should be at least: {next_cursor:?}","messagePattern":"(.+?) is out of order\\. Block and height should be at least: (.+?)","errorType":"validation","errorClass":"InboxError","httpStatus":null,"severity":"error","filePath":"linera-chain/src/inbox.rs","lineNumber":223,"sourceCode":"        Ok(())\n    }\n\n    /// Consumes a bundle from the inbox.\n    ///\n    /// Returns `true` if the bundle was already known, i.e. it was present in `added_bundles`.\n    pub(crate) async fn remove_bundle(\n        &mut self,\n        bundle: &MessageBundle,\n    ) -> Result<bool, InboxError> {\n        // Record the latest cursor.\n        let cursor = bundle.cursor();\n        if cursor < *self.restored_cursor.get() {\n            // Bundle's effects are already in the restored execution state; treat the\n            // consumption as a no-op without touching `removed_bundles` so the queue\n            // doesn't fill with bundles a sender will never push again.\n            return Ok(true);\n        }\n        ensure!(\n            cursor >= *self.next_cursor_to_remove.get(),\n            InboxError::IncorrectOrder {\n                bundle: bundle.clone(),\n                next_cursor: *self.next_cursor_to_remove.get(),\n            }\n        );\n        // Discard added bundles with lower cursors (if any).\n        while let Some(previous_bundle) = self.added_bundles.front().await? {\n            if previous_bundle.cursor() >= cursor {\n                break;\n            }\n            ensure!(\n                previous_bundle.is_skippable(),\n                InboxError::UnskippableBundle {\n                    bundle: previous_bundle\n                }\n            );\n            self.added_bundles.delete_front();","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-chain/src/inbox.rs#L205-L241","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nfor bundle in bundles {\n    inbox.remove_bundle(&bundle).await?; // may arrive below next cursor\n}\n\n// after\nlet mut ordered: Vec<_> = bundles.into_iter().collect();\nordered.sort_by_key(|b| b.cursor());\nfor bundle in ordered {\n    inbox.remove_bundle(&bundle).await?;\n}","handlingStrategy":"validation","validationCode":"// before removing, check the bundle is not below the inbox's next cursor\nlet next = *inbox.next_cursor_to_remove.get();\nif bundle.cursor() < next {\n    return Err(anyhow::anyhow!(\n        \"bundle {:?} below next expected cursor {:?} — stale or reordered certificate\",\n        bundle.cursor(),\n        next\n    ));\n}","typeGuard":null,"tryCatchPattern":"match inbox.remove_bundle(&bundle).await {\n    Err(InboxError::IncorrectOrder { bundle, next_cursor }) => {\n        // do not retry the same bundle; re-sync execution state to the chain's current inbox\n        Err(anyhow::anyhow!(\"inbox out of sync: expected cursor {:?}, got {:?}\", next_cursor, bundle.cursor()))\n    }\n    other => other,\n}","preventionTips":["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."],"tags":["inbox","cross-chain-messages","ordering","chain-execution"],"backgroundTag":"out-of-order-message-delivery","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}