{"record":{"id":"498b12928e1c086f","repo":"linera-io/linera-protocol","slug":"receipt-missing-block-hash-pending-tx","errorCode":null,"errorMessage":"receipt missing block_hash (pending tx?)","messagePattern":"receipt missing block_hash \\(pending tx\\?\\)","errorType":"exception","errorClass":"ProofError","httpStatus":null,"severity":"warning","filePath":"linera-bridge/src/proof/gen.rs","lineNumber":107,"sourceCode":"}\n\n#[async_trait]\nimpl DepositProofClient for HttpDepositProofClient {\n    async fn generate_deposit_proof(&self, tx_hash: B256) -> Result<DepositProof, ProofError> {\n        // 1. Get transaction receipt → block hash, tx index\n        let receipt = self\n            .provider\n            .get_transaction_receipt(tx_hash)\n            .await\n            .map_err(|e| ProofError::Transient(e.into()))?\n            .ok_or_else(|| {\n                ProofError::Transient(anyhow::anyhow!(\n                    \"transaction receipt not found for {tx_hash}\"\n                ))\n            })?;\n\n        let block_hash = receipt.inner.block_hash.ok_or_else(|| {\n            ProofError::Transient(anyhow::anyhow!(\"receipt missing block_hash (pending tx?)\"))\n        })?;\n        let tx_index = receipt.inner.transaction_index.ok_or_else(|| {\n            ProofError::Transient(anyhow::anyhow!(\"receipt missing transaction_index\"))\n        })?;\n\n        // 2. Get full block → header RLP\n        let block = self\n            .provider\n            .get_block_by_hash(block_hash)\n            .await\n            .map_err(|e| ProofError::Transient(e.into()))?\n            .ok_or_else(|| {\n                ProofError::Transient(anyhow::anyhow!(\"block not found for hash {block_hash}\"))\n            })?;\n\n        let mut block_header_rlp = Vec::new();\n        block.header.inner.encode(&mut block_header_rlp);\n","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-bridge/src/proof/gen.rs#L89-L125","documentation":"The fetched receipt exists but its block_hash field is None. A mined transaction's receipt always carries a block hash, so this indicates a pending or just-sealed transaction, or an RPC returning non-conforming receipts. Classified ProofError::Transient, so retrying after the transaction finalizes is the intended remedy.","triggerScenarios":"Calling generate_deposit_proof while the transaction is still pending (receipt served from the mempool/sequencer pre-state); some L2 sequencer endpoints return receipts before the l1-inclusive block hash is assigned.","commonSituations":"Polling for a proof right after submitting the deposit on an L2 where the sequencer acknowledges before data availability; RPC providers that proxy multiple backends with mixed receipt shapes.","solutions":["Retry after the transaction reaches at least one confirmation","Switch to an RPC node that is fully synced","If persistent, use a different RPC provider — the endpoint is returning malformed receipts"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Preflight: require block_hash and transaction_index before proof generation.\nlet ready = provider.get_transaction_receipt(tx_hash).await?.is_some_and(|r| {\n    r.inner.block_hash.is_some() && r.inner.transaction_index.is_some()\n});","typeGuard":null,"tryCatchPattern":"Err(ProofError::Transient(e)) if e.to_string().contains(\"block_hash\") => {\n    // pending tx: back off and retry after confirmation\n}","preventionTips":["Treat a receipt without block_hash as 'not final yet' in your poller","Add confirmation-depth gating in automated bridge relays","Prefer fully synced RPC nodes for proof generation"],"tags":["evm","rpc","receipt","bridge","transient","pending-transaction"],"backgroundTag":"pending-transaction-receipt","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}