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);
falseView on GitHub (pinned to 6c226ddcb3)
Solutions
- 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.
- If a fork was resolved on the sender side, wait for the sender's finality before applying its cross-chain updates to receivers.
- Do not hand-construct bundles; always derive them from the sender's certified block so content matches at each cursor.
- 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
- Only apply cross-chain updates from finalized (certified) sender blocks.
- Do not re-execute blocks from a losing fork.
- Treat UnexpectedBundle as a security event, never as a transient error.
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
- {bundle:?} is out of order. Block and height should be at le
- {bundle:?} cannot be skipped: it must be received before the
- The previous block hash of a new block should match the last
- Cannot vote for block proposal of chain {chain_id} because {
- InvalidCrossChainRequest
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/b22233609519a97b.
Report an issue: GitHub.