Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions yarn-project/aztec-node/src/aztec-node/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
public async setConfig(config: Partial<SequencerConfig & ProverConfig>): Promise<void> {
const newConfig = { ...this.config, ...config };
await this.sequencer?.updateSequencerConfig(config);
await this.p2pClient.updateP2PConfig(config);

if (newConfig.realProofs !== this.config.realProofs) {
this.proofVerifier = config.realProofs ? await BBCircuitVerifier.new(newConfig) : new TestCircuitVerifier();
Expand Down
55 changes: 55 additions & 0 deletions yarn-project/end-to-end/src/e2e_mempool_limit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { AztecAddress, TxStatus, type Wallet, retryUntil } from '@aztec/aztec.js';
import { TokenContract } from '@aztec/noir-contracts.js/Token';
import type { AztecNodeAdmin } from '@aztec/stdlib/interfaces/client';

import { jest } from '@jest/globals';

import { setup } from './fixtures/utils.js';

describe('e2e_mempool_limit', () => {
let wallet: Wallet;
let aztecNodeAdmin: AztecNodeAdmin | undefined;
let token: TokenContract;

beforeAll(async () => {
({ aztecNodeAdmin, wallet } = await setup(1));

if (!aztecNodeAdmin) {
throw new Error('Aztec node admin API must be available for this test');
}

token = await TokenContract.deploy(wallet, wallet.getAddress(), 'TEST', 'T', 18).send().deployed();
await token.methods
.mint_to_public(wallet.getAddress(), 10n ** 18n)
.send()
.wait();
});

it('should evict txs if there are too many', async () => {
const tx1 = await token.methods.transfer_in_public(wallet.getAddress(), await AztecAddress.random(), 1, 0).prove();
const txSize = tx1.getSize();

// set a min tx greater than the mempool so that the sequencer doesn't all of a sudden build a block
await aztecNodeAdmin!.setConfig({ maxTxPoolSize: Math.floor(2.5 * txSize), minTxsPerBlock: 4 });

const tx2 = await token.methods.transfer_in_public(wallet.getAddress(), await AztecAddress.random(), 1, 0).prove();
const tx3 = await token.methods.transfer_in_public(wallet.getAddress(), await AztecAddress.random(), 1, 0).prove();

const sentTx1 = tx1.send();
await expect(sentTx1.getReceipt()).resolves.toEqual(expect.objectContaining({ status: TxStatus.PENDING }));

const sentTx2 = tx2.send();
await expect(sentTx1.getReceipt()).resolves.toEqual(expect.objectContaining({ status: TxStatus.PENDING }));
await expect(sentTx2.getReceipt()).resolves.toEqual(expect.objectContaining({ status: TxStatus.PENDING }));

const sendSpy = jest.spyOn(wallet, 'sendTx');
const sentTx3 = tx3.send();

// this retry is needed becauase tx3 is sent asynchronously and we need to wait for the event loop to fully drain
await retryUntil(() => sendSpy.mock.results[0]?.value);

// one of the txs will be dropped. Which one is picked is somewhat random because all three will have the same fee
const receipts = await Promise.all([sentTx1.getReceipt(), sentTx2.getReceipt(), sentTx3.getReceipt()]);
expect(receipts.reduce((count, r) => (r.status === TxStatus.PENDING ? count + 1 : count), 0)).toBeLessThan(3);
});
});
16 changes: 15 additions & 1 deletion yarn-project/p2p/src/client/p2p_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ export type P2P<T extends P2PClientType = P2PClientType.Full> = ProverCoordinati

/** Identifies a p2p client. */
isP2PClient(): true;

updateP2PConfig(config: Partial<P2PConfig>): Promise<void>;
};

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

private blockStream;

private config: P2PConfig;

/**
* In-memory P2P client constructor.
* @param store - The client's instance of the KV store.
Expand All @@ -221,10 +225,13 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
) {
super(telemetry, 'P2PClient');

const { keepProvenTxsInPoolFor, blockCheckIntervalMS, blockRequestBatchSize, keepAttestationsInPoolFor } = {
this.config = {
...getP2PDefaultConfig(),
...config,
};

const { keepProvenTxsInPoolFor, blockCheckIntervalMS, blockRequestBatchSize, keepAttestationsInPoolFor } =
this.config;
this.keepProvenTxsFor = keepProvenTxsInPoolFor;
this.keepAttestationsInPoolFor = keepAttestationsInPoolFor;

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

public async updateP2PConfig(config: Partial<P2PConfig>): Promise<void> {
if (typeof config.maxTxPoolSize === 'number' && this.config.maxTxPoolSize !== config.maxTxPoolSize) {
await this.txPool.setMaxTxPoolSize(config.maxTxPoolSize);
this.config.maxTxPoolSize = config.maxTxPoolSize;
}
}

public async getL2Tips(): Promise<L2Tips> {
const latestBlockNumber = await this.getSyncedLatestBlockNum();
let latestBlockHash: string | undefined;
Expand Down
5 changes: 5 additions & 0 deletions yarn-project/p2p/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,11 @@ export class AztecKVTxPool implements TxPool {
return vals.map(x => TxHash.fromString(x));
}

public setMaxTxPoolSize(maxSizeBytes: number | undefined): Promise<void> {
this.#maxTxPoolSize = maxSizeBytes;
return Promise.resolve();
}

/**
* Creates a GasTxValidator instance.
* @param db - DB for the validator to use
Expand Down
4 changes: 4 additions & 0 deletions yarn-project/p2p/src/mem_pools/tx_pool/memory_tx_pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,8 @@ export class InMemoryTxPool implements TxPool {
public getAllTxHashes(): Promise<TxHash[]> {
return Promise.resolve(Array.from(this.txs.keys()).map(x => TxHash.fromBigInt(x)));
}

setMaxTxPoolSize(_maxSizeBytes: number | undefined): Promise<void> {
return Promise.resolve();
}
}
6 changes: 6 additions & 0 deletions yarn-project/p2p/src/mem_pools/tx_pool/tx_pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,10 @@ export interface TxPool {
* @returns Pending or mined depending on its status, or undefined if not found.
*/
getTxStatus(txHash: TxHash): Promise<'pending' | 'mined' | undefined>;

/**
* Configure the maximum size of the tx pool
* @param maxSizeBytes - The maximum size in bytes of the mempool. Set to undefined to disable it
*/
setMaxTxPoolSize(maxSizeBytes: number | undefined): Promise<void>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ function mockTxPool(): TxPool {
getPendingTxHashes: () => Promise.resolve([]),
getMinedTxHashes: () => Promise.resolve([]),
getTxStatus: () => Promise.resolve(TxStatus.PENDING),
setMaxTxPoolSize: () => Promise.resolve(),
};
}

Expand Down
2 changes: 1 addition & 1 deletion yarn-project/stdlib/src/interfaces/aztec-node-admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export interface AztecNodeAdmin {
* Updates the configuration of this node.
* @param config - Updated configuration to be merged with the current one.
*/
setConfig(config: Partial<SequencerConfig & ProverConfig>): Promise<void>;
setConfig(config: Partial<SequencerConfig & ProverConfig & { maxTxPoolSize: number }>): Promise<void>;

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