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

  1. Rebuild the proof from a trusted source and confirm each node is a single RLP-encoded trie node (<~700 bytes)
  2. Reject and alert on submissions from sources repeatedly hitting the cap
  3. 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

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


AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22). Data as JSON: /api/errors/d9ec8ba21e1b1d96. Report an issue: GitHub.