linera-io/linera-protocol · error
topics must be an RLP list
Error message
topics must be an RLP list
What it means
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).
Source
Thrown at linera-bridge/src/proof/mod.rs:568
let log_header =
alloy_rlp::Header::decode(data).map_err(|e| anyhow!("invalid log RLP: {e}"))?;
ensure!(log_header.list, "log must be an RLP list");
ensure!(
data.len() >= log_header.payload_length,
"log payload extends past available data"
);
// Limit reads to the declared payload boundary.
let mut log_data_buf = &data[..log_header.payload_length];
*data = &data[log_header.payload_length..];
let address = <Address as alloy_rlp::Decodable>::decode(&mut log_data_buf)
.map_err(|e| anyhow!("invalid log address: {e}"))?;
// Decode topics list
let topics_header = alloy_rlp::Header::decode(&mut log_data_buf)
.map_err(|e| anyhow!("invalid topics list RLP: {e}"))?;
ensure!(topics_header.list, "topics must be an RLP list");
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}"))?;View on GitHub (pinned to 6c226ddcb3)
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.
Example fix
// before (fixture): topics encoded as one concatenated string topics_bytes.extend(topic0); topics_bytes.extend(topic1); // after: encode as an RLP list of strings let topics: Vec<B256> = vec![topic0, topic1]; topics.encode(&mut log_buf);
Defensive patterns
Strategy: try-catch
Validate before calling
fn topics_field_is_list(log_item: &[u8]) -> bool {
// after the address item, the next header must declare a list
let mut buf = log_item;
if <alloy_primitives::Address as alloy_rlp::Decodable>::decode(&mut buf).is_err() { return false; }
alloy_rlp::Header::decode(&mut buf).map(|h| h.list).unwrap_or(false)
} Try / catch
match decode_receipt_logs(receipt_rlp) {
Ok(logs) => logs,
Err(e) if e.to_string().contains("topics must be an RLP list") => {
tracing::warn!("malformed topics field; rejecting node");
Vec::new()
}
Err(e) => return Err(e),
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- log data must be a byte string, not a 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/7b4c073f65633080.
Report an issue: GitHub.