linera-io/linera-protocol · error

topics payload extends past log boundary

Error message

topics payload extends past log boundary

What it means

decode_log bounds every field read by the log's declared payload boundary. At linera-bridge/src/proof/mod.rs:569 it verifies the topics list's declared payload_length fits inside the remaining log payload before slicing topics_data. Failure means the topics header claims more bytes than the log's own boundary allows — an inconsistent inner length inside a truncated or malformed log.

Source

Thrown at linera-bridge/src/proof/mod.rs:569

        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}"))?;
    ensure!(

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Rebuild the receipt/log via proper RLP encoding (alloy_rlp Encodable) so all nested length prefixes are computed, not hand-written.
  2. Verify the proof against receipts_root; discard and re-fetch nodes that fail structural checks.
  3. Add a structural sanity pass: outer log payload >= address item + topics header + topics payload before decoding.
  4. Log the three numbers (log payload_length, topics payload_length, remaining bytes) at failure to pinpoint which length is wrong.
Defensive patterns

Strategy: try-catch

Try / catch

match decode_receipt_logs(receipt_rlp) {
    Ok(logs) => logs,
    Err(e) if e.to_string().contains("topics payload extends past log boundary") => {
        tracing::warn!(error = %e, "inconsistent nested lengths; rejecting node");
        Vec::new()
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: A log whose topics-list length prefix exceeds the space left after the address field; corrupt trie node bytes where one length field was edited but others were not; fixtures that enlarge topics without growing the outer log payload length.

Common situations: Partial writes or truncation of proof nodes; hand-maintained test RLP where the topics count changed but both the topics-list header and the log-list header were not updated; provider serving a garbled node.

Related errors


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