fix(p2p)!: fix BLOCK_TXS response under proposer equivocation#23786
Merged
Conversation
spalladino
approved these changes
Jun 2, 2026
Comment on lines
+73
to
+74
| const responseTxs = (await txPool.getTxsByHash(requestedTxsHashes)).filter(tx => !!tx); | ||
| const response = new BlockTxsResponse(new TxArray(...responseTxs), responseBitVector); | ||
| const response = new BlockTxsResponse(new TxArray(...responseTxs), availableIndicesBitVector); |
Contributor
There was a problem hiding this comment.
Shouldn't we check if requestedTxHashes is non-empty before this, and if it is, return NOT_FOUND?
Contributor
Author
There was a problem hiding this comment.
For simplicity I'm choosing to always try to service and return a response. NOT_FOUND is not a valid response anymore. The requester can now derive if the peer has the block from the bit vector (or better, using peerHasBlock(), and then can look at the array to see what the peer was able to return.
The only concern I see is that the "nothing found" path would be slightly bigger on the network, but still sth like 2 bytes (instead of possibly 1).
Base automatically changed from
fc/fix-block-txs-validation
to
merge-train/spartan
June 2, 2026 01:56
12c4d5c to
c8f703b
Compare
c8f703b to
2be45eb
Compare
2be45eb to
75c2843
Compare
danielntmd
pushed a commit
to danielntmd/aztec-packages
that referenced
this pull request
Jun 4, 2026
BEGIN_COMMIT_OVERRIDE chore: deploy next-net and reuse contracts (AztecProtocol#23761) chore: turn on autoscaling (AztecProtocol#23706) chore: rename staging-public to staging (AztecProtocol#23767) chore(p2p): use sync hash for tx validation hashing (AztecProtocol#23768) test(e2e): wait warmup slots in slashing tests (AztecProtocol#23719) feat(api)!: make getTxReceipt the single tx-lookup API (AztecProtocol#23660) fix: cap cloned n_tps fees within sponsored FPC balance (AztecProtocol#23770) fix: protect HA validator Postgres from cluster scale-down (AztecProtocol#23772) refactor: remove non-pipelining sequencer code path (AztecProtocol#23665) feat(archiver): add getL2ToL1MembershipWitness node RPC (AztecProtocol#23646) fix(p2p)!: revamp BLOCK_TXS validations (AztecProtocol#23778) chore: name the bots (AztecProtocol#23795) fix(e2e): ensure BBSync init (AztecProtocol#23793) fix(p2p)!: fix BLOCK_TXS response under proposer equivocation (AztecProtocol#23786) fix: reconnect L1 port-forward after epoch-boundary sleep in n_tps_prove (AztecProtocol#23800) chore: add empty vscode settings for yarn-project (AztecProtocol#23808) fix(sequencer): only warn about missing proposed checkpoint once overdue (AztecProtocol#23807) fix: refresh n_tps fee quotes during sustained benchmark (AztecProtocol#23797) fix(sequencer): enforce build-frame deadlines and align attestation/publish windows (AztecProtocol#23776) END_COMMIT_OVERRIDE
spalladino
added a commit
that referenced
this pull request
Jun 4, 2026
## Motivation `v5-next` was cut from `next` at cbc99df (Jun 1), so PRs merged to `merge-train/spartan` after the cut never flowed into it. This backports all of them (authored by @spalladino and @fcarreiro) to keep v5-next current with the spartan train. ## Approach Each PR is cherry-picked from its squashed merge commit on `merge-train/spartan`, in merge order, preserving the original commit message and PR number — one commit per backported PR. All 11 applied cleanly with no conflicts; patches are identical to the originals (verified via `git patch-id`), and `bootstrap.sh build yarn-project` passes on the result. Labeled `ci-no-squash` to preserve the per-PR commits. ## Backported PRs - #23768 — chore(p2p): use sync hash for tx validation hashing - #23719 — test(e2e): wait warmup slots in slashing tests - #23660 — feat(api)!: make getTxReceipt the single tx-lookup API - #23665 — refactor: remove non-pipelining sequencer code path - #23646 — feat(archiver): add getL2ToL1MembershipWitness node RPC - #23778 — fix(p2p)!: revamp BLOCK_TXS validations - #23786 — fix(p2p)!: fix BLOCK_TXS response under proposer equivocation - #23808 — chore: add empty vscode settings for yarn-project - #23807 — fix(sequencer): only warn about missing proposed checkpoint once overdue - #23776 — fix(sequencer): enforce build-frame deadlines and align attestation/publish windows - #23818 — chore(p2p): BlockTxsRequest comment Note #23660, #23778, and #23786 are breaking changes (node RPC + tx-effect db format, and p2p wire format respectively), as they were on `next`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes A-1070: a malicious proposer who sends two different proposals with the same archive root but different tx sets could make two honest nodes fail the
BLOCK_TXSexchange and penalize each other.In the
BLOCK_TXSprotocol the requester asks for txs by their index within a block (proposal), identified only by its archive root. If an equivocating proposer gives node A and node B two proposals that share an archive root but differ in their tx list, then:[i, j, …]of "the block with this archive root".validateRequestedBlockTxsConsistencyrejects the response and penalizes B — an honest node punished for honest behavior.Fix
The request now carries a commitment to the full set of block tx hashes (
blockTxHashesCommitment, a SHA-256 over the serialized tx hashes) alongside the archive root. The responder only serves txs by index (and advertises availability via the bitvector) when its own block's tx-hash commitment matches the request's. Otherwise it treats the request as "I don't have that block" — returning an empty bitvector and only servicing any explicitly-requested tx hashes — so neither side is penalized for an equivocation it didn't cause.This closes the gap that the archive root alone could not: identical archive roots no longer imply identical tx sets.
Why not use proposal hash?
That would work when the BLOCK_TXS request is from a proposal, but it cannot be used when it's done from a block (e.g., in the prover node).
Changes
BlockTxsRequestgains ablockTxHashesCommitmentfield and acomputeBlockTxHashesCommitmenthelper; serialization andfromTxsSourceAndMissingTxsupdated accordingly.reqRespBlockTxsHandlerverifies the commitment before serving txs by index; on mismatch it falls back to the "block not available" path instead of returning indexed txs.reqRespBlockTxsHandlerno longer returnsNOT_FOUND. It always services what it can — explicitly-requested tx hashes, plus index-based txs when the commitment matches — and signals that it does not have the block via an empty bitvector. Requesters detect this throughBlockTxsResponse.peerHasBlock(). Duplicate requested hashes (by index and by explicit hash) are de-duplicated before the pool lookup.BatchTxRequester: a peer that lacks the block (empty bitvector) is demoted to dumb without penalty.handleFailResponseFromPeernow penalizesNOT_FOUNDalong withFAILURE/UNKNOWN(sinceNOT_FOUNDis no longer a legitimate response), while still demoting-without-penalty onINTERNAL_ERROR(consistency-validation failure).extractHashesPeerHasFromResponseis simplified to map bitvector indices directly to block tx hashes.BLOCK_TXSvalidation revamp commit (consistency checks on the requester side, response no longer echoes the archive root).block_txs,block_txs_handler,batch_tx_requester,libp2p_service, and the integration suite: the "no block" case now expects an empty, block-lessSUCCESSresponse instead ofNOT_FOUND; the obsolete "demote onNOT_FOUNDwithout penalizing" test was removed (legitimate demotion is now the empty-bitvector path). Added a handler test covering the equivocation case (different proposal under the same archive root → responder refuses to serve by index) and one asserting no duplicate txs are served when the same tx is requested by index and by repeated explicit hash.Closes https://linear.app/aztec-labs/issue/A-1070/malicious-proposer-can-make-honest-nodes-to-fail-tx-validation .