linera-io/linera-protocol · error
block header payload extends past available data
Error message
block header payload extends past available data
What it means
After decoding the outer list header, decode_block_header checks that the remaining bytes cover the declared payload_length. alloy_rlp::Header only strips the prefix, not the payload, so if the buffer is truncated relative to the declared size this ensure! fires. It is a deliberate bounds check that prevents out-of-bounds slicing at `&data[..payload_length]` and DoS via oversized length claims.
Source
Thrown at linera-bridge/src/proof/mod.rs:309
let (root, proof) = super::build_receipt_proof(receipts, target_tx_index);
(root, proof.into_iter().map(Into::into).collect())
}
}
/// Decodes an RLP-encoded Ethereum block header, returning `(block_hash, receipts_root)`.
///
/// The block hash is `keccak256(header_rlp)`. The receipts root is field index 5
/// in the RLP list.
pub fn decode_block_header(header_rlp: &[u8]) -> Result<(B256, B256)> {
let block_hash = keccak256(header_rlp);
let mut data = header_rlp;
let list_header =
alloy_rlp::Header::decode(&mut data).map_err(|e| anyhow!("invalid RLP header: {e}"))?;
ensure!(list_header.list, "block header must be an RLP list");
// Limit reads to the header's declared payload.
ensure!(
data.len() >= list_header.payload_length,
"block header payload extends past available data"
);
let mut data = &data[..list_header.payload_length];
// Skip first 5 fields: parentHash, ommersHash, beneficiary, stateRoot, transactionsRoot
for i in 0..5 {
skip_rlp_item(&mut data).map_err(|e| anyhow!("failed to skip header field {i}: {e}"))?;
}
// Field index 5: receiptsRoot (B256)
let receipts_root = <B256 as alloy_rlp::Decodable>::decode(&mut data)
.map_err(|e| anyhow!("failed to decode receipts_root: {e}"))?;
Ok((block_hash, receipts_root))
}
/// Maximum number of MPT proof nodes accepted.View on GitHub (pinned to 6c226ddcb3)
Solutions
- Log the input length vs declared payload_length to confirm truncation, then fix the upstream producer/transport
- Fetch headers with full bodies from a trusted RPC and pass bytes through unmodified
- For cross-chain support of non-standard headers (e.g. OP-stack extra fields), verify against the chain's actual header spec before parsing
Defensive patterns
Strategy: try-catch
Try / catch
match decode_block_header(header_rlp) {
Ok(v) => v,
Err(e) if e.to_string().contains("extends past available data") => {
// truncated or malformed input: re-fetch rather than retry
anyhow::bail!("truncated block header ({} bytes); refetch from RPC", header_rlp.len())
}
Err(e) => return Err(e),
} Prevention
- Use length-prefixed framing or complete-response checks when transporting RLP blobs
- Never slice header bytes manually before decoding; pass the full buffer
- Log input length alongside parse errors to spot truncation immediately
When it happens
Trigger: Passing a header that was cut short (partial read from RPC, truncated proof, split message framing); malformed input where the RLP length prefix declares more bytes than actually follow.
Common situations: Fixed-size buffer truncation upstream; an L2/non-standard chain emitting headers with extra fields after receiptsRoot such that an intermediate parser sliced too early; network transport delivering partial frames.
Related errors
- block header must be an RLP list
- receipt payload extends past available data
- logs payload extends past receipt boundary
- empty receipt RLP
- receipt must be an RLP list
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/dee3a6555265965c.
Report an issue: GitHub.