{"record":{"id":"3107473cb3185408","repo":"linera-io/linera-protocol","slug":"too-many-proof-nodes-max","errorCode":null,"errorMessage":"too many proof nodes: {} (max {})","messagePattern":"too many proof nodes: (.+?) \\(max (.+?)\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"linera-bridge/src/proof/mod.rs","lineNumber":349,"sourceCode":"const MAX_PROOF_NODES: usize = 32;\n\n/// Maximum total bytes across all proof nodes (32 KiB).\n///\n/// Each MPT branch node has 17 children (16 nibbles + value), each a 32-byte hash\n/// plus RLP overhead, for a worst-case size of ~600 bytes per node.\n/// With 32 nodes that's ~19,200 bytes; 32 KiB (32,768) is ~1.7× the theoretical max.\nconst MAX_PROOF_BYTES: usize = 32 * 1024;\n\n/// Verifies that a receipt is included in the receipts trie via MPT proof.\n///\n/// Enforces DoS limits on the proof size before forwarding to the trie verifier.\npub fn verify_receipt_inclusion(\n    receipts_root: B256,\n    tx_index: u64,\n    receipt_rlp: &[u8],\n    proof_nodes: &[Bytes],\n) -> Result<()> {\n    ensure!(\n        proof_nodes.len() <= MAX_PROOF_NODES,\n        \"too many proof nodes: {} (max {})\",\n        proof_nodes.len(),\n        MAX_PROOF_NODES\n    );\n    let total_bytes: usize = proof_nodes.iter().map(|n| n.len()).sum();\n    ensure!(\n        total_bytes <= MAX_PROOF_BYTES,\n        \"proof too large: {total_bytes} bytes (max {MAX_PROOF_BYTES})\",\n    );\n\n    let key = receipt_trie_key(tx_index);\n    alloy_trie::proof::verify_proof(receipts_root, key, Some(receipt_rlp.to_vec()), proof_nodes)\n        .map_err(|e| anyhow!(\"MPT proof verification failed: {e}\"))\n}\n\n/// Decodes a receipt's RLP and extracts its logs.\n///","sourceCodeStart":331,"sourceCodeEnd":367,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-bridge/src/proof/mod.rs#L331-L367","documentation":"verify_receipt_inclusion enforces MAX_PROOF_NODES = 32 on the Merkle-Patricia-Trie proof node list before running verification. The constant is sized ~3x the theoretical maximum receipts-trie depth (an Ethereum block fits ~1,400 txs, giving depth ~11), so more than 32 nodes is either malicious (DoS) or not a receipts proof at all.","triggerScenarios":"Submitting a receipt inclusion proof whose node array exceeds 32 entries: hand-crafted adversarial proofs in fuzzing, or mistakenly passing an account/state-trie proof (deeper tree) where a receipts proof belongs.","commonSituations":"Bridge/relayer tests generating proofs from the state trie instead of the receipts trie; a malicious relayer padding the node list; upstream proof builder emitting every trie level including leaf+extension for a pathological key.","solutions":["Confirm the proof comes from eth_getProof-style receipts data for the right block, and that the trie root is the block header's receiptsRoot","If legitimately deeper trees are expected on your chain, re-derive MAX_PROOF_NODES from its gas limit/tx throughput rather than removing the check","Treat repeated occurrences from one source as adversarial and reject the relayer's messages"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Cheap pre-check mirroring the library's DoS bound (MAX_PROOF_NODES = 32)\nconst MAX_PROOF_NODES: usize = 32;\nif proof_nodes.len() > MAX_PROOF_NODES {\n    return Err(anyhow::anyhow!(\"rejecting proof: {} nodes\", proof_nodes.len()));\n}\nverify_receipt_inclusion(root, tx_index, receipt, proof_nodes)?;","typeGuard":null,"tryCatchPattern":"match verify_receipt_inclusion(root, idx, rlp, nodes) {\n    Ok(()) => Ok(()),\n    Err(e) if e.to_string().contains(\"too many proof nodes\") => {\n        // not a legitimate receipts proof: drop message, do not retry\n        Err(anyhow::anyhow!(\"malicious or wrong-trie proof rejected\"))\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Reject submissions exceeding the node cap before they reach the verifier","Verify proofs are built against receiptsRoot (not stateRoot) so trie depth stays bounded","Monitor rejection rates per relayer to detect adversarial sources"],"tags":["ethereum","merkle-proof","mpt","dos-protection","bridge","rust"],"backgroundTag":"proof-too-large","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}