linera-io/linera-protocol · error

log payload extends past available data

Error message

log payload extends past available data

What it means

After reading a log's list header, decode_log enforces at linera-bridge/src/proof/mod.rs:553 that the remaining buffer covers header.payload_length before slicing the log's payload boundary. The failure means the log list header declares more payload bytes than exist — truncated log data or a corrupted length prefix. All subsequent field reads are bounded by that declared payload, so this check is the outer safety net for the whole log decode.

Solutions

  1. Verify the parent logs-list payload length actually covers all entries: logs_data was sliced with logs_header.payload_length — confirm that header matches the real encoded size.
  2. Re-encode the expected log set with alloy_rlp and byte-compare with what you are parsing to find where lengths diverge.
  3. Re-fetch the proof node; a declared-length overflow inside an otherwise valid receipt strongly suggests provider corruption rather than a protocol condition.
  4. If building test receipts, always encode via the library (Encodable) instead of hand-writing length prefixes.

Example fix

// before
ensure!(data.len() >= log_header.payload_length, "log payload extends past available data");

// after
ensure!(
    data.len() >= log_header.payload_length,
    "log payload extends past available data: declared {}, have {} — log RLP truncated",
    log_header.payload_length,
    data.len()
);
Defensive patterns

Strategy: try-catch

Validate before calling

fn log_payload_fits(data: &[u8]) -> Option<bool> {
    let h = alloy_rlp::Header::decode(&mut &data[..]).ok()?;
    Some(h.list && data.len() >= h.payload_length)
}

Try / catch

match decode_receipt_logs(receipt_rlp) {
    Ok(logs) => logs,
    Err(e) if e.to_string().contains("log payload extends past available data") => {
        tracing::warn!(error = %e, "truncated log payload; rejecting proof node");
        Vec::new()
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: A trie leaf carrying a log whose outer length prefix overstates the content; receipt bytes truncated mid-log (transport or storage cut); a fixture that concatenates logs with a stale total-length header after editing entries.

Common situations: RPC/proof provider returns partial node bytes; receipt RLP rebuilt by hand (build_test_receipt style helpers) after adding a topic without updating the outer payload length; off-by-N slicing when logs_data was carved out of the receipt payload.

Related errors


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

Appendix: source

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

fn skip_rlp_item(data: &mut &[u8]) -> Result<()> {
    let header = alloy_rlp::Header::decode(data).map_err(|e| anyhow!("invalid RLP item: {e}"))?;
    ensure!(
        data.len() >= header.payload_length,
        "not enough data to skip RLP item"
    );
    *data = &data[header.payload_length..];
    Ok(())
}

/// Decodes a single log entry from RLP.
///
/// Enforces the declared payload boundary: after decoding address, topics, and data,
/// verifies that exactly `payload_length` bytes were consumed.
fn decode_log(data: &mut &[u8]) -> Result<ReceiptLog> {
    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"

View on GitHub (pinned to 6c226ddcb3)