fix(p2p): fall back to archiver in BLOCK_TXS response validation - #23624
Merged
PhilWindle merged 1 commit intoMay 29, 2026
Merged
Conversation
PhilWindle
approved these changes
May 29, 2026
danielntmd
pushed a commit
to danielntmd/aztec-packages
that referenced
this pull request
Jun 4, 2026
BEGIN_COMMIT_OVERRIDE test(e2e): unskip pipelining related e2e tests (AztecProtocol#23642) fix(archiver): prune blocks without proposed checkpoint by end of build slot (AztecProtocol#23606) test: migrate benchmarks to pipelining setup (AztecProtocol#23647) fix(p2p): fall back to archiver in BLOCK_TXS response validation (AztecProtocol#23624) docs(slashing): align operator and slasher docs with AZIP-7 (AztecProtocol#23494) fix(p2p): do not penalize peers that signal a missing block with Fr.ZERO (AztecProtocol#23672) chore: adjust metrics deployment (AztecProtocol#23676) fix(cheat-codes): warpL2TimeAtLeastBy advances relative to leading clock (AztecProtocol#23675) chore: tighten node pool sizes (AztecProtocol#23678) chore: remove archival nodes (AztecProtocol#23630) chore: merge blob sink duties into RPC node (AztecProtocol#23631) fix: sync avm-transpiler Cargo.lock with noir submodule (AztecProtocol#23683) fix(spartan): set validator lag env vars in tps-scenario (AztecProtocol#23684) fix: make world-state hash queries reorg-aware to close getWorldState race (AztecProtocol#23677) fix: pin noir submodule to next's version on merge-train/spartan (AztecProtocol#23690) fix: ensure image ref is used by bench runner (AztecProtocol#23682) fix(ci): retry aztec-nr nargo dependency clone on transient network flake (AztecProtocol#23653) chore: run one-off jobs on network nodes (AztecProtocol#23701) fix: simulate proposals inside target slot (AztecProtocol#23692) chore: smaller eth-devnet (AztecProtocol#23704) chore: enable testnet autoscaling (AztecProtocol#23705) feat(api)!: redesign node log retrieval API around tag-based queries (AztecProtocol#23625) fix(sequencer): set own proposed checkpoint locally instead of via p2p loopback (AztecProtocol#23659) END_COMMIT_OVERRIDE
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
Libp2pService.validateRequestedBlockTxsConsistencyrejects every BLOCK_TXS reqresp response for any block whose proposal is not in the local attestation pool. The responder handler already falls back to the archiver in this case, the validator did not. So any node that doesn't have the proposal locally — but does know the block from the archiver — cannot collect its missing txs, and instead storms its peers until it is rate-limited and disconnected.This PR teaches the validator the same proposal-or-archiver fallback the handler already uses, breaking the storm at its source.
The defect: validator/handler asymmetry
To verify that a peer's BLOCK_TXS response matches the block, the validator needs the canonical tx-hash list for the block. Until this PR it consulted only the attestation pool:
The responder handler (
p2p/src/services/reqresp/protocols/block_txs/block_txs_handler.ts:40-45) already serves from either source:So peers can produce valid responses for blocks that are only known via the archiver, but the validator at the other end rejects them.
When this fires
Any p2p-enabled node subscribes to
block_proposalgossip (libp2p_service.ts:575) and stores received proposals into its attestation pool (validateAndStoreBlockProposalatlibp2p_service.ts:1236, callingtryAddBlockProposalat line 1252). Neither subscription nor storage is gated bydisableValidator— that flag only controls the validator-client (attestation signing). So in the steady state, a node that was online when a proposal was gossiped does have it locally.The validator's lookup fails whenever the node lacks the proposal in its local attestation pool, yet still needs to collect the block's txs over reqresp. The real-world triggers we've seen and can describe:
ProverNode.gatherTxs(prover-node/src/prover-node.ts:330) →TxProvider.getTxsForBlock→TxCollection.collectFastFor({type:'block', ...})→BatchTxRequesterfor any block whose proposal it doesn't happen to hold: prover restart, gossip drop, mesh churn during the epoch, etc.In every case the prover (or any node) has the mined block in its archiver but no proposal in its attestation pool. Until this PR the validator only consulted the attestation pool, so every otherwise-valid response was rejected.
The self-inflicted ban-storm
In
BatchTxRequester(p2p/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts):The dumb worker loop (
batch_tx_requester.ts:261-304) has no inter-iteration sleep and ten parallel workers (dumbParallelWorkerCount: 10) round-robin the peers. Per-peer in-flight de-duplication caps it at one request in flight per peer, but the steady-state hit-rate per peer easily exceeds the responder's per-peer GCRA cap.The penalty arrives from the responder side, on the requester:
With ~1 GCRA denial per second per peer, the prover loses 2 points/sec at each responder. The first responder hits -50 in ~25 s and goodbyes the prover via
peer_manager.ts:601-603 pruneUnhealthyPeers(GoodByeReason.LOW_SCORE); -100 in ~50 s would ban.Fix
A four-line change in
yarn-project/p2p/src/services/libp2p/libp2p_service.ts: fall back to the archiver after the attestation-pool lookup, mirroring the responder handler. If neither source has the block we still returnfalsewithout penalising the peer (it really is unverifiable locally).The validator's other checks (archive-root match, bitvector length, no dupes, size bounds, subset-membership, ordering) are unchanged.
Tests
Unit anchor —
p2p/src/services/libp2p/libp2p_service.test.tsExpected: true, Received: false) and green after.End-to-end —
end-to-end/src/e2e_p2p/late_prover_tx_collection.test.tsValidators form a mesh and mine a block carrying real txs; a prover joins after the block is mined, so it has the block in its archiver but never received the proposal or txs over gossip. The test then drives the exact production path the prover would take to gather txs for proving:
collected.length === 0(validation rejects every response, dumb loop runs until the deadline), assertion fails.block.body.txEffects's txs are collected.