linera-io/linera-protocol · error
proof too large: {total_bytes} bytes (max {MAX_PROOF_BYTES})
Error message
proof too large: {total_bytes} bytes (max {MAX_PROOF_BYTES}) What it means
The second DoS guard in verify_receipt_inclusion: it sums the byte length of all proof nodes and requires <= MAX_PROOF_BYTES (32 KiB, ~1.7x the theoretical worst case of ~600 bytes x 32 nodes). Oversized total means bloated or malicious nodes, independent of node count.
Source
Thrown at linera-bridge/src/proof/mod.rs:356
const MAX_PROOF_BYTES: usize = 32 * 1024;
/// Verifies that a receipt is included in the receipts trie via MPT proof.
///
/// Enforces DoS limits on the proof size before forwarding to the trie verifier.
pub fn verify_receipt_inclusion(
receipts_root: B256,
tx_index: u64,
receipt_rlp: &[u8],
proof_nodes: &[Bytes],
) -> Result<()> {
ensure!(
proof_nodes.len() <= MAX_PROOF_NODES,
"too many proof nodes: {} (max {})",
proof_nodes.len(),
MAX_PROOF_NODES
);
let total_bytes: usize = proof_nodes.iter().map(|n| n.len()).sum();
ensure!(
total_bytes <= MAX_PROOF_BYTES,
"proof too large: {total_bytes} bytes (max {MAX_PROOF_BYTES})",
);
let key = receipt_trie_key(tx_index);
alloy_trie::proof::verify_proof(receipts_root, key, Some(receipt_rlp.to_vec()), proof_nodes)
.map_err(|e| anyhow!("MPT proof verification failed: {e}"))
}
/// Decodes a receipt's RLP and extracts its logs.
///
/// 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.View on GitHub (pinned to 6c226ddcb3)
Solutions
- Rebuild the proof from a trusted source and confirm each node is a single RLP-encoded trie node (<~700 bytes)
- Reject and alert on submissions from sources repeatedly hitting the cap
- If raising the cap for a high-throughput chain, recompute the bound from node-count x max-node-size, keeping headroom
Defensive patterns
Strategy: validation
Validate before calling
// Pre-check total size before verification (MAX_PROOF_BYTES = 32 KiB)
let total: usize = proof_nodes.iter().map(|n| n.len()).sum();
if total > 32 * 1024 {
return Err(anyhow::anyhow!("proof payload {total} B exceeds 32 KiB budget"));
} Try / catch
match verify_receipt_inclusion(root, idx, rlp, nodes) {
Ok(()) => Ok(()),
Err(e) if e.to_string().contains("proof too large") => {
Err(anyhow::anyhow!("oversized proof from peer; rate-limit or ban source"))
}
Err(e) => Err(e),
} Prevention
- Build proofs with a library that emits one RLP node per entry, not whole-trie serializations
- Enforce message size caps at the network layer before deserializing proof payloads
- Keep the byte budget derived from node-count x max-node-size when re-tuning constants
When it happens
Trigger: A proof with few nodes but each padded to be huge (e.g. 2 nodes of 20 KiB); a malformed node list containing embedded extra data; passing unencoded (raw trie structure serialized wholesale) nodes rather than per-node RLP.
Common situations: Relayer bugs that serialize the whole trie instead of the proof path; adversarial submissions probing the bridge's verification limits; upstream library change in node encoding inflating sizes.
Related errors
- too many proof nodes: {} (max {})
- Writer limit exceeded
- block header must be an RLP list
- block header payload extends past available data
- empty receipt RLP
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/d9ec8ba21e1b1d96.
Report an issue: GitHub.