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.

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)

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.