linera-io/linera-protocol · error
log data must be a byte string, not a list
Error message
log data must be a byte string, not a list
What it means
The third field of an Ethereum log is the arbitrary data byte string, and decode_log enforces at linera-bridge/src/proof/mod.rs:587 that its header declares a string (!list). A list header at that position means the bytes do not follow the log schema (address, topics list, data string) — a malformed or non-log item reached the data-field position.
Source
Thrown at linera-bridge/src/proof/mod.rs:587
ensure!(
log_data_buf.len() >= topics_header.payload_length,
"topics payload extends past log boundary"
);
let mut topics_data = &log_data_buf[..topics_header.payload_length];
log_data_buf = &log_data_buf[topics_header.payload_length..];
let mut topics = Vec::new();
while !topics_data.is_empty() {
let topic = <B256 as alloy_rlp::Decodable>::decode(&mut topics_data)
.map_err(|e| anyhow!("invalid topic: {e}"))?;
topics.push(topic);
}
// Decode log data (byte string)
let data_header = alloy_rlp::Header::decode(&mut log_data_buf)
.map_err(|e| anyhow!("invalid log data RLP: {e}"))?;
ensure!(
!data_header.list,
"log data must be a byte string, not a list"
);
ensure!(
log_data_buf.len() >= data_header.payload_length,
"log data extends past log boundary"
);
let log_bytes = log_data_buf[..data_header.payload_length].to_vec();
log_data_buf = &log_data_buf[data_header.payload_length..];
ensure!(
log_data_buf.is_empty(),
"trailing data after log fields ({} unexpected bytes)",
log_data_buf.len()
);
Ok(ReceiptLog {
address,View on GitHub (pinned to 6c226ddcb3)
Solutions
- Verify the topics payload was consumed exactly (topics_data emptied by B256 decodes) before reading the data header — a leftover topic shifts the cursor onto the wrong item.
- Hex-dump the data-field position: a prefix >= 0xC0 is a list header and proves schema violation; expect 0x80 (empty) or string prefixes.
- Re-encode fixtures using Vec<u8>::encode so data is a string item.
- If a legitimate emitter encodes list-shaped data, that content is opaque event bytes — it still must be a single RLP string at the receipt layer, so fix the producer.
Example fix
// before (fixture): log data encoded as a list vec![vec![1u8, 2, 3]].encode(&mut buf); // after: log data must be a byte string vec![1u8, 2, 3].encode(&mut buf);
Defensive patterns
Strategy: try-catch
Try / catch
match decode_receipt_logs(receipt_rlp) {
Ok(logs) => logs,
Err(e) if e.to_string().contains("log data must be a byte string") => {
tracing::warn!("log data field malformed; rejecting node");
Vec::new()
}
Err(e) => return Err(e),
} Prevention
- The receipt-layer data field is an opaque byte string: encode opaque payloads with Vec<u8>::encode only.
- Ensure topics decode consumed exactly the topics payload before reading the next header.
When it happens
Trigger: A crafted log whose data field is RLP-encoded as a list (e.g., someone encoded a struct there); a misaligned slice where the previous topics payload was under-consumed so decoding resumes on a nested list; test fixtures encoding data with a helper that wraps content in a list.
Common situations: Hand-rolled fixture encoders; receipts from custom chains or tooling that pack structured data as a list in the data slot; off-by-N consumption of the topics list leaving the cursor inside another list.
Related errors
- topics must be an RLP list
- not enough data to skip RLP item
- log must be an RLP list
- log payload extends past available data
- topics payload extends past log boundary
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/96842d5aff8dff8e.
Report an issue: GitHub.