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
- 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
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
- 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
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
- transaction receipt not found for {tx_hash}
- receipt missing transaction_index
- block not found for hash {block_hash}
- block receipts not found for block {block_hash}
- header RLP hash mismatch: computed {computed_hash}, expected
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/498b12928e1c086f.
Report an issue: GitHub.