{"record":{"id":"7b4c073f65633080","repo":"linera-io/linera-protocol","slug":"topics-must-be-an-rlp-list","errorCode":null,"errorMessage":"topics must be an RLP list","messagePattern":"topics must be an RLP list","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"linera-bridge/src/proof/mod.rs","lineNumber":568,"sourceCode":"    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\"\n    );\n\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}\"))?;","sourceCodeStart":550,"sourceCodeEnd":586,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-bridge/src/proof/mod.rs#L550-L586","documentation":"Inside decode_log, after the address field, the second field of an Ethereum log must be the topics list. The ensure at linera-bridge/src/proof/mod.rs:568 fails when the header decoded at that position has list == false, i.e. the bytes where topics should be form a byte string. Because address decoding succeeded just before, the slice is aligned; the content itself does not follow the log schema (address, list-of-32-byte-topics, byte string).","triggerScenarios":"A crafted or corrupt log whose second field is a string; a trie node that is not a real log but happens to start with an address-shaped item; a fixture that encodes topics as a concatenated byte string instead of a list of strings.","commonSituations":"Hand-written test RLP for logs; decoding logs produced by non-standard tooling; receipt bytes from a mismatched chain variant with a different log schema.","solutions":["Hex-dump the bytes at the topics position (right after the address item) — a prefix below 0xC0 means string; per spec topics must be a list (0xC0+ for 1-3 topics... 0xF8 for larger).","If this is test data, re-encode topics with alloy_rlp as Vec<B256> so the list wrapper is correct.","Validate the whole receipt against the block's receipts_root first; if the proof verifies but the schema is wrong, the parser and the emitting chain disagree on the log format — check chain/EIP configuration.","Treat the receipt as invalid input: skip it and continue scanning rather than crashing the scan loop."],"exampleFix":"// before (fixture): topics encoded as one concatenated string\ntopics_bytes.extend(topic0); topics_bytes.extend(topic1);\n\n// after: encode as an RLP list of strings\nlet topics: Vec<B256> = vec![topic0, topic1];\ntopics.encode(&mut log_buf);","handlingStrategy":"try-catch","validationCode":"fn topics_field_is_list(log_item: &[u8]) -> bool {\n    // after the address item, the next header must declare a list\n    let mut buf = log_item;\n    if <alloy_primitives::Address as alloy_rlp::Decodable>::decode(&mut buf).is_err() { return false; }\n    alloy_rlp::Header::decode(&mut buf).map(|h| h.list).unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":"match decode_receipt_logs(receipt_rlp) {\n    Ok(logs) => logs,\n    Err(e) if e.to_string().contains(\"topics must be an RLP list\") => {\n        tracing::warn!(\"malformed topics field; rejecting node\");\n        Vec::new()\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Encode fixture topics as Vec<B256> so the list wrapper is always correct.","Schema violations in proof-verified receipts indicate tooling bugs upstream — report rather than patch around."],"tags":["rlp","ethereum","decoding","event-logs","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"}