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

  1. 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).
  2. If this is test data, re-encode topics with alloy_rlp as Vec<B256> so the list wrapper is correct.
  3. 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.
  4. 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

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


AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22). Data as JSON: /api/errors/7b4c073f65633080. Report an issue: GitHub.