linera-io/linera-protocol · error

invalid log data RLP: {e}

Error message

invalid log data RLP: {e}

What it means

After address and topics were consumed, the final field — the log data — does not start with a parsable RLP header. The data field must be an RLP byte string (list flag clear); a header decode failure here means the remaining bytes inside the log payload are truncated or misaligned.

Source

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

    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!(
        !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 {

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Pre-validate the entire log entry with alloy_primitives::Log::decode — it checks the three-field layout end to end.
  2. Confirm the encoder always emits the data byte string, even when empty (0x80).
  3. Check that topics_header.payload_length covers exactly all topics and nothing more.
  4. Regenerate the receipt and proof from the source chain.

Example fix

// before: data header read from whatever bytes remain
let data_header = alloy_rlp::Header::decode(&mut log_data_buf)
    .map_err(|e| anyhow!("invalid log data RLP: {e}"))?;

// after: whole-entry validation first, custom parse second
use alloy_rlp::Decodable;
anyhow::ensure!(
    alloy_primitives::Log::decode(&mut &log_entry_bytes[..]).is_ok(),
    "log data field missing or misaligned — rejecting entry"
);
Defensive patterns

Strategy: validation

Validate before calling

use alloy_rlp::Decodable;
fn log_has_valid_data_field(rlp: &[u8]) -> bool {
    alloy_primitives::Log::decode(&mut &rlp[..]).is_ok() // data must be an RLP byte string
}

Try / catch

Err(e) if e.to_string().contains("invalid log data RLP") => reject_proof(e),

Prevention

When it happens

Trigger: decode_log with log_data_buf exhausted or starting mid-item after the topics slice: payload_length that stops before the data field, extra bytes appended inside the topics region, or a data field encoded as something other than a byte string header.

Common situations: Logs built without the data field at all (empty payloads must still be encoded as 0x80); encoders that put the data before the topics; truncation when copying proof blobs between systems.

Related errors


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