{"record":{"id":"6033e719c65b57f7","repo":"linera-io/linera-protocol","slug":"block-header-must-be-an-rlp-list","errorCode":null,"errorMessage":"block header must be an RLP list","messagePattern":"block header must be an RLP list","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"linera-bridge/src/proof/mod.rs","lineNumber":306,"sourceCode":"        receipts: &[(u64, Vec<u8>)],\n        target_tx_index: u64,\n    ) -> (B256, Vec<Bytes>) {\n        let (root, proof) = super::build_receipt_proof(receipts, target_tx_index);\n        (root, proof.into_iter().map(Into::into).collect())\n    }\n}\n\n/// Decodes an RLP-encoded Ethereum block header, returning `(block_hash, receipts_root)`.\n///\n/// The block hash is `keccak256(header_rlp)`. The receipts root is field index 5\n/// in the RLP list.\npub fn decode_block_header(header_rlp: &[u8]) -> Result<(B256, B256)> {\n    let block_hash = keccak256(header_rlp);\n\n    let mut data = header_rlp;\n    let list_header =\n        alloy_rlp::Header::decode(&mut data).map_err(|e| anyhow!(\"invalid RLP header: {e}\"))?;\n    ensure!(list_header.list, \"block header must be an RLP list\");\n\n    // Limit reads to the header's declared payload.\n    ensure!(\n        data.len() >= list_header.payload_length,\n        \"block header payload extends past available data\"\n    );\n    let mut data = &data[..list_header.payload_length];\n\n    // Skip first 5 fields: parentHash, ommersHash, beneficiary, stateRoot, transactionsRoot\n    for i in 0..5 {\n        skip_rlp_item(&mut data).map_err(|e| anyhow!(\"failed to skip header field {i}: {e}\"))?;\n    }\n\n    // Field index 5: receiptsRoot (B256)\n    let receipts_root = <B256 as alloy_rlp::Decodable>::decode(&mut data)\n        .map_err(|e| anyhow!(\"failed to decode receipts_root: {e}\"))?;\n\n    Ok((block_hash, receipts_root))","sourceCodeStart":288,"sourceCodeEnd":324,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-bridge/src/proof/mod.rs#L288-L324","documentation":"decode_block_header RLP-decodes the outer item of an Ethereum block header and requires it to be a list (headers are a 15-field RLP list). If alloy_rlp::Header decodes successfully but reports a string (single value) instead, this ensure! fails — the input bytes are well-formed RLP but not a block header.","triggerScenarios":"Passing a receipt, transaction, or arbitrary RLP string where a block header is expected; feeding truncated data whose first bytes happen to decode as an RLP string header; wrong endianness or double-hashed input.","commonSituations":"Indexer/bridge code fetching headers via an RPC proxy that returns the wrong field (e.g. transactionsRoot instead of the header); hand-built test vectors that RLP-encode a flat byte array rather than a field list; feeding the keccak256 hash of the header rather than the header itself.","solutions":["Verify you are passing the raw RLP-encoded block header bytes, not its hash, the header JSON, or another trie node","Cross-check against a known-good source: eth_getBlockByHash(..., false). Then re-encode header.raw and compare bytes","For test vectors, build the header as an RLP list of all 15 fields (alloy_primitives::Header struct serializes correctly)"],"exampleFix":"// before\nlet (hash, root) = decode_block_header(&block_hash_bytes)?; // passing the hash -> error\n\n// after\nlet (hash, root) = decode_block_header(&header_rlp_bytes)?; // full header RLP list","handlingStrategy":"validation","validationCode":"// Sanity-check the source before decoding\nassert_eq!(header_rlp.first(), Some(&0xf9), \"block header RLP must start with a list header (0xf9)\");\nlet (hash, root) = decode_block_header(header_rlp)?;","typeGuard":null,"tryCatchPattern":"match decode_block_header(bytes) {\n    Ok(pair) => pair,\n    Err(e) if e.to_string().contains(\"must be an RLP list\") => {\n        anyhow::bail!(\"input is not a block header (got a non-list RLP item); verify RPC field\")\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Fetch headers from a trusted RPC endpoint and pass header.raw bytes unchanged","Never substitute the header's keccak hash or the JSON representation for the RLP bytes","Build test vectors with alloy's Header type so they are lists by construction"],"tags":["ethereum","rlp","block-header","bridge","decoding","rust"],"backgroundTag":"rlp-decoding-failed","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}