From 09bd1727544f4f32ffbf0b5aae352d245b4ebcab Mon Sep 17 00:00:00 2001 From: spypsy Date: Wed, 18 Feb 2026 12:03:36 +0000 Subject: [PATCH 1/2] fix: deflake e2e HA full test --- .../src/composed/ha/e2e_ha_full.test.ts | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/yarn-project/end-to-end/src/composed/ha/e2e_ha_full.test.ts b/yarn-project/end-to-end/src/composed/ha/e2e_ha_full.test.ts index 1ce73ebffd1c..415d03713b39 100644 --- a/yarn-project/end-to-end/src/composed/ha/e2e_ha_full.test.ts +++ b/yarn-project/end-to-end/src/composed/ha/e2e_ha_full.test.ts @@ -416,9 +416,9 @@ describe('HA Full Setup', () => { const round = await governanceProposer.computeRound(blockSlot); logger.info(`Block slot ${blockSlot}, governance round ${round}`); - // Poll L1 for governance votes + // Poll L1 until at least one governance vote appears logger.info('Polling L1 for governance votes...'); - const l1VoteCount = await retryUntil( + await retryUntil( async () => { const voteCount = Number( await governanceProposer.getPayloadSignals( @@ -433,11 +433,6 @@ describe('HA Full Setup', () => { 6, // timeout in seconds (30 attempts * 200ms) 0.2, // interval in seconds (200ms) ); - logger.info(`Found ${l1VoteCount} governance vote(s) on L1 for payload ${mockGovernancePayload.toString()}`); - - // Verify votes were actually sent to L1 - expect(l1VoteCount).toBeGreaterThan(0); - logger.info(`Verified ${l1VoteCount} governance vote(s) successfully sent to L1`); // Get L1 round info to determine which slots have actually landed on L1. // We anchor the comparison on L1's lastSignalSlot since: @@ -448,6 +443,17 @@ describe('HA Full Setup', () => { round, ); const lastSignalSlot = Number(roundInfo.lastSignalSlot); + + // Re-query L1 vote count after getting lastSignalSlot in case more votes landed between the poll and getRoundInfo: + // the retryUntil may return a stale count if more votes land between the poll and getRoundInfo + const l1VoteCount = Number( + await governanceProposer.getPayloadSignals( + deployL1ContractsValues.l1ContractAddresses.rollupAddress.toString(), + round, + mockGovernancePayload.toString(), + ), + ); + expect(l1VoteCount).toBeGreaterThan(0); logger.info( `L1 round ${round} info: lastSignalSlot=${lastSignalSlot}, l1VoteCount=${l1VoteCount}, payloadWithMostSignals=${roundInfo.payloadWithMostSignals}`, ); From 7595aacdc0303fe2c0b9597638da3716593da718 Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Wed, 18 Feb 2026 12:08:25 +0000 Subject: [PATCH 2/2] fix(p2p): wait for GossipSub mesh formation before sending txs in e2e tests (#20626) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Fixes flaky `e2e_p2p_network > should rollup txs from all peers` test by waiting for GossipSub mesh formation (not just peer connections) before sending transactions - Adds `getGossipMeshPeerCount` method through the P2P stack (`P2PService` → `P2PClient` → test helpers) - Enhances `waitForP2PMeshConnectivity` to wait for each node to have at least 1 mesh peer on the tx topic ## Root cause GossipSub requires heartbeat cycles (~700ms each) after peer connections to form the actual message-routing mesh. The test was sending transactions immediately after connections were established but before the mesh formed. With `allowPublishToZeroTopicPeers: true`, the publish silently succeeded with 0 recipients, and the tx expired from the gossipsub cache before the mesh was ready. ## Test plan - Build passes (`yarn build`) - The flaky test `e2e_p2p/gossip_network.test.ts` should no longer time out waiting for transactions to propagate 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.6 --- .../end-to-end/src/e2e_p2p/p2p_network.ts | 22 +++++++++++++++++++ yarn-project/p2p/src/client/interface.ts | 11 +++++++++- yarn-project/p2p/src/client/p2p_client.ts | 5 +++++ .../p2p/src/services/dummy_service.ts | 6 ++++- .../p2p/src/services/libp2p/libp2p_service.ts | 4 ++++ yarn-project/p2p/src/services/service.ts | 11 +++++++++- .../p2p/src/test-helpers/mock-pubsub.ts | 10 +++++++++ yarn-project/p2p/src/util.ts | 8 ++++++- .../txe/src/state_machine/dummy_p2p_client.ts | 6 ++++- 9 files changed, 78 insertions(+), 5 deletions(-) diff --git a/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts b/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts index ed470053f9b0..bdb752da9f00 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts @@ -25,6 +25,7 @@ import type { BootstrapNode } from '@aztec/p2p/bootstrap'; import { createBootstrapNodeFromPrivateKey, getBootstrapNodeEnr } from '@aztec/p2p/test-helpers'; import { tryStop } from '@aztec/stdlib/interfaces/server'; import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts'; +import { TopicType } from '@aztec/stdlib/p2p'; import type { PublicDataTreeLeaf } from '@aztec/stdlib/trees'; import { ZkPassportProofParams } from '@aztec/stdlib/zkpassport'; import { getGenesisValues } from '@aztec/world-state/testing'; @@ -431,6 +432,27 @@ export class P2PNetworkTest { ); this.logger.warn('All nodes connected to P2P mesh'); + + // Wait for GossipSub mesh to form for the tx topic. + // We only require at least 1 mesh peer per node because GossipSub + // stops grafting once it reaches Dlo peers and won't fill the mesh to all available peers. + this.logger.warn('Waiting for GossipSub mesh to form for tx topic...'); + await Promise.all( + nodes.map(async (node, index) => { + const p2p = node.getP2P(); + await retryUntil( + async () => { + const meshPeers = await p2p.getGossipMeshPeerCount(TopicType.tx); + this.logger.debug(`Node ${index} has ${meshPeers} gossip mesh peers for tx topic`); + return meshPeers >= 1 ? true : undefined; + }, + `Node ${index} to have gossip mesh peers for tx topic`, + timeoutSeconds, + checkIntervalSeconds, + ); + }), + ); + this.logger.warn('All nodes have gossip mesh peers for tx topic'); } async teardown() { diff --git a/yarn-project/p2p/src/client/interface.ts b/yarn-project/p2p/src/client/interface.ts index 9585dd6b89e8..6199b7158fe0 100644 --- a/yarn-project/p2p/src/client/interface.ts +++ b/yarn-project/p2p/src/client/interface.ts @@ -1,7 +1,13 @@ import type { SlotNumber } from '@aztec/foundation/branded-types'; import type { EthAddress, L2BlockId } from '@aztec/stdlib/block'; import type { ITxProvider, P2PApiFull } from '@aztec/stdlib/interfaces/server'; -import type { BlockProposal, CheckpointAttestation, CheckpointProposal, P2PClientType } from '@aztec/stdlib/p2p'; +import type { + BlockProposal, + CheckpointAttestation, + CheckpointProposal, + P2PClientType, + TopicType, +} from '@aztec/stdlib/p2p'; import type { BlockHeader, Tx, TxHash } from '@aztec/stdlib/tx'; import type { PeerId } from '@libp2p/interface'; @@ -237,4 +243,7 @@ export type P2P = P2PApiFull & /** If node running this P2P stack is validator, passes in validator address to P2P layer */ registerThisValidatorAddresses(address: EthAddress[]): void; + + /** Returns the number of peers in the GossipSub mesh for a given topic type. */ + getGossipMeshPeerCount(topicType: TopicType): Promise; }; diff --git a/yarn-project/p2p/src/client/p2p_client.ts b/yarn-project/p2p/src/client/p2p_client.ts index 6daad444646c..2e164bd80361 100644 --- a/yarn-project/p2p/src/client/p2p_client.ts +++ b/yarn-project/p2p/src/client/p2p_client.ts @@ -25,6 +25,7 @@ import { CheckpointAttestation, type CheckpointProposal, type P2PClientType, + type TopicType, } from '@aztec/stdlib/p2p'; import type { BlockHeader, Tx, TxHash } from '@aztec/stdlib/tx'; import { Attributes, type TelemetryClient, WithTracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client'; @@ -167,6 +168,10 @@ export class P2PClient return Promise.resolve(this.p2pService.getPeers(includePending)); } + public getGossipMeshPeerCount(topicType: TopicType): Promise { + return Promise.resolve(this.p2pService.getGossipMeshPeerCount(topicType)); + } + public getL2BlockHash(number: BlockNumber): Promise { return this.l2Tips.getL2BlockHash(number); } diff --git a/yarn-project/p2p/src/services/dummy_service.ts b/yarn-project/p2p/src/services/dummy_service.ts index 44e6c4367512..3b0c57eb2651 100644 --- a/yarn-project/p2p/src/services/dummy_service.ts +++ b/yarn-project/p2p/src/services/dummy_service.ts @@ -1,6 +1,6 @@ import type { EthAddress } from '@aztec/foundation/eth-address'; import type { PeerInfo } from '@aztec/stdlib/interfaces/server'; -import type { Gossipable, PeerErrorSeverity } from '@aztec/stdlib/p2p'; +import type { Gossipable, PeerErrorSeverity, TopicType } from '@aztec/stdlib/p2p'; import { Tx, TxHash } from '@aztec/stdlib/tx'; import type { PeerId } from '@libp2p/interface'; @@ -44,6 +44,10 @@ export class DummyP2PService implements P2PService { return []; } + getGossipMeshPeerCount(_topicType: TopicType): number { + return 0; + } + /** * Starts the dummy implementation. * @returns A resolved promise. diff --git a/yarn-project/p2p/src/services/libp2p/libp2p_service.ts b/yarn-project/p2p/src/services/libp2p/libp2p_service.ts index 617e5bb7db11..3274cf749197 100644 --- a/yarn-project/p2p/src/services/libp2p/libp2p_service.ts +++ b/yarn-project/p2p/src/services/libp2p/libp2p_service.ts @@ -615,6 +615,10 @@ export class LibP2PService extends return this.peerManager.getPeers(includePending); } + public getGossipMeshPeerCount(topicType: TopicType): number { + return this.node.services.pubsub.getMeshPeers(this.topicStrings[topicType]).length; + } + private handleGossipSubEvent(e: CustomEvent) { this.logger.trace(`Received PUBSUB message.`); diff --git a/yarn-project/p2p/src/services/service.ts b/yarn-project/p2p/src/services/service.ts index b7b3b6fa42d0..62dcaa1c2c51 100644 --- a/yarn-project/p2p/src/services/service.ts +++ b/yarn-project/p2p/src/services/service.ts @@ -1,7 +1,13 @@ import type { SlotNumber } from '@aztec/foundation/branded-types'; import type { EthAddress } from '@aztec/foundation/eth-address'; import type { PeerInfo } from '@aztec/stdlib/interfaces/server'; -import type { BlockProposal, CheckpointAttestation, CheckpointProposalCore, Gossipable } from '@aztec/stdlib/p2p'; +import type { + BlockProposal, + CheckpointAttestation, + CheckpointProposalCore, + Gossipable, + TopicType, +} from '@aztec/stdlib/p2p'; import type { Tx } from '@aztec/stdlib/tx'; import type { PeerId } from '@libp2p/interface'; @@ -130,6 +136,9 @@ export interface P2PService { getPeers(includePending?: boolean): PeerInfo[]; + /** Returns the number of peers in the GossipSub mesh for a given topic type. */ + getGossipMeshPeerCount(topicType: TopicType): number; + validate(txs: Tx[]): Promise; addReqRespSubProtocol( diff --git a/yarn-project/p2p/src/test-helpers/mock-pubsub.ts b/yarn-project/p2p/src/test-helpers/mock-pubsub.ts index 12e227d9461c..03d50870945d 100644 --- a/yarn-project/p2p/src/test-helpers/mock-pubsub.ts +++ b/yarn-project/p2p/src/test-helpers/mock-pubsub.ts @@ -270,6 +270,16 @@ class MockGossipSubService extends TypedEventEmitter implements { msgId, propagationSource, acceptance }, ); } + + getMeshPeers(topic?: TopicStr): PeerIdStr[] { + if (topic && !this.subscribedTopics.has(topic)) { + return []; + } + return this.network + .getPeers() + .filter(peer => !this.peerId.equals(peer)) + .map(peer => peer.toString()); + } } /** diff --git a/yarn-project/p2p/src/util.ts b/yarn-project/p2p/src/util.ts index 8cfae711e134..ab14817d8b47 100644 --- a/yarn-project/p2p/src/util.ts +++ b/yarn-project/p2p/src/util.ts @@ -23,7 +23,13 @@ export interface PubSubLibp2p extends Pick & { score: Pick }; }; } diff --git a/yarn-project/txe/src/state_machine/dummy_p2p_client.ts b/yarn-project/txe/src/state_machine/dummy_p2p_client.ts index db9c94b94942..1cb90ade171f 100644 --- a/yarn-project/txe/src/state_machine/dummy_p2p_client.ts +++ b/yarn-project/txe/src/state_machine/dummy_p2p_client.ts @@ -17,7 +17,7 @@ import type { } from '@aztec/p2p'; import type { EthAddress, L2BlockStreamEvent, L2Tips } from '@aztec/stdlib/block'; import type { ITxProvider, PeerInfo } from '@aztec/stdlib/interfaces/server'; -import type { BlockProposal, CheckpointAttestation, CheckpointProposal } from '@aztec/stdlib/p2p'; +import type { BlockProposal, CheckpointAttestation, CheckpointProposal, TopicType } from '@aztec/stdlib/p2p'; import type { BlockHeader, Tx, TxHash } from '@aztec/stdlib/tx'; export class DummyP2P implements P2P { @@ -41,6 +41,10 @@ export class DummyP2P implements P2P { throw new Error('DummyP2P does not implement "getPeers"'); } + public getGossipMeshPeerCount(_topicType: TopicType): Promise { + return Promise.resolve(0); + } + public broadcastProposal(_proposal: BlockProposal): Promise { throw new Error('DummyP2P does not implement "broadcastProposal"'); }