{"record":{"id":"819859b42fb42383","repo":"linera-io/linera-protocol","slug":"unexpectedblob","errorCode":"UnexpectedBlob","errorMessage":"Blob was not required by any pending block","messagePattern":"Blob was not required by any pending block","errorType":"validation","errorClass":"WorkerError","httpStatus":null,"severity":"warning","filePath":"linera-core/src/chain_worker/state.rs","lineNumber":2288,"sourceCode":"            .pending_proposed_blobs\n            .try_load_all_entries_mut()\n            .await?\n        {\n            if !pending_blobs.validated.get() {\n                let (_, committee) = self.chain.current_committee().await?;\n                let policy = committee.policy();\n                policy\n                    .check_blob_size(blob.content())\n                    .with_execution_context(ChainExecutionContext::Block)?;\n                ensure!(\n                    u64::try_from(pending_blobs.pending_blobs.iterative_count().await?)\n                        .is_ok_and(|count| count < policy.maximum_published_blobs),\n                    WorkerError::TooManyPublishedBlobs(policy.maximum_published_blobs)\n                );\n            }\n            was_expected = was_expected || pending_blobs.maybe_insert(&blob).await?;\n        }\n        ensure!(was_expected, WorkerError::UnexpectedBlob);\n        self.save().await?;\n        self.chain_info_response().await\n    }\n\n    /// Returns a stored [`Certificate`] for the chain's block at the requested [`BlockHeight`].\n    ///\n    /// Does not need `&mut self` because the chain is eagerly initialized when the\n    /// chain handle is created.\n    #[cfg(with_testing)]\n    #[instrument(skip_all, fields(\n        chain_id = %self.chain_id(),\n        height = %height\n    ))]\n    pub(crate) async fn read_certificate(\n        &self,\n        height: BlockHeight,\n    ) -> Result<Option<CacheArc<ConfirmedBlockCertificate>>, WorkerError> {\n        let certificate_hash = match self.chain.block_hashes.get(&height).await? {","sourceCodeStart":2270,"sourceCodeEnd":2306,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-core/src/chain_worker/state.rs#L2270-L2306","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Send the block proposal (or certificate) first; only then upload the blobs it announces.","Filter uploads against the proposal's expected_blob_ids.","On retries after a round change, resend the updated proposal before resending blobs."],"exampleFix":"// before: pushing blobs before anything references them\nfor blob in blobs {\n    client.upload_blob(chain_id, blob).await?;\n}\nclient.submit_proposal(proposal).await?;\n\n// after: the proposal announces the blob ids, then the blobs follow\nclient.submit_proposal(proposal).await?;\nfor blob in blobs {\n    client.upload_blob(chain_id, blob).await?;\n}","handlingStrategy":"validation","validationCode":"// Only upload blobs the pending proposal actually requires.\nlet expected: HashSet<BlobId> = proposal.expected_blob_ids().iter().cloned().collect();\nfor blob in &blobs {\n    ensure!(expected.contains(&blob.id()), WorkerError::UnexpectedBlob);\n}","typeGuard":null,"tryCatchPattern":"match node.handle_pending_blob(blob).await {\n    Err(NodeError::WorkerError(err)) if matches!(*err, WorkerError::UnexpectedBlob) => {\n        // Not required (yet): submit the proposal first, then retry the upload.\n    }\n    result => result,\n}","preventionTips":["Order requests: proposal or certificate first, blobs second.","Derive the upload list from expected_blob_ids, never from local state alone.","After any round change, resend the proposal before resending blobs.","Do not use validators as blob caches: unrequired blobs are rejected by design."],"tags":["rust","linera","blockchain","blobs","ordering","request-rejected"],"backgroundTag":"unsolicited-blob-rejected","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}