{"record":{"id":"b17484322d63d778","repo":"linera-io/linera-protocol","slug":"invalid-block-header-rlp","errorCode":null,"errorMessage":"invalid block header RLP","messagePattern":"invalid block header RLP","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"linera-bridge/contracts/evm-bridge/src/contract.rs","lineNumber":202,"sourceCode":"        };\n        assert!(finalized, \"block is not finalized\");\n\n        log::info!(\"verified block hash 0x{hash_hex} is finalized\");\n    }\n\n    async fn process_deposit(\n        &mut self,\n        block_header_rlp: &[u8],\n        receipt_rlp: &[u8],\n        proof_nodes: &[Vec<u8>],\n        tx_index: u64,\n        log_index: u64,\n    ) {\n        let params = self.runtime.application_parameters();\n\n        // 1. Decode block header → (block_hash, receipts_root)\n        let (block_hash, receipts_root) =\n            proof::decode_block_header(block_header_rlp).expect(\"invalid block header RLP\");\n\n        // 1b. Finality check: when an endpoint is configured, verify the block hash\n        //     is finalized. Uses cached result if a previous deposit from this block\n        //     was already processed.\n        if self.state.rpc_endpoint.get().is_empty() {\n            log::warn!(\"rpc_endpoint is empty — skipping block finality verification.\");\n        } else if !self\n            .state\n            .verified_block_hashes\n            .contains(&block_hash.0)\n            .await\n            .expect(\"failed to check verified block hashes\")\n        {\n            self.verify_block_hash(block_hash.0).await;\n        }\n\n        // 2. Verify receipt inclusion via MPT proof\n        let proof_bytes: Vec<Bytes> = proof_nodes","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-bridge/contracts/evm-bridge/src/contract.rs#L184-L220","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Encode the header from a typed source: alloy_consensus::Header::decode(&rlp) locally before submitting, and submit exactly those bytes","Fetch the header via eth_getBlockByHash with full transactions off and use the returned RLP-compatible encoding path your relayer already proved","Check for hex/raw mixups: ensure you send RLP bytes, not a 0x-hex string","Pin alloy versions between relayer and contract so header deserialization rules match"],"exampleFix":"// before (relayer sends whatever bytes it has)\nlet header_bytes = raw_from_feeder; // unvalidated\nsubmit(ProcessDeposit { block_header_rlp: header_bytes, .. });\n\n// after (prove it decodes before paying for the transaction)\nuse alloy_consensus::Header;\nlet header: Header = alloy_rlp::decode(&header_bytes)\n    .map_err(|e| anyhow!(\"invalid block header RLP: {e}\"))?; // fail fast off-chain\nsubmit(ProcessDeposit { block_header_rlp: header_bytes, .. });","handlingStrategy":"validation","validationCode":"// Decode the header locally before submitting ProcessDeposit:\nuse alloy_consensus::Header;\nfn header_rlp_valid(bytes: &[u8]) -> bool {\n    alloy_rlp::decode::<Header>(bytes).is_ok()\n}\nassert!(header_rlp_valid(&block_header_rlp), \"do not submit: invalid block header RLP\");","typeGuard":"fn valid_block_header_rlp(bytes: &[u8]) -> bool {\n    alloy_rlp::decode::<alloy_consensus::Header>(bytes).is_ok()\n}","tryCatchPattern":"// Contract-side panic aborts the operation atomically. Relayer-side, wrap\n// the local decode in Result handling and quarantine the bad record:\nmatch alloy_rlp::decode::<Header>(&bytes) {\n    Ok(h) => submit(ProcessDeposit { block_header_rlp: bytes, .. }),\n    Err(e) => { tracing::error!(\"bad header from feeder: {e}\"); quarantine(bytes); }","preventionTips":["Always derive header bytes from eth_getBlockByHash rather than manual assembly","Round-trip-check RLP through the same alloy version the contract uses","Add fixtures in CI that assert decode_block_header succeeds for real mainnet headers"],"tags":["linera","bridge","ethereum","rlp","block-header","decode","panic"],"backgroundTag":"rlp-decode-failed","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}