linera-io/linera-protocol · warning · WorkerError

UnexpectedBlob

UnexpectedBlob

Error message

Blob was not required by any pending block

What it means

Validators only store blobs that a pending block actually requires. handle_pending_blob inserts into pending_validated_blobs and every pending_proposed_blobs entry via maybe_insert, which returns true only when the blob id was already announced by a validated block or by a proposal's expected blob ids. If no pending block lists the blob, the request fails with UnexpectedBlob, so validators cannot be used as arbitrary blob storage.

Source

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

            .pending_proposed_blobs
            .try_load_all_entries_mut()
            .await?
        {
            if !pending_blobs.validated.get() {
                let (_, committee) = self.chain.current_committee().await?;
                let policy = committee.policy();
                policy
                    .check_blob_size(blob.content())
                    .with_execution_context(ChainExecutionContext::Block)?;
                ensure!(
                    u64::try_from(pending_blobs.pending_blobs.iterative_count().await?)
                        .is_ok_and(|count| count < policy.maximum_published_blobs),
                    WorkerError::TooManyPublishedBlobs(policy.maximum_published_blobs)
                );
            }
            was_expected = was_expected || pending_blobs.maybe_insert(&blob).await?;
        }
        ensure!(was_expected, WorkerError::UnexpectedBlob);
        self.save().await?;
        self.chain_info_response().await
    }

    /// Returns a stored [`Certificate`] for the chain's block at the requested [`BlockHeight`].
    ///
    /// Does not need `&mut self` because the chain is eagerly initialized when the
    /// chain handle is created.
    #[cfg(with_testing)]
    #[instrument(skip_all, fields(
        chain_id = %self.chain_id(),
        height = %height
    ))]
    pub(crate) async fn read_certificate(
        &self,
        height: BlockHeight,
    ) -> Result<Option<CacheArc<ConfirmedBlockCertificate>>, WorkerError> {
        let certificate_hash = match self.chain.block_hashes.get(&height).await? {

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Send the block proposal (or certificate) first; only then upload the blobs it announces.
  2. Filter uploads against the proposal's expected_blob_ids.
  3. On retries after a round change, resend the updated proposal before resending blobs.

Example fix

// before: pushing blobs before anything references them
for blob in blobs {
    client.upload_blob(chain_id, blob).await?;
}
client.submit_proposal(proposal).await?;

// after: the proposal announces the blob ids, then the blobs follow
client.submit_proposal(proposal).await?;
for blob in blobs {
    client.upload_blob(chain_id, blob).await?;
}
Defensive patterns

Strategy: validation

Validate before calling

// Only upload blobs the pending proposal actually requires.
let expected: HashSet<BlobId> = proposal.expected_blob_ids().iter().cloned().collect();
for blob in &blobs {
    ensure!(expected.contains(&blob.id()), WorkerError::UnexpectedBlob);
}

Try / catch

match node.handle_pending_blob(blob).await {
    Err(NodeError::WorkerError(err)) if matches!(*err, WorkerError::UnexpectedBlob) => {
        // Not required (yet): submit the proposal first, then retry the upload.
    }
    result => result,
}

Prevention

When it happens

Trigger: Calling handle_pending_blob (directly, via send_pending_blobs, or with a handle_message carrying a blob) before the proposal or certificate that references the blob has arrived; uploading a blob the proposal does not list in its expected blob ids; resending blobs after a round change cleared the pending set.

Common situations: Custom clients that pre-upload blobs eagerly; ordering bugs where the blob upload races the block proposal; retries after round changes that reset pending_blobs.

Related errors


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