{"record":{"id":"de147bea4c52fd00","repo":"linera-io/linera-protocol","slug":"log-data-extends-past-log-boundary","errorCode":null,"errorMessage":"log data extends past log boundary","messagePattern":"log data extends past log boundary","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"linera-bridge/src/proof/mod.rs","lineNumber":591,"sourceCode":"\n    let mut topics_data = &log_data_buf[..topics_header.payload_length];\n    log_data_buf = &log_data_buf[topics_header.payload_length..];\n\n    let mut topics = Vec::new();\n    while !topics_data.is_empty() {\n        let topic = <B256 as alloy_rlp::Decodable>::decode(&mut topics_data)\n            .map_err(|e| anyhow!(\"invalid topic: {e}\"))?;\n        topics.push(topic);\n    }\n\n    // Decode log data (byte string)\n    let data_header = alloy_rlp::Header::decode(&mut log_data_buf)\n        .map_err(|e| anyhow!(\"invalid log data RLP: {e}\"))?;\n    ensure!(\n        !data_header.list,\n        \"log data must be a byte string, not a list\"\n    );\n    ensure!(\n        log_data_buf.len() >= data_header.payload_length,\n        \"log data extends past log boundary\"\n    );\n    let log_bytes = log_data_buf[..data_header.payload_length].to_vec();\n    log_data_buf = &log_data_buf[data_header.payload_length..];\n\n    ensure!(\n        log_data_buf.is_empty(),\n        \"trailing data after log fields ({} unexpected bytes)\",\n        log_data_buf.len()\n    );\n\n    Ok(ReceiptLog {\n        address,\n        topics,\n        data: log_bytes,\n    })\n}","sourceCodeStart":573,"sourceCodeEnd":609,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-bridge/src/proof/mod.rs#L573-L609","documentation":"The final bounds check on the data field in decode_log: after confirming the data item is a byte string, linera-bridge/src/proof/mod.rs:591 ensures the remaining log payload covers data_header.payload_length before copying log_bytes. Failure means the data length prefix overstates the available bytes — the log is truncated or its inner lengths are inconsistent.","triggerScenarios":"A log whose data-string length prefix exceeds the bytes left inside the declared log payload; truncation of the receipt node after the data header; fixtures appending data bytes without updating the data-item length or the outer log length.","commonSituations":"Corrupt or partially delivered Merkle proof nodes; test receipts assembled by concatenation where one length field went stale after an edit; provider bugs or proxy middleware mangling responses.","solutions":["Re-derive the receipt from a trusted full node and byte-compare to localize the corrupted length field.","Verify the Merkle proof against the header's receipts_root before parsing; a verified proof with failing inner lengths points to your own slicing, an unverified one to bad node data.","Regenerate fixtures via alloy_rlp encoding rather than manual concatenation.","Check transport: HTTP JSON-RPC hex decoding (odd-length hex, 0x-prefix handling) can silently drop bytes."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match decode_receipt_logs(receipt_rlp) {\n    Ok(logs) => logs,\n    Err(e) if e.to_string().contains(\"log data extends past log boundary\") => {\n        tracing::warn!(error = %e, \"truncated log data; rejecting node\");\n        Vec::new()\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Hex-decode RPC responses with strict validation; silent byte loss produces exactly this class of failure.","Compare fetched node bytes against a second provider when lengths fail."],"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"}