linera-io/linera-protocol · warning · ProofError

receipt missing block_hash (pending tx?)

Error message

receipt missing block_hash (pending tx?)

What it means

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.

Source

Thrown at linera-bridge/src/proof/gen.rs:107

}

#[async_trait]
impl DepositProofClient for HttpDepositProofClient {
    async fn generate_deposit_proof(&self, tx_hash: B256) -> Result<DepositProof, ProofError> {
        // 1. Get transaction receipt → block hash, tx index
        let receipt = self
            .provider
            .get_transaction_receipt(tx_hash)
            .await
            .map_err(|e| ProofError::Transient(e.into()))?
            .ok_or_else(|| {
                ProofError::Transient(anyhow::anyhow!(
                    "transaction receipt not found for {tx_hash}"
                ))
            })?;

        let block_hash = receipt.inner.block_hash.ok_or_else(|| {
            ProofError::Transient(anyhow::anyhow!("receipt missing block_hash (pending tx?)"))
        })?;
        let tx_index = receipt.inner.transaction_index.ok_or_else(|| {
            ProofError::Transient(anyhow::anyhow!("receipt missing transaction_index"))
        })?;

        // 2. Get full block → header RLP
        let block = self
            .provider
            .get_block_by_hash(block_hash)
            .await
            .map_err(|e| ProofError::Transient(e.into()))?
            .ok_or_else(|| {
                ProofError::Transient(anyhow::anyhow!("block not found for hash {block_hash}"))
            })?;

        let mut block_header_rlp = Vec::new();
        block.header.inner.encode(&mut block_header_rlp);

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Retry after the transaction reaches at least one confirmation
  2. Switch to an RPC node that is fully synced
  3. If persistent, use a different RPC provider — the endpoint is returning malformed receipts
Defensive patterns

Strategy: retry

Validate before calling

// Preflight: require block_hash and transaction_index before proof generation.
let ready = provider.get_transaction_receipt(tx_hash).await?.is_some_and(|r| {
    r.inner.block_hash.is_some() && r.inner.transaction_index.is_some()
});

Try / catch

Err(ProofError::Transient(e)) if e.to_string().contains("block_hash") => {
    // pending tx: back off and retry after confirmation
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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