linera-io/linera-protocol · error
receipt payload extends past available data
Error message
receipt payload extends past available data
What it means
decode_receipt_logs bounds-checks the receipt's declared payload_length against the bytes remaining after the RLP header. If the length prefix claims more data than is present, the ensure! fails before any slicing, preventing an out-of-bounds panic at `&data[..payload_length]` and rejecting malformed/truncated receipts.
Source
Thrown at linera-bridge/src/proof/mod.rs:389
/// 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");
ensure!(
data.len() >= logs_header.payload_length,
"logs payload extends past receipt boundary"
);View on GitHub (pinned to 6c226ddcb3)
Solutions
- Log remaining length vs payload_length to confirm truncation, then fix the producer/transport layer
- Fetch receipts from a trusted node and pass bytes verbatim
- Ensure buffers carrying receipts are dynamically sized (Vec<u8>) not fixed arrays
Defensive patterns
Strategy: try-catch
Try / catch
match decode_receipt_logs(receipt_rlp) {
Ok(logs) => logs,
Err(e) if e.to_string().contains("extends past available data") => {
// truncated input: refetch, never retry the same bytes
anyhow::bail!("truncated receipt RLP ({} bytes); refetch from RPC", receipt_rlp.len())
}
Err(e) => return Err(e),
} Prevention
- Carry receipt bytes in dynamically sized buffers; avoid fixed-size truncating arrays
- Log declared payload_length vs actual length when catches occur to locate the truncating layer
- Verify end-to-end integrity (checksums or length framing) for relayed proof payloads
When it happens
Trigger: Truncated receipt bytes (partial RPC response, message framing bug); adversarial input with an inflated RLP length field; upstream code slicing the receipt short after stripping a prefix it shouldn't have.
Common situations: Fixed 512/1024-byte buffers truncating large receipts with many logs; a relayer relaying only the first N bytes; test fixtures copied from logs with ellipsis.
Related errors
- logs payload extends past receipt boundary
- block header payload extends past available data
- empty receipt RLP
- receipt must be an RLP list
- logs must be an RLP list
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/43a81e1d9e008fe7.
Report an issue: GitHub.