linera-io/linera-protocol · error
logs must be an RLP list
Error message
logs must be an RLP list
What it means
After skipping status, cumulative_gas_used and logs_bloom, decode_receipt_logs decodes the next RLP item — the logs array — and requires it to be a list (an array of log entries). If the bytes at that position decode as an RLP string instead (wrong field alignment, extra/missing fields, post-Byzantium vs pre-Byzantium receipt shape), this ensure! fails.
Source
Thrown at linera-bridge/src/proof/mod.rs:403
alloy_rlp::Header::decode(&mut data).map_err(|e| anyhow!("invalid receipt RLP: {e}"))?;
ensure!(list_header.list, "receipt must be an RLP list");
// Limit reads to the receipt's declared payload.
ensure!(
data.len() >= list_header.payload_length,
"receipt payload extends past available data"
);
let mut data = &data[..list_header.payload_length];
// Skip: status (0), cumulative_gas_used (1), logs_bloom (2)
for i in 0..3 {
skip_rlp_item(&mut data).map_err(|e| anyhow!("failed to skip receipt field {i}: {e}"))?;
}
// Decode the logs list
let logs_header =
alloy_rlp::Header::decode(&mut data).map_err(|e| anyhow!("invalid logs list RLP: {e}"))?;
ensure!(logs_header.list, "logs must be an RLP list");
ensure!(
data.len() >= logs_header.payload_length,
"logs payload extends past receipt boundary"
);
let mut logs_data = &data[..logs_header.payload_length];
let mut logs = Vec::new();
while !logs_data.is_empty() {
logs.push(decode_log(&mut logs_data)?);
}
Ok(logs)
}
/// Parses a `DepositInitiated` event from a receipt log.
///
/// Verifies that `topic[0]` matches the event signature, that the log was emitted by
/// the `expected_emitter` (bridge contract address), and ABI-decodes the data fields.View on GitHub (pinned to 6c226ddcb3)
Solutions
- Decode with a reference implementation (alloy_consensus::Receipt) to validate the bytes, then compare field-by-field
- Rebuild test fixtures using real receipts fetched from RPC
- Check that exactly three fields were skipped before the logs list
Defensive patterns
Strategy: try-catch
Try / catch
match decode_receipt_logs(bytes) {
Ok(logs) => logs,
Err(e) if e.to_string().contains("logs must be an RLP list") => {
anyhow::bail!("receipt field misalignment: expected [status, gas, bloom, logs] list")
}
Err(e) => return Err(e),
} Prevention
- Validate receipts against a reference decoder before feeding bespoke parsers
- Build fixtures from mainnet receipts so field order is guaranteed canonical
- When a new EIP changes receipt shape, add fixtures and re-run the decoder tests first
When it happens
Trigger: Receipts that skip fields this parser expects (e.g. pre-Byzantium root instead of status can still parse, but any variant with fewer than 4 fields misaligns); slicing bugs that leave the parser positioned on a non-list item; hand-built receipts with logs encoded as a string.
Common situations: Test vectors constructed field-by-field where the logs entry was RLP-encoded as a byte string; upstream changes to receipt field counts (new EIPs adding fields after logs would not matter, but fields before logs would); desynchronized offsets after manual prefix stripping.
Related errors
- logs payload extends past receipt boundary
- empty receipt RLP
- receipt must be an RLP list
- receipt payload extends past available data
- invalid logs list RLP: {e}
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/61c42f8bf49a3e62.
Report an issue: GitHub.