linera-io/linera-protocol · critical · ProofError
header RLP hash mismatch: computed {computed_hash}, expected
Error message
header RLP hash mismatch: computed {computed_hash}, expected {block_hash}. This may indicate the RPC returned non-standard header fields. What it means
A permanent sanity check: the locally RLP-encoded block header must keccak256-hash to the block hash reported by the receipt. A mismatch means the header the RPC returned does not round-trip to the canonical consensus encoding — extra/non-standard fields, a different header layout, or wrong alloy encoding for this chain type. Retrying will not help; the trust anchor for the proof is broken.
Source
Thrown at linera-bridge/src/proof/gen.rs:129
})?;
// 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);
// Sanity check: header RLP hashes to the expected block hash
let computed_hash = alloy_primitives::keccak256(&block_header_rlp);
if computed_hash != block_hash {
return Err(ProofError::Permanent(anyhow::anyhow!(
"header RLP hash mismatch: computed {computed_hash}, expected {block_hash}. \
This may indicate the RPC returned non-standard header fields."
)));
}
// 3. Get all block receipts
let all_receipts = self
.provider
.get_block_receipts(block.header.number.into())
.await
.map_err(|e| ProofError::Transient(e.into()))?
.ok_or_else(|| {
ProofError::Transient(anyhow::anyhow!(
"block receipts not found for block {block_hash}"
))
})?;
// 4. Encode each receipt to canonical EIP-2718 form (as stored in the trie).View on GitHub (pinned to 6c226ddcb3)
Solutions
- Try a different, reputable RPC provider for the same chain — often the gateway mangles headers
- Verify the RPC URL targets the expected network (Base L2 with OP-style headers)
- Pin/align alloy and op-alloy versions to the ones this crate was built and tested with
- If reproducible across providers, file a bug — the header Encodable path no longer matches consensus
Defensive patterns
Strategy: fallback
Try / catch
Err(ProofError::Permanent(e)) if e.to_string().contains("header RLP hash mismatch") => {
// trust failure: fall back to a different RPC provider and re-run;
// if it persists across providers, stop and report — do not bypass the check
} Prevention
- Keep a list of fallback RPC providers for the chain
- Pin alloy/op-alloy versions known to encode the target chain's headers canonically
- Never skip or downgrade this sanity check — it anchors the whole proof
When it happens
Trigger: Calling generate_deposit_proof against an RPC that returns non-canonical header fields (some OP-Stack/L2 providers expose custom fields); using the wrong network type for the chain (Ethereum header encoding on an Optimism chain); alloy/op-alloy version drift changing header Encodable behavior.
Common situations: L2 chains with extended headers served by third-party gateways; after upgrading alloy crates where header RLP encoding changed; pointing the client at a chain it was not configured for (the client is built with the Optimism network type).
Related errors
- tx_index {tx_index} not found in block receipts
- receipts root mismatch: computed {receipts_root}, header say
- transaction receipt not found for {tx_hash}
- receipt missing block_hash (pending tx?)
- receipt missing transaction_index
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/0ae95819c8618a0c.
Report an issue: GitHub.