linera-io/linera-protocol · error
invalid receipt RLP: {e}
Error message
invalid receipt RLP: {e} What it means
decode_receipt_logs failed to decode an RLP header for the receipt body: after optionally stripping the EIP-2718 type byte, the next bytes are not a valid RLP list header. The input is not structured as a receipt at all (or is corrupted). generate_deposit_proof maps this to ProofError::Permanent.
Source
Thrown at linera-bridge/src/proof/mod.rs:385
///
/// Handles EIP-2718 typed receipts (type byte prefix < 0x80).
///
/// This variant exists because when tests build MPT tries
/// with multiple receipts (e.g. test_build_receipt_proof_multiple_receipts),
/// each receipt needs a distinct cumulative_gas_used to produce different
/// RLP encodings. Without that, all empty-log receipts would be byte-identical,
/// making the trie degenerate.
pub fn decode_receipt_logs(receipt_rlp: &[u8]) -> Result<Vec<ReceiptLog>> {
ensure!(!receipt_rlp.is_empty(), "empty receipt RLP");
let mut data: &[u8] = receipt_rlp;
// EIP-2718: if first byte < 0x80, it's a transaction type prefix
if data[0] < 0x80 {
data = &data[1..];
}
let list_header =
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");View on GitHub (pinned to 6c226ddcb3)
Solutions
- Feed the exact EIP-2718 canonical receipt bytes (as stored in the receipts trie), not a transaction or a JSON-decoded structure
- Hex-decode before calling if the source data is a hex string
- Log the first bytes (type prefix, list header) to see what shape the input actually has
Example fix
// before: passing a hex string let logs = decode_receipt_logs(hex_str.as_bytes()); // after: decode hex to bytes first let bytes = hex::decode(hex_str)?; let logs = decode_receipt_logs(&bytes)?;
Defensive patterns
Strategy: try-catch
Validate before calling
fn is_receipt_shaped(b: &[u8]) -> bool {
if b.is_empty() { return false; }
let d = if b[0] < 0x80 { &b[1..] } else { &b[..] };
!d.is_empty() && (d[0] >= 0xc0) // typed/stripped payload must start with an RLP list prefix
} Type guard
fn decode_logs_safe(rlp: &[u8]) -> Option<Vec<ReceiptLog>> {
decode_receipt_logs(rlp).ok()
} Try / catch
match decode_receipt_logs(&receipt_rlp) {
Err(e) if e.to_string().contains("invalid receipt RLP") => {
// reject the input; re-derive canonical bytes via Encodable2718
}
other => other?,
} Prevention
- Only pass canonical EIP-2718 receipt bytes produced by Encodable2718
- Hex-decode before calling when the source is a hex string
- Reject empty or string-prefixed input before decoding
When it happens
Trigger: Passing garbage or truncated bytes as receipt_rlp to decode_receipt_logs; bytes that are an RLP string rather than a list; a length prefix that exceeds the buffer. Unit tests exercise empty/single/multiple/typed receipts, so well-formed receipts never hit this.
Common situations: Feeding a hex string without decoding; feeding a transaction RLP instead of a receipt RLP; corrupted fixture or storage bytes.
Related errors
- failed to skip receipt field {i}: {e}
- invalid logs list RLP: {e}
- invalid RLP item: {e}
- invalid log RLP: {e}
- receipt must be an RLP list
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/b16feb098970282f.
Report an issue: GitHub.