{"record":{"id":"77cfd5f4cf1dfc67","repo":"linera-io/linera-protocol","slug":"empty-receipt-rlp","errorCode":null,"errorMessage":"empty receipt RLP","messagePattern":"empty receipt RLP","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"linera-bridge/src/proof/mod.rs","lineNumber":376,"sourceCode":"        \"proof too large: {total_bytes} bytes (max {MAX_PROOF_BYTES})\",\n    );\n\n    let key = receipt_trie_key(tx_index);\n    alloy_trie::proof::verify_proof(receipts_root, key, Some(receipt_rlp.to_vec()), proof_nodes)\n        .map_err(|e| anyhow!(\"MPT proof verification failed: {e}\"))\n}\n\n/// Decodes a receipt's RLP and extracts its logs.\n///\n/// Handles EIP-2718 typed receipts (type byte prefix < 0x80).\n///\n/// This variant exists because when tests build MPT tries\n/// with multiple receipts (e.g. test_build_receipt_proof_multiple_receipts),\n/// each receipt needs a distinct cumulative_gas_used to produce different\n/// RLP encodings. Without that, all empty-log receipts would be byte-identical,\n/// making the trie degenerate.\npub fn decode_receipt_logs(receipt_rlp: &[u8]) -> Result<Vec<ReceiptLog>> {\n    ensure!(!receipt_rlp.is_empty(), \"empty receipt RLP\");\n\n    let mut data: &[u8] = receipt_rlp;\n    // EIP-2718: if first byte < 0x80, it's a transaction type prefix\n    if data[0] < 0x80 {\n        data = &data[1..];\n    }\n\n    let list_header =\n        alloy_rlp::Header::decode(&mut data).map_err(|e| anyhow!(\"invalid receipt RLP: {e}\"))?;\n    ensure!(list_header.list, \"receipt must be an RLP list\");\n\n    // Limit reads to the receipt's declared payload.\n    ensure!(\n        data.len() >= list_header.payload_length,\n        \"receipt payload extends past available data\"\n    );\n    let mut data = &data[..list_header.payload_length];\n","sourceCodeStart":358,"sourceCodeEnd":394,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-bridge/src/proof/mod.rs#L358-L394","documentation":"decode_receipt_logs refuses zero-length input up front: an empty byte slice cannot contain a transaction-type prefix, an RLP header, or the receipt fields, so all later indexing (data[0]) would panic. The check converts that into a clean error.","triggerScenarios":"Calling decode_receipt_logs(&[]) or on a slice emptied by earlier processing: a receipt value absent from the trie proof (verify_proof with None), a splitting/offset bug leaving a 0-byte remainder, or an RPC returning empty receipt data.","commonSituations":"Bridge code decoding a receipt whose RLP was stored as empty when the tx had no logs; upstream code that passes an Option's default Vec::new(); off-by-one slicing when extracting the receipt from a concatenated blob.","solutions":["Check the source of the slice: log the tx_index and proof payload lengths before decoding","If a receipt legitimately has no logs, still pass its real RLP (status/cumulative_gas/bloom present) — the RLP is never empty for a mined transaction","Fix upstream slicing so the receipt bytes are passed in full"],"exampleFix":"// before\nlet logs = decode_receipt_logs(&receipt_opt.unwrap_or_default())?; // Vec::new() -> error\n\n// after\nlet rlp = receipt_opt.as_deref().ok_or_else(|| anyhow::anyhow!(\"no receipt for tx {tx_index}\"))?;\nlet logs = decode_receipt_logs(rlp)?;","handlingStrategy":"validation","validationCode":"// Guard the input before decoding\nif receipt_rlp.is_empty() {\n    anyhow::bail!(\"no receipt bytes for tx index {tx_index}; proof value missing?\");\n}\nlet logs = decode_receipt_logs(receipt_rlp)?;","typeGuard":null,"tryCatchPattern":"match decode_receipt_logs(bytes) {\n    Ok(logs) => logs,\n    Err(e) if e.to_string().contains(\"empty receipt RLP\") => {\n        Vec::new() // treat missing receipt as no logs, if that is valid for your flow\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Propagate Option/Result from proof extraction instead of defaulting to empty slices","Assert non-empty inputs at the boundary where bytes enter from RPC/relayers","Add unit tests for empty input so behavior is pinned"],"tags":["ethereum","rlp","receipt","bridge","validation","rust"],"backgroundTag":"rlp-decoding-failed","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}