linera-io/linera-protocol · error · WorkerError

InvalidCrossChainRequest

InvalidCrossChainRequest

Error message

Invalid cross-chain request

What it means

select_message_bundles validates incoming cross-chain update requests: message bundles must appear with non-decreasing heights, because they are appended to the sender chain's outbox in order. A batch whose heights go backwards is malformed and the whole request is rejected before any message is applied.

Solutions

  1. Order the bundles by height (preserving the sender's outbox order) before submitting the cross-chain request
  2. Rebuild the batch from the sender chain's outbox state instead of merging partial batches

Example fix

// before: merged batches may be out of order
let bundles = old_retry_bundles.into_iter().chain(new_bundles).collect();
worker.handle_cross_chain_update(origin, recipient, bundles).await?; // InvalidCrossChainRequest

// after: keep heights non-decreasing before submitting
let mut bundles = old_retry_bundles;
bundles.extend(new_blobs);
bundles.sort_by(|a, b| a.1.height.cmp(&b.1.height));
worker.handle_cross_chain_update(origin, recipient, bundles).await?;
Defensive patterns

Strategy: validation

Validate before calling

// Guarantee non-decreasing heights before submitting a cross-chain update.
fn bundles_are_ordered(bundles: &[(Epoch, MessageBundle)]) -> bool {
    bundles.windows(2).all(|w| w[0].1.height <= w[1].1.height)
}
if !bundles_are_ordered(&bundles) {
    bundles.sort_by(|a, b| a.1.height.cmp(&b.1.height));
}
worker.handle_cross_chain_update(origin, recipient, bundles).await?;

Type guard

fn is_invalid_cross_chain_request(e: &WorkerError) -> bool {
    matches!(e, WorkerError::InvalidCrossChainRequest)
}

Prevention

When it happens

Trigger: process_cross_chain_update (from handle_cross_chain_update) with a bundle list where a later bundle's height is lower than an earlier one's.

Common situations: Client-side bug when assembling or merging batches of cross-chain messages; corrupted or hand-built cross-chain request; partial retries concatenated out of order; version changes in the request format.

Related errors


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

Appendix: source

Thrown at linera-core/src/chain_worker/state.rs:297

    /// ones whose epoch has been revoked on the admin chain.
    ///
    /// A revoked-epoch bundle is still accepted if (a) it has already been executed by
    /// anticipation (`bundle.height <= last_anticipated_block_height`), or (b) a later
    /// bundle in the same batch is in a still-trusted epoch — that bundle's certificate
    /// transitively re-certifies all preceding ones via prev-hash chaining.
    pub(crate) async fn select_message_bundles(
        &self,
        origin: &ChainId,
        next_height_to_receive: BlockHeight,
        last_anticipated_block_height: Option<BlockHeight>,
        mut bundles: Vec<(Epoch, MessageBundle)>,
    ) -> Result<Vec<MessageBundle>, WorkerError> {
        let recipient = self.chain_id();
        let mut latest_height = None;
        let mut skipped_len = 0;
        let mut trusted_len = 0;
        for (i, (epoch, bundle)) in bundles.iter().enumerate() {
            ensure!(
                latest_height <= Some(bundle.height),
                WorkerError::InvalidCrossChainRequest
            );
            latest_height = Some(bundle.height);
            if bundle.height < next_height_to_receive {
                skipped_len = i + 1;
            }
            let is_revoked = self
                .storage
                .is_epoch_revoked(*epoch)
                .await
                .map_err(|error| {
                    WorkerError::ChainError(Box::new(ChainError::ExecutionError(
                        Box::new(error),
                        ChainExecutionContext::Block,
                    )))
                })?;
            if !is_revoked || Some(bundle.height) <= last_anticipated_block_height {

View on GitHub (pinned to 6c226ddcb3)