linera-io/linera-protocol · error

invalid block header RLP

Error message

invalid block header RLP

What it means

Step 1 of process_deposit decodes the user-submitted block_header_rlp with proof::decode_block_header and expects success. The panic means the bytes are not valid RLP or do not deserialize into an EVM block header (wrong field structure/list encoding). The whole ProcessDeposit transaction aborts, so no state changes; this is an input-validation guard against malformed relayer submissions.

Source

Thrown at linera-bridge/contracts/evm-bridge/src/contract.rs:202

        };
        assert!(finalized, "block is not finalized");

        log::info!("verified block hash 0x{hash_hex} is finalized");
    }

    async fn process_deposit(
        &mut self,
        block_header_rlp: &[u8],
        receipt_rlp: &[u8],
        proof_nodes: &[Vec<u8>],
        tx_index: u64,
        log_index: u64,
    ) {
        let params = self.runtime.application_parameters();

        // 1. Decode block header → (block_hash, receipts_root)
        let (block_hash, receipts_root) =
            proof::decode_block_header(block_header_rlp).expect("invalid block header RLP");

        // 1b. Finality check: when an endpoint is configured, verify the block hash
        //     is finalized. Uses cached result if a previous deposit from this block
        //     was already processed.
        if self.state.rpc_endpoint.get().is_empty() {
            log::warn!("rpc_endpoint is empty — skipping block finality verification.");
        } else if !self
            .state
            .verified_block_hashes
            .contains(&block_hash.0)
            .await
            .expect("failed to check verified block hashes")
        {
            self.verify_block_hash(block_hash.0).await;
        }

        // 2. Verify receipt inclusion via MPT proof
        let proof_bytes: Vec<Bytes> = proof_nodes

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Encode the header from a typed source: alloy_consensus::Header::decode(&rlp) locally before submitting, and submit exactly those bytes
  2. Fetch the header via eth_getBlockByHash with full transactions off and use the returned RLP-compatible encoding path your relayer already proved
  3. Check for hex/raw mixups: ensure you send RLP bytes, not a 0x-hex string
  4. Pin alloy versions between relayer and contract so header deserialization rules match

Example fix

// before (relayer sends whatever bytes it has)
let header_bytes = raw_from_feeder; // unvalidated
submit(ProcessDeposit { block_header_rlp: header_bytes, .. });

// after (prove it decodes before paying for the transaction)
use alloy_consensus::Header;
let header: Header = alloy_rlp::decode(&header_bytes)
    .map_err(|e| anyhow!("invalid block header RLP: {e}"))?; // fail fast off-chain
submit(ProcessDeposit { block_header_rlp: header_bytes, .. });
Defensive patterns

Strategy: validation

Validate before calling

// Decode the header locally before submitting ProcessDeposit:
use alloy_consensus::Header;
fn header_rlp_valid(bytes: &[u8]) -> bool {
    alloy_rlp::decode::<Header>(bytes).is_ok()
}
assert!(header_rlp_valid(&block_header_rlp), "do not submit: invalid block header RLP");

Type guard

fn valid_block_header_rlp(bytes: &[u8]) -> bool {
    alloy_rlp::decode::<alloy_consensus::Header>(bytes).is_ok()
}

Try / catch

// Contract-side panic aborts the operation atomically. Relayer-side, wrap
// the local decode in Result handling and quarantine the bad record:
match alloy_rlp::decode::<Header>(&bytes) {
    Ok(h) => submit(ProcessDeposit { block_header_rlp: bytes, .. }),
    Err(e) => { tracing::error!("bad header from feeder: {e}"); quarantine(bytes); }

Prevention

When it happens

Trigger: A relayer submits a truncated or byte-corrupted header; the header was RLP-encoded with a different library or a non-canonical encoding; hex vs raw-bytes confusion so the payload is a hex string rather than RLP; the header is from a non-EVM chain or pre-merge format the decoder rejects.

Common situations: Relayer pipelines that slice bytes by fixed offsets instead of using alloy/ethers Header types; passing blockHash instead of the header; double 0x-prefix stripping bugs; upgrades of alloy that tighten header deserialization.

Related errors


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