linera-io/linera-protocol · critical · InboxError

Cannot reconcile {bundle:?} with {previous_bundle:?}

Error message

Cannot reconcile {bundle:?} with {previous_bundle:?}

What it means

Raised by InboxStateView::remove_bundle when the bundle being consumed matches the cursor of the next added bundle but the two bundles differ (InboxError::UnexpectedBundle). Cursors identify bundle positions per origin; if the cursor is equal the bundles must be byte-equal, otherwise the receiver's stored bundle and the one in the incoming certificate contradict each other. Since the incoming bundle can never be added at that position, remove_bundle fails rather than corrupting the inbox.

Source

Thrown at linera-chain/src/inbox.rs:252

            }
            ensure!(
                previous_bundle.is_skippable(),
                InboxError::UnskippableBundle {
                    bundle: previous_bundle
                }
            );
            self.added_bundles.delete_front();
            tracing::trace!("Skipping previously received bundle {:?}", previous_bundle);
        }
        // Reconcile the bundle with the next added bundle, or mark it as removed.
        let already_known = match self.added_bundles.front().await? {
            Some(previous_bundle) => {
                // Rationale: If the two cursors are equal, then the bundles should match.
                // Otherwise, at this point we know that `self.next_cursor_to_add >
                // previous_bundle.cursor() > cursor`. Notably, `bundle` will never be
                // added in the future. Therefore, we should fail instead of adding
                // it to `self.removed_bundles`.
                ensure!(
                    bundle == &previous_bundle,
                    InboxError::UnexpectedBundle {
                        previous_bundle,
                        bundle: bundle.clone(),
                    }
                );
                self.added_bundles.delete_front();
                tracing::trace!("Consuming bundle {:?}", bundle);
                true
            }
            None => {
                tracing::trace!("Marking bundle as expected: {:?}", bundle);
                self.removed_bundles.push_back(bundle.clone());
                #[cfg(with_metrics)]
                metrics::REMOVED_BUNDLES
                    .with_label_values(&[])
                    .observe(self.removed_bundles.count() as f64);
                false

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Identify which of the two blocks at the conflicting cursor is the one finalized by the sender chain, and process only certificates from that fork.
  2. If a fork was resolved on the sender side, wait for the sender's finality before applying its cross-chain updates to receivers.
  3. Do not hand-construct bundles; always derive them from the sender's certified block so content matches at each cursor.
  4. Report the conflicting certificates as evidence of sender misbehavior if both are finalized.
Defensive patterns

Strategy: try-catch

Validate before calling

// before executing a block, ensure its bundle matches what the inbox holds at that cursor
if let Some(front) = inbox.added_bundles.front().await? {
    if front.cursor() == bundle.cursor() {
        ensure!(
            *front == *bundle,
            "conflicting bundle at cursor {:?}: sender fork",
            bundle.cursor()
        );
    }
}

Try / catch

match inbox.remove_bundle(&bundle).await {
    Err(InboxError::UnexpectedBundle { previous_bundle, bundle }) => {
        // evidence of a forked sender chain: halt processing and report both bundles
        report_sender_fork(origin, previous_bundle, bundle);
        Err(anyhow::anyhow!("sender chain fork detected at cursor {:?}", bundle.cursor()))
    }
    other => other,
}

Prevention

When it happens

Trigger: Executing a block that removes a bundle at cursor C while the inbox's added_bundles front is a different bundle also at cursor C. Indicates the certificate's block content disagrees with the receiver's inbox: e.g. two conflicting blocks at the same height from the same sender chain, or a forked sender chain delivering divergent bundles at the same cursor.

Common situations: Processing a block from a forked sender chain (same height, different content) after already accepting bundles from the other fork; a malicious sender chain signing contradictory cross-chain updates; a bug that swaps bundle contents between cursors when constructing blocks.

Related errors


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