Skip to content

Commit 820bcc6

Browse files
authored
fix: potential e2e-p2p fix (#10094)
1 parent cdabd85 commit 820bcc6

File tree

10 files changed

+174
-64
lines changed

10 files changed

+174
-64
lines changed

yarn-project/end-to-end/scripts/e2e_test_config.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ tests:
8585
e2e_p2p_upgrade_governance_proposer:
8686
test_path: 'e2e_p2p/upgrade_governance_proposer.test.ts'
8787
# https://github.com/AztecProtocol/aztec-packages/issues/9843
88-
# e2e_p2p_rediscovery:
89-
# test_path: 'e2e_p2p/rediscovery.test.ts'
88+
e2e_p2p_rediscovery:
89+
test_path: 'e2e_p2p/rediscovery.test.ts'
9090
e2e_p2p_reqresp:
9191
test_path: 'e2e_p2p/reqresp.test.ts'
9292
flakey_e2e_tests:

yarn-project/end-to-end/src/e2e_p2p/gossip_network.test.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { sleep } from '@aztec/aztec.js';
33

44
import fs from 'fs';
55

6-
// import { METRICS_PORT } from '../fixtures/fixtures.js';
6+
import { shouldCollectMetrics } from '../fixtures/fixtures.js';
77
import { type NodeContext, createNodes } from '../fixtures/setup_p2p_test.js';
88
import { P2PNetworkTest, WAIT_FOR_TX_TIMEOUT } from './p2p_network.js';
99
import { createPXEServiceAndSubmitTransactions } from './shared.js';
@@ -24,8 +24,8 @@ describe('e2e_p2p_network', () => {
2424
testName: 'e2e_p2p_network',
2525
numberOfNodes: NUM_NODES,
2626
basePort: BOOT_NODE_UDP_PORT,
27-
// Uncomment to collect metrics - run in aztec-packages `docker compose --profile metrics up`
28-
// metricsPort: METRICS_PORT,
27+
// To collect metrics - run in aztec-packages `docker compose --profile metrics up` and set COLLECT_METRICS=true
28+
metricsPort: shouldCollectMetrics(),
2929
});
3030
await t.applyBaseSnapshots();
3131
await t.setup();
@@ -44,6 +44,7 @@ describe('e2e_p2p_network', () => {
4444
if (!t.bootstrapNodeEnr) {
4545
throw new Error('Bootstrap node ENR is not available');
4646
}
47+
4748
// create our network of nodes and submit txs into each of them
4849
// the number of txs per node and the number of txs per rollup
4950
// should be set so that the only way for rollups to be built
@@ -57,8 +58,8 @@ describe('e2e_p2p_network', () => {
5758
NUM_NODES,
5859
BOOT_NODE_UDP_PORT,
5960
DATA_DIR,
60-
// Uncomment to collect metrics - run in aztec-packages `docker compose --profile metrics up`
61-
// METRICS_PORT,
61+
// To collect metrics - run in aztec-packages `docker compose --profile metrics up` and set COLLECT_METRICS=true
62+
shouldCollectMetrics(),
6263
);
6364

6465
// wait a bit for peers to discover each other

yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts

+49-11
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { getContract } from 'viem';
1212
import { privateKeyToAccount } from 'viem/accounts';
1313

1414
import {
15+
PRIVATE_KEYS_START_INDEX,
1516
createValidatorConfig,
1617
generateNodePrivateKeys,
1718
generatePeerIdPrivateKeys,
@@ -33,6 +34,7 @@ export class P2PNetworkTest {
3334

3435
public ctx!: SubsystemsContext;
3536
public nodePrivateKeys: `0x${string}`[] = [];
37+
public nodePublicKeys: string[] = [];
3638
public peerIdPrivateKeys: string[] = [];
3739

3840
public bootstrapNodeEnr: string = '';
@@ -51,7 +53,8 @@ export class P2PNetworkTest {
5153

5254
// Set up the base account and node private keys for the initial network deployment
5355
this.baseAccount = privateKeyToAccount(`0x${getPrivateKeyFromIndex(0)!.toString('hex')}`);
54-
this.nodePrivateKeys = generateNodePrivateKeys(1, numberOfNodes);
56+
this.nodePrivateKeys = generateNodePrivateKeys(PRIVATE_KEYS_START_INDEX, numberOfNodes);
57+
this.nodePublicKeys = this.nodePrivateKeys.map(privateKey => privateKeyToAccount(privateKey).address);
5558
this.peerIdPrivateKeys = generatePeerIdPrivateKeys(numberOfNodes);
5659

5760
this.bootstrapNodeEnr = bootstrapNode.getENR().encodeTxt();
@@ -108,16 +111,11 @@ export class P2PNetworkTest {
108111
const txHashes: `0x${string}`[] = [];
109112
for (let i = 0; i < this.numberOfNodes; i++) {
110113
const account = privateKeyToAccount(this.nodePrivateKeys[i]!);
114+
this.logger.debug(`Adding ${account.address} as validator`);
111115
const txHash = await rollup.write.addValidator([account.address]);
112116
txHashes.push(txHash);
113-
this.logger.debug(`Adding ${account.address} as validator`);
114117
}
115118

116-
// Remove the setup validator
117-
const initialValidatorAddress = privateKeyToAccount(`0x${getPrivateKeyFromIndex(0)!.toString('hex')}`).address;
118-
const txHash = await rollup.write.removeValidator([initialValidatorAddress]);
119-
txHashes.push(txHash);
120-
121119
// Wait for all the transactions adding validators to be mined
122120
await Promise.all(
123121
txHashes.map(txHash =>
@@ -150,12 +148,52 @@ export class P2PNetworkTest {
150148
});
151149
}
152150

151+
async removeInitialNode() {
152+
await this.snapshotManager.snapshot(
153+
'remove-inital-validator',
154+
async ({ deployL1ContractsValues, aztecNodeConfig }) => {
155+
const rollup = getContract({
156+
address: deployL1ContractsValues.l1ContractAddresses.rollupAddress.toString(),
157+
abi: RollupAbi,
158+
client: deployL1ContractsValues.walletClient,
159+
});
160+
161+
// Remove the setup validator
162+
const initialValidatorAddress = privateKeyToAccount(`0x${getPrivateKeyFromIndex(0)!.toString('hex')}`).address;
163+
const txHash = await rollup.write.removeValidator([initialValidatorAddress]);
164+
165+
await deployL1ContractsValues.publicClient.waitForTransactionReceipt({
166+
hash: txHash,
167+
});
168+
169+
//@note Now we jump ahead to the next epoch such that the validator committee is picked
170+
// INTERVAL MINING: If we are using anvil interval mining this will NOT progress the time!
171+
// Which means that the validator set will still be empty! So anyone can propose.
172+
const slotsInEpoch = await rollup.read.EPOCH_DURATION();
173+
const timestamp = await rollup.read.getTimestampForSlot([slotsInEpoch]);
174+
const cheatCodes = new EthCheatCodes(aztecNodeConfig.l1RpcUrl);
175+
try {
176+
await cheatCodes.warp(Number(timestamp));
177+
} catch (err) {
178+
this.logger.debug('Warp failed, time already satisfied');
179+
}
180+
181+
// Send and await a tx to make sure we mine a block for the warp to correctly progress.
182+
await deployL1ContractsValues.publicClient.waitForTransactionReceipt({
183+
hash: await deployL1ContractsValues.walletClient.sendTransaction({
184+
to: this.baseAccount.address,
185+
value: 1n,
186+
account: this.baseAccount,
187+
}),
188+
});
189+
190+
await this.ctx.aztecNode.stop();
191+
},
192+
);
193+
}
194+
153195
async setup() {
154196
this.ctx = await this.snapshotManager.setup();
155-
156-
// TODO(md): make it such that the test can set these up
157-
this.ctx.aztecNodeConfig.minTxsPerBlock = 4;
158-
this.ctx.aztecNodeConfig.maxTxsPerBlock = 4;
159197
}
160198

161199
async stopNodes(nodes: AztecNodeService[]) {

yarn-project/end-to-end/src/e2e_p2p/rediscovery.test.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ describe('e2e_p2p_rediscovery', () => {
2626
});
2727
await t.applyBaseSnapshots();
2828
await t.setup();
29+
30+
// We remove the initial node such that it will no longer attempt to build blocks / be in the sequencing set
31+
await t.removeInitialNode();
2932
});
3033

3134
afterEach(async () => {
@@ -61,7 +64,7 @@ describe('e2e_p2p_rediscovery', () => {
6164
const node = nodes[i];
6265
await node.stop();
6366
t.logger.info(`Node ${i} stopped`);
64-
await sleep(1200);
67+
await sleep(2500);
6568

6669
const newNode = await createNode(
6770
t.ctx.aztecNodeConfig,

yarn-project/end-to-end/src/e2e_p2p/reqresp.test.ts

+44-13
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import { type AztecNodeService } from '@aztec/aztec-node';
22
import { sleep } from '@aztec/aztec.js';
3+
import { RollupAbi } from '@aztec/l1-artifacts';
34

45
import { jest } from '@jest/globals';
56
import fs from 'fs';
7+
import { getContract } from 'viem';
68

79
import { type NodeContext, createNodes } from '../fixtures/setup_p2p_test.js';
810
import { P2PNetworkTest, WAIT_FOR_TX_TIMEOUT } from './p2p_network.js';
911
import { createPXEServiceAndSubmitTransactions } from './shared.js';
1012

1113
// Don't set this to a higher value than 9 because each node will use a different L1 publisher account and anvil seeds
12-
const NUM_NODES = 4;
14+
const NUM_NODES = 6;
1315
const NUM_TXS_PER_NODE = 2;
1416
const BOOT_NODE_UDP_PORT = 40800;
1517

@@ -27,6 +29,7 @@ describe('e2e_p2p_reqresp_tx', () => {
2729
});
2830
await t.applyBaseSnapshots();
2931
await t.setup();
32+
await t.removeInitialNode();
3033
});
3134

3235
afterEach(async () => {
@@ -37,10 +40,6 @@ describe('e2e_p2p_reqresp_tx', () => {
3740
}
3841
});
3942

40-
// NOTE: If this test fails in a PR where the shuffling algorithm is changed, then it is failing as the node with
41-
// the mocked p2p layer is being picked as the sequencer, and it does not have any transactions in it's mempool.
42-
// If this is the case, then we should update the test to switch off the mempool of a different node.
43-
// adjust `nodeToTurnOffTxGossip` in the test below.
4443
it('should produce an attestation by requesting tx data over the p2p network', async () => {
4544
/**
4645
* Birds eye overview of the test
@@ -73,12 +72,15 @@ describe('e2e_p2p_reqresp_tx', () => {
7372
// wait a bit for peers to discover each other
7473
await sleep(4000);
7574

76-
t.logger.info('Turning off tx gossip');
75+
const { proposerIndexes, nodesToTurnOffTxGossip } = await getProposerIndexes();
76+
77+
t.logger.info(`Turning off tx gossip for nodes: ${nodesToTurnOffTxGossip}`);
78+
t.logger.info(`Sending txs to proposer nodes: ${proposerIndexes}`);
79+
7780
// Replace the p2p node implementation of some of the nodes with a spy such that it does not store transactions that are gossiped to it
7881
// Original implementation of `processTxFromPeer` will store received transactions in the tx pool.
79-
// We have chosen nodes 0,3 as they do not get chosen to be the sequencer in this test.
80-
const nodeToTurnOffTxGossip = [0, 3];
81-
for (const nodeIndex of nodeToTurnOffTxGossip) {
82+
// We chose the first 2 nodes that will be the proposers for the next few slots
83+
for (const nodeIndex of nodesToTurnOffTxGossip) {
8284
jest
8385
.spyOn((nodes[nodeIndex] as any).p2pClient.p2pService, 'processTxFromPeer')
8486
.mockImplementation((): Promise<void> => {
@@ -87,10 +89,9 @@ describe('e2e_p2p_reqresp_tx', () => {
8789
}
8890

8991
t.logger.info('Submitting transactions');
90-
// Only submit transactions to the first two nodes, so that we avoid our sequencer with a mocked p2p layer being picked to produce a block.
91-
// If the shuffling algorithm changes, then this will need to be updated.
92-
for (let i = 1; i < 3; i++) {
93-
const context = await createPXEServiceAndSubmitTransactions(t.logger, nodes[i], NUM_TXS_PER_NODE);
92+
93+
for (const nodeIndex of proposerIndexes.slice(0, 2)) {
94+
const context = await createPXEServiceAndSubmitTransactions(t.logger, nodes[nodeIndex], NUM_TXS_PER_NODE);
9495
contexts.push(context);
9596
}
9697

@@ -107,4 +108,34 @@ describe('e2e_p2p_reqresp_tx', () => {
107108
);
108109
t.logger.info('All transactions mined');
109110
});
111+
112+
/**
113+
* Get the indexes in the nodes array that will produce the next few blocks
114+
*/
115+
async function getProposerIndexes() {
116+
// Get the nodes for the next set of slots
117+
const rollupContract = getContract({
118+
address: t.ctx.deployL1ContractsValues.l1ContractAddresses.rollupAddress.toString(),
119+
abi: RollupAbi,
120+
client: t.ctx.deployL1ContractsValues.publicClient,
121+
});
122+
123+
const currentTime = await t.ctx.cheatCodes.eth.timestamp();
124+
const slotDuration = await rollupContract.read.SLOT_DURATION();
125+
126+
const proposers = [];
127+
128+
for (let i = 0; i < 3; i++) {
129+
const nextSlot = BigInt(currentTime) + BigInt(i) * BigInt(slotDuration);
130+
const proposer = await rollupContract.read.getProposerAt([nextSlot]);
131+
proposers.push(proposer);
132+
}
133+
134+
// Get the indexes of the nodes that are responsible for the next two slots
135+
const proposerIndexes = proposers.map(proposer => t.nodePublicKeys.indexOf(proposer));
136+
const nodesToTurnOffTxGossip = Array.from({ length: NUM_NODES }, (_, i) => i).filter(
137+
i => !proposerIndexes.includes(i),
138+
);
139+
return { proposerIndexes, nodesToTurnOffTxGossip };
140+
}
110141
});

yarn-project/end-to-end/src/fixtures/fixtures.ts

+7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
export const METRICS_PORT = 4318;
22

3+
export const shouldCollectMetrics = () => {
4+
if (process.env.COLLECT_METRICS) {
5+
return METRICS_PORT;
6+
}
7+
return undefined;
8+
};
9+
310
export const MNEMONIC = 'test test test test test test test test test test test junk';
411
export const privateKey = Buffer.from('ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80', 'hex');
512
export const privateKey2 = Buffer.from('59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d', 'hex');

yarn-project/end-to-end/src/fixtures/setup_p2p_test.ts

+15-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import { generatePrivateKey } from 'viem/accounts';
1212
import { getPrivateKeyFromIndex } from './utils.js';
1313
import { getEndToEndTestTelemetryClient } from './with_telemetry_utils.js';
1414

15+
// Setup snapshots will create a node with index 0, so all of our loops here
16+
// need to start from 1 to avoid running validators with the same key
17+
export const PRIVATE_KEYS_START_INDEX = 1;
18+
1519
export interface NodeContext {
1620
node: AztecNodeService;
1721
pxeService: PXEService;
@@ -52,11 +56,19 @@ export function createNodes(
5256
): Promise<AztecNodeService[]> {
5357
const nodePromises = [];
5458
for (let i = 0; i < numNodes; i++) {
55-
// We run on ports from the bootnode upwards if a port if provided, otherwise we get a random port
59+
// We run on ports from the bootnode upwards
5660
const port = bootNodePort + i + 1;
5761

5862
const dataDir = dataDirectory ? `${dataDirectory}-${i}` : undefined;
59-
const nodePromise = createNode(config, peerIdPrivateKeys[i], port, bootstrapNodeEnr, i, dataDir, metricsPort);
63+
const nodePromise = createNode(
64+
config,
65+
peerIdPrivateKeys[i],
66+
port,
67+
bootstrapNodeEnr,
68+
i + PRIVATE_KEYS_START_INDEX,
69+
dataDir,
70+
metricsPort,
71+
);
6072
nodePromises.push(nodePromise);
6173
}
6274
return Promise.all(nodePromises);
@@ -95,7 +107,7 @@ export async function createValidatorConfig(
95107
bootstrapNodeEnr?: string,
96108
port?: number,
97109
peerIdPrivateKey?: string,
98-
accountIndex: number = 0,
110+
accountIndex: number = 1,
99111
dataDirectory?: string,
100112
) {
101113
peerIdPrivateKey = peerIdPrivateKey ?? generatePeerIdPrivateKey();
@@ -114,8 +126,6 @@ export async function createValidatorConfig(
114126
tcpListenAddress: `0.0.0.0:${port}`,
115127
tcpAnnounceAddress: `127.0.0.1:${port}`,
116128
udpAnnounceAddress: `127.0.0.1:${port}`,
117-
minTxsPerBlock: config.minTxsPerBlock,
118-
maxTxsPerBlock: config.maxTxsPerBlock,
119129
p2pEnabled: true,
120130
blockCheckIntervalMS: 1000,
121131
transactionProtocol: '',

yarn-project/end-to-end/src/fixtures/snapshot_manager.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,8 @@ async function setupFromFresh(
325325
const publisherPrivKeyRaw = hdAccount.getHdKey().privateKey;
326326
const publisherPrivKey = publisherPrivKeyRaw === null ? null : Buffer.from(publisherPrivKeyRaw);
327327

328-
const validatorPrivKey = getPrivateKeyFromIndex(1);
329-
const proverNodePrivateKey = getPrivateKeyFromIndex(2);
328+
const validatorPrivKey = getPrivateKeyFromIndex(0);
329+
const proverNodePrivateKey = getPrivateKeyFromIndex(0);
330330

331331
aztecNodeConfig.publisherPrivateKey = `0x${publisherPrivKey!.toString('hex')}`;
332332
aztecNodeConfig.validatorPrivateKey = `0x${validatorPrivKey!.toString('hex')}`;

0 commit comments

Comments
 (0)