{"record":{"id":"61c42f8bf49a3e62","repo":"linera-io/linera-protocol","slug":"logs-must-be-an-rlp-list","errorCode":null,"errorMessage":"logs must be an RLP list","messagePattern":"logs must be an RLP list","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"linera-bridge/src/proof/mod.rs","lineNumber":403,"sourceCode":"        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\n    // Skip: status (0), cumulative_gas_used (1), logs_bloom (2)\n    for i in 0..3 {\n        skip_rlp_item(&mut data).map_err(|e| anyhow!(\"failed to skip receipt field {i}: {e}\"))?;\n    }\n\n    // Decode the logs list\n    let logs_header =\n        alloy_rlp::Header::decode(&mut data).map_err(|e| anyhow!(\"invalid logs list RLP: {e}\"))?;\n    ensure!(logs_header.list, \"logs must be an RLP list\");\n    ensure!(\n        data.len() >= logs_header.payload_length,\n        \"logs payload extends past receipt boundary\"\n    );\n\n    let mut logs_data = &data[..logs_header.payload_length];\n    let mut logs = Vec::new();\n    while !logs_data.is_empty() {\n        logs.push(decode_log(&mut logs_data)?);\n    }\n\n    Ok(logs)\n}\n\n/// Parses a `DepositInitiated` event from a receipt log.\n///\n/// Verifies that `topic[0]` matches the event signature, that the log was emitted by\n/// the `expected_emitter` (bridge contract address), and ABI-decodes the data fields.","sourceCodeStart":385,"sourceCodeEnd":421,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-bridge/src/proof/mod.rs#L385-L421","documentation":"After skipping status, cumulative_gas_used and logs_bloom, decode_receipt_logs decodes the next RLP item — the logs array — and requires it to be a list (an array of log entries). If the bytes at that position decode as an RLP string instead (wrong field alignment, extra/missing fields, post-Byzantium vs pre-Byzantium receipt shape), this ensure! fails.","triggerScenarios":"Receipts that skip fields this parser expects (e.g. pre-Byzantium root instead of status can still parse, but any variant with fewer than 4 fields misaligns); slicing bugs that leave the parser positioned on a non-list item; hand-built receipts with logs encoded as a string.","commonSituations":"Test vectors constructed field-by-field where the logs entry was RLP-encoded as a byte string; upstream changes to receipt field counts (new EIPs adding fields after logs would not matter, but fields before logs would); desynchronized offsets after manual prefix stripping.","solutions":["Decode with a reference implementation (alloy_consensus::Receipt) to validate the bytes, then compare field-by-field","Rebuild test fixtures using real receipts fetched from RPC","Check that exactly three fields were skipped before the logs list"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match decode_receipt_logs(bytes) {\n    Ok(logs) => logs,\n    Err(e) if e.to_string().contains(\"logs must be an RLP list\") => {\n        anyhow::bail!(\"receipt field misalignment: expected [status, gas, bloom, logs] list\")\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Validate receipts against a reference decoder before feeding bespoke parsers","Build fixtures from mainnet receipts so field order is guaranteed canonical","When a new EIP changes receipt shape, add fixtures and re-run the decoder tests first"],"tags":["ethereum","rlp","receipt","logs","bridge","rust"],"backgroundTag":"rlp-decoding-failed","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}