linera-io/linera-protocol · error
not enough data to skip RLP item
Error message
not enough data to skip RLP item
What it means
skip_rlp_item is a helper used by decode_block_header and decode_receipt_logs to step over RLP items (status, cumulative gas used, logs bloom) by reading an alloy_rlp Header and advancing past header.payload_length. The ensure at linera-bridge/src/proof/mod.rs:537 fails when the remaining buffer is shorter than the declared payload, i.e. the byte string claims more content than actually present. This is the classic signature of truncated or corrupted RLP — often a receipt pulled from a bad Merkle-proof node.
Source
Thrown at linera-bridge/src/proof/mod.rs:537
for (key, value) in &entries {
builder.add_leaf(*key, value);
}
let root = builder.root();
let proof_nodes = builder.take_proof_nodes();
let proof = proof_nodes
.matching_nodes_sorted(&target_key)
.into_iter()
.map(|(_, bytes)| bytes.to_vec())
.collect();
(root, proof)
}
/// Skips one RLP item (string or list) by reading its header and advancing past the payload.
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"View on GitHub (pinned to 6c226ddcb3)
Solutions
- Log the failing field index (the caller wraps this as 'failed to skip receipt field {i}') to see how far decoding got before the shortfall.
- Re-fetch the receipt RLP and its proof from the node; verify the proof against the block's receipts_root before parsing.
- Check that the receipt bytes were not truncated in transit: total length should exactly cover the outer list header plus payload_length.
- Confirm the receipt type matches the chain's active EIPs (post-1559/4844 receipts carry typed prefixes; ensure the leading type byte was stripped before RLP decoding).
Example fix
// before
for i in 0..3 {
skip_rlp_item(&mut data).map_err(|e| anyhow!("failed to skip receipt field {i}: {e}"))?;
}
// after: fail fast with a boundary check before parsing
ensure!(data.len() >= list_header.payload_length, "receipt truncated: have {}, declared {}", data.len(), list_header.payload_length);
for i in 0..3 {
skip_rlp_item(&mut data).map_err(|e| anyhow!("failed to skip receipt field {i}: {e}"))?;
} Defensive patterns
Strategy: try-catch
Validate before calling
// Structural pre-check before decode_receipt_logs: outer receipt must cover its payload.
fn receipt_rlp_bounds_ok(mut data: &[u8]) -> bool {
if data.first() == Some(&0x80) || data.first().is_some_and(|b| *b < 0x80) { data = &data[1..]; }
match alloy_rlp::Header::decode(&mut data) {
Ok(h) => h.list && data.len() >= h.payload_length,
Err(_) => false,
}
} Try / catch
match decode_receipt_logs(receipt_rlp) {
Ok(logs) => logs,
Err(e) if e.to_string().contains("not enough data to skip RLP item") => {
// Truncated/corrupt node: drop the proof and re-fetch rather than retry the same bytes.
tracing::warn!(error = %e, "receipt RLP truncated; discarding proof");
Vec::new()
}
Err(e) => return Err(e),
} Prevention
- Always verify the receipt Merkle proof against the header's receipts_root before parsing RLP.
- Fetch nodes from a trusted archive provider and hex-decode strictly (handle 0x prefix, even length).
- Encode test receipts with alloy_rlp instead of hand-written length prefixes.
- Quarantine nodes that fail structural checks; count them to detect provider degradation.
When it happens
Trigger: decode_receipt_logs walks a receipt whose RLP was cut short (truncated proof node bytes, wrong trie node delivered for the key, hex/nibble mismatch in the receipt trie key); decode_block_header reads a header whose payload length exceeds the input; a test fixture hand-assembles receipt RLP with a wrong length prefix.
Common situations: Merkle proof nodes from an RPC provider are truncated or from a different block; receipt bytes were copied with an off-by-one offset (extra or missing prefix byte, see the 0x80 single-byte handling above); network transport or storage cut the payload; receipt format from a chain with extra fields (e.g., EIP-4844 blob gas) that shifts field boundaries so a skip lands mid-item.
Related errors
- log must be an RLP list
- log payload extends past available data
- topics must be an RLP list
- topics payload extends past log boundary
- log data must be a byte string, not a list
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/cfc0223e5de434fd.
Report an issue: GitHub.