Skip to content

Commit def1287

Browse files
authored
test: mempool limit (#13735)
This PR adds and e2e test to verify the mempool limit works as expected
1 parent e392d7c commit def1287

File tree

8 files changed

+88
-2
lines changed

8 files changed

+88
-2
lines changed

yarn-project/aztec-node/src/aztec-node/server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
997997
public async setConfig(config: Partial<SequencerConfig & ProverConfig>): Promise<void> {
998998
const newConfig = { ...this.config, ...config };
999999
await this.sequencer?.updateSequencerConfig(config);
1000+
await this.p2pClient.updateP2PConfig(config);
10001001

10011002
if (newConfig.realProofs !== this.config.realProofs) {
10021003
this.proofVerifier = config.realProofs ? await BBCircuitVerifier.new(newConfig) : new TestCircuitVerifier();
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { AztecAddress, TxStatus, type Wallet, retryUntil } from '@aztec/aztec.js';
2+
import { TokenContract } from '@aztec/noir-contracts.js/Token';
3+
import type { AztecNodeAdmin } from '@aztec/stdlib/interfaces/client';
4+
5+
import { jest } from '@jest/globals';
6+
7+
import { setup } from './fixtures/utils.js';
8+
9+
describe('e2e_mempool_limit', () => {
10+
let wallet: Wallet;
11+
let aztecNodeAdmin: AztecNodeAdmin | undefined;
12+
let token: TokenContract;
13+
14+
beforeAll(async () => {
15+
({ aztecNodeAdmin, wallet } = await setup(1));
16+
17+
if (!aztecNodeAdmin) {
18+
throw new Error('Aztec node admin API must be available for this test');
19+
}
20+
21+
token = await TokenContract.deploy(wallet, wallet.getAddress(), 'TEST', 'T', 18).send().deployed();
22+
await token.methods
23+
.mint_to_public(wallet.getAddress(), 10n ** 18n)
24+
.send()
25+
.wait();
26+
});
27+
28+
it('should evict txs if there are too many', async () => {
29+
const tx1 = await token.methods.transfer_in_public(wallet.getAddress(), await AztecAddress.random(), 1, 0).prove();
30+
const txSize = tx1.getSize();
31+
32+
// set a min tx greater than the mempool so that the sequencer doesn't all of a sudden build a block
33+
await aztecNodeAdmin!.setConfig({ maxTxPoolSize: Math.floor(2.5 * txSize), minTxsPerBlock: 4 });
34+
35+
const tx2 = await token.methods.transfer_in_public(wallet.getAddress(), await AztecAddress.random(), 1, 0).prove();
36+
const tx3 = await token.methods.transfer_in_public(wallet.getAddress(), await AztecAddress.random(), 1, 0).prove();
37+
38+
const sentTx1 = tx1.send();
39+
await expect(sentTx1.getReceipt()).resolves.toEqual(expect.objectContaining({ status: TxStatus.PENDING }));
40+
41+
const sentTx2 = tx2.send();
42+
await expect(sentTx1.getReceipt()).resolves.toEqual(expect.objectContaining({ status: TxStatus.PENDING }));
43+
await expect(sentTx2.getReceipt()).resolves.toEqual(expect.objectContaining({ status: TxStatus.PENDING }));
44+
45+
const sendSpy = jest.spyOn(wallet, 'sendTx');
46+
const sentTx3 = tx3.send();
47+
48+
// this retry is needed becauase tx3 is sent asynchronously and we need to wait for the event loop to fully drain
49+
await retryUntil(() => sendSpy.mock.results[0]?.value);
50+
51+
// one of the txs will be dropped. Which one is picked is somewhat random because all three will have the same fee
52+
const receipts = await Promise.all([sentTx1.getReceipt(), sentTx2.getReceipt(), sentTx3.getReceipt()]);
53+
expect(receipts.reduce((count, r) => (r.status === TxStatus.PENDING ? count + 1 : count), 0)).toBeLessThan(3);
54+
});
55+
});

yarn-project/p2p/src/client/p2p_client.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ export type P2P<T extends P2PClientType = P2PClientType.Full> = ProverCoordinati
167167

168168
/** Identifies a p2p client. */
169169
isP2PClient(): true;
170+
171+
updateP2PConfig(config: Partial<P2PConfig>): Promise<void>;
170172
};
171173

172174
/**
@@ -200,6 +202,8 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
200202

201203
private blockStream;
202204

205+
private config: P2PConfig;
206+
203207
/**
204208
* In-memory P2P client constructor.
205209
* @param store - The client's instance of the KV store.
@@ -221,10 +225,13 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
221225
) {
222226
super(telemetry, 'P2PClient');
223227

224-
const { keepProvenTxsInPoolFor, blockCheckIntervalMS, blockRequestBatchSize, keepAttestationsInPoolFor } = {
228+
this.config = {
225229
...getP2PDefaultConfig(),
226230
...config,
227231
};
232+
233+
const { keepProvenTxsInPoolFor, blockCheckIntervalMS, blockRequestBatchSize, keepAttestationsInPoolFor } =
234+
this.config;
228235
this.keepProvenTxsFor = keepProvenTxsInPoolFor;
229236
this.keepAttestationsInPoolFor = keepAttestationsInPoolFor;
230237

@@ -256,6 +263,13 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
256263
return this.synchedBlockHashes.getAsync(number);
257264
}
258265

266+
public async updateP2PConfig(config: Partial<P2PConfig>): Promise<void> {
267+
if (typeof config.maxTxPoolSize === 'number' && this.config.maxTxPoolSize !== config.maxTxPoolSize) {
268+
await this.txPool.setMaxTxPoolSize(config.maxTxPoolSize);
269+
this.config.maxTxPoolSize = config.maxTxPoolSize;
270+
}
271+
}
272+
259273
public async getL2Tips(): Promise<L2Tips> {
260274
const latestBlockNumber = await this.getSyncedLatestBlockNum();
261275
let latestBlockHash: string | undefined;

yarn-project/p2p/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,11 @@ export class AztecKVTxPool implements TxPool {
341341
return vals.map(x => TxHash.fromString(x));
342342
}
343343

344+
public setMaxTxPoolSize(maxSizeBytes: number | undefined): Promise<void> {
345+
this.#maxTxPoolSize = maxSizeBytes;
346+
return Promise.resolve();
347+
}
348+
344349
/**
345350
* Creates a GasTxValidator instance.
346351
* @param db - DB for the validator to use

yarn-project/p2p/src/mem_pools/tx_pool/memory_tx_pool.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,4 +171,8 @@ export class InMemoryTxPool implements TxPool {
171171
public getAllTxHashes(): Promise<TxHash[]> {
172172
return Promise.resolve(Array.from(this.txs.keys()).map(x => TxHash.fromBigInt(x)));
173173
}
174+
175+
setMaxTxPoolSize(_maxSizeBytes: number | undefined): Promise<void> {
176+
return Promise.resolve();
177+
}
174178
}

yarn-project/p2p/src/mem_pools/tx_pool/tx_pool.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,10 @@ export interface TxPool {
7373
* @returns Pending or mined depending on its status, or undefined if not found.
7474
*/
7575
getTxStatus(txHash: TxHash): Promise<'pending' | 'mined' | undefined>;
76+
77+
/**
78+
* Configure the maximum size of the tx pool
79+
* @param maxSizeBytes - The maximum size in bytes of the mempool. Set to undefined to disable it
80+
*/
81+
setMaxTxPoolSize(maxSizeBytes: number | undefined): Promise<void>;
7682
}

yarn-project/p2p/src/testbench/p2p_client_testbench_worker.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ function mockTxPool(): TxPool {
4545
getPendingTxHashes: () => Promise.resolve([]),
4646
getMinedTxHashes: () => Promise.resolve([]),
4747
getTxStatus: () => Promise.resolve(TxStatus.PENDING),
48+
setMaxTxPoolSize: () => Promise.resolve(),
4849
};
4950
}
5051

yarn-project/stdlib/src/interfaces/aztec-node-admin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export interface AztecNodeAdmin {
1515
* Updates the configuration of this node.
1616
* @param config - Updated configuration to be merged with the current one.
1717
*/
18-
setConfig(config: Partial<SequencerConfig & ProverConfig>): Promise<void>;
18+
setConfig(config: Partial<SequencerConfig & ProverConfig & { maxTxPoolSize: number }>): Promise<void>;
1919

2020
/**
2121
* Forces the next block to be built bypassing all time and pending checks.

0 commit comments

Comments
 (0)