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

  1. Try a different, reputable RPC provider for the same chain — often the gateway mangles headers
  2. Verify the RPC URL targets the expected network (Base L2 with OP-style headers)
  3. Pin/align alloy and op-alloy versions to the ones this crate was built and tested with
  4. 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

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


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