{"record":{"id":"cfc0223e5de434fd","repo":"linera-io/linera-protocol","slug":"not-enough-data-to-skip-rlp-item","errorCode":null,"errorMessage":"not enough data to skip RLP item","messagePattern":"not enough data to skip RLP item","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"linera-bridge/src/proof/mod.rs","lineNumber":537,"sourceCode":"    for (key, value) in &entries {\n        builder.add_leaf(*key, value);\n    }\n\n    let root = builder.root();\n    let proof_nodes = builder.take_proof_nodes();\n    let proof = proof_nodes\n        .matching_nodes_sorted(&target_key)\n        .into_iter()\n        .map(|(_, bytes)| bytes.to_vec())\n        .collect();\n\n    (root, proof)\n}\n\n/// Skips one RLP item (string or list) by reading its header and advancing past the payload.\nfn skip_rlp_item(data: &mut &[u8]) -> Result<()> {\n    let header = alloy_rlp::Header::decode(data).map_err(|e| anyhow!(\"invalid RLP item: {e}\"))?;\n    ensure!(\n        data.len() >= header.payload_length,\n        \"not enough data to skip RLP item\"\n    );\n    *data = &data[header.payload_length..];\n    Ok(())\n}\n\n/// Decodes a single log entry from RLP.\n///\n/// Enforces the declared payload boundary: after decoding address, topics, and data,\n/// verifies that exactly `payload_length` bytes were consumed.\nfn decode_log(data: &mut &[u8]) -> Result<ReceiptLog> {\n    let log_header =\n        alloy_rlp::Header::decode(data).map_err(|e| anyhow!(\"invalid log RLP: {e}\"))?;\n    ensure!(log_header.list, \"log must be an RLP list\");\n    ensure!(\n        data.len() >= log_header.payload_length,\n        \"log payload extends past available data\"","sourceCodeStart":519,"sourceCodeEnd":555,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-bridge/src/proof/mod.rs#L519-L555","documentation":"skip_rlp_item is a helper used by decode_block_header and decode_receipt_logs to step over RLP items (status, cumulative gas used, logs bloom) by reading an alloy_rlp Header and advancing past header.payload_length. The ensure at linera-bridge/src/proof/mod.rs:537 fails when the remaining buffer is shorter than the declared payload, i.e. the byte string claims more content than actually present. This is the classic signature of truncated or corrupted RLP — often a receipt pulled from a bad Merkle-proof node.","triggerScenarios":"decode_receipt_logs walks a receipt whose RLP was cut short (truncated proof node bytes, wrong trie node delivered for the key, hex/nibble mismatch in the receipt trie key); decode_block_header reads a header whose payload length exceeds the input; a test fixture hand-assembles receipt RLP with a wrong length prefix.","commonSituations":"Merkle proof nodes from an RPC provider are truncated or from a different block; receipt bytes were copied with an off-by-one offset (extra or missing prefix byte, see the 0x80 single-byte handling above); network transport or storage cut the payload; receipt format from a chain with extra fields (e.g., EIP-4844 blob gas) that shifts field boundaries so a skip lands mid-item.","solutions":["Log the failing field index (the caller wraps this as 'failed to skip receipt field {i}') to see how far decoding got before the shortfall.","Re-fetch the receipt RLP and its proof from the node; verify the proof against the block's receipts_root before parsing.","Check that the receipt bytes were not truncated in transit: total length should exactly cover the outer list header plus payload_length.","Confirm the receipt type matches the chain's active EIPs (post-1559/4844 receipts carry typed prefixes; ensure the leading type byte was stripped before RLP decoding)."],"exampleFix":"// before\nfor i in 0..3 {\n    skip_rlp_item(&mut data).map_err(|e| anyhow!(\"failed to skip receipt field {i}: {e}\"))?;\n}\n\n// after: fail fast with a boundary check before parsing\nensure!(data.len() >= list_header.payload_length, \"receipt truncated: have {}, declared {}\", data.len(), list_header.payload_length);\nfor i in 0..3 {\n    skip_rlp_item(&mut data).map_err(|e| anyhow!(\"failed to skip receipt field {i}: {e}\"))?;\n}","handlingStrategy":"try-catch","validationCode":"// Structural pre-check before decode_receipt_logs: outer receipt must cover its payload.\nfn receipt_rlp_bounds_ok(mut data: &[u8]) -> bool {\n    if data.first() == Some(&0x80) || data.first().is_some_and(|b| *b < 0x80) { data = &data[1..]; }\n    match alloy_rlp::Header::decode(&mut data) {\n        Ok(h) => h.list && data.len() >= h.payload_length,\n        Err(_) => false,\n    }\n}","typeGuard":null,"tryCatchPattern":"match decode_receipt_logs(receipt_rlp) {\n    Ok(logs) => logs,\n    Err(e) if e.to_string().contains(\"not enough data to skip RLP item\") => {\n        // Truncated/corrupt node: drop the proof and re-fetch rather than retry the same bytes.\n        tracing::warn!(error = %e, \"receipt RLP truncated; discarding proof\");\n        Vec::new()\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Always verify the receipt Merkle proof against the header's receipts_root before parsing RLP.","Fetch nodes from a trusted archive provider and hex-decode strictly (handle 0x prefix, even length).","Encode test receipts with alloy_rlp instead of hand-written length prefixes.","Quarantine nodes that fail structural checks; count them to detect provider degradation."],"tags":["rlp","ethereum","decoding","merkle-proof","rust","linera-bridge"],"backgroundTag":"rlp-decoding-error","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}