{"record":{"id":"f68cae51e7165e6c","repo":"linera-io/linera-protocol","slug":"log-payload-extends-past-available-data","errorCode":null,"errorMessage":"log payload extends past available data","messagePattern":"log payload extends past available data","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"linera-bridge/src/proof/mod.rs","lineNumber":553,"sourceCode":"fn 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\"\n    );\n\n    // Limit reads to the declared payload boundary.\n    let mut log_data_buf = &data[..log_header.payload_length];\n    *data = &data[log_header.payload_length..];\n\n    let address = <Address as alloy_rlp::Decodable>::decode(&mut log_data_buf)\n        .map_err(|e| anyhow!(\"invalid log address: {e}\"))?;\n\n    // Decode topics list\n    let topics_header = alloy_rlp::Header::decode(&mut log_data_buf)\n        .map_err(|e| anyhow!(\"invalid topics list RLP: {e}\"))?;\n    ensure!(topics_header.list, \"topics must be an RLP list\");\n    ensure!(\n        log_data_buf.len() >= topics_header.payload_length,\n        \"topics payload extends past log boundary\"","sourceCodeStart":535,"sourceCodeEnd":571,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-bridge/src/proof/mod.rs#L535-L571","documentation":"After reading a log's list header, decode_log enforces at linera-bridge/src/proof/mod.rs:553 that the remaining buffer covers header.payload_length before slicing the log's payload boundary. The failure means the log list header declares more payload bytes than exist — truncated log data or a corrupted length prefix. All subsequent field reads are bounded by that declared payload, so this check is the outer safety net for the whole log decode.","triggerScenarios":"A trie leaf carrying a log whose outer length prefix overstates the content; receipt bytes truncated mid-log (transport or storage cut); a fixture that concatenates logs with a stale total-length header after editing entries.","commonSituations":"RPC/proof provider returns partial node bytes; receipt RLP rebuilt by hand (build_test_receipt style helpers) after adding a topic without updating the outer payload length; off-by-N slicing when logs_data was carved out of the receipt payload.","solutions":["Verify the parent logs-list payload length actually covers all entries: logs_data was sliced with logs_header.payload_length — confirm that header matches the real encoded size.","Re-encode the expected log set with alloy_rlp and byte-compare with what you are parsing to find where lengths diverge.","Re-fetch the proof node; a declared-length overflow inside an otherwise valid receipt strongly suggests provider corruption rather than a protocol condition.","If building test receipts, always encode via the library (Encodable) instead of hand-writing length prefixes."],"exampleFix":"// before\nensure!(data.len() >= log_header.payload_length, \"log payload extends past available data\");\n\n// after\nensure!(\n    data.len() >= log_header.payload_length,\n    \"log payload extends past available data: declared {}, have {} — log RLP truncated\",\n    log_header.payload_length,\n    data.len()\n);","handlingStrategy":"try-catch","validationCode":"fn log_payload_fits(data: &[u8]) -> Option<bool> {\n    let h = alloy_rlp::Header::decode(&mut &data[..]).ok()?;\n    Some(h.list && data.len() >= h.payload_length)\n}","typeGuard":null,"tryCatchPattern":"match decode_receipt_logs(receipt_rlp) {\n    Ok(logs) => logs,\n    Err(e) if e.to_string().contains(\"log payload extends past available data\") => {\n        tracing::warn!(error = %e, \"truncated log payload; rejecting proof node\");\n        Vec::new()\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Reject proofs whose declared lengths exceed the delivered bytes; never pad or guess.","Verify total node length equals header + payload before entering per-field decoding.","Re-fetch from a different provider when a node fails bounds checks to distinguish corruption from protocol data."],"tags":["rlp","ethereum","decoding","bounds-check","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"}