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
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe('LowPriorityEvictionRule', () => {
});

describe('evict method', () => {
describe('non-TXS_ADDED events', () => {
describe('BLOCK_MINED events', () => {
it('returns empty result for BLOCK_MINED event', async () => {
const context: EvictionContext = {
event: EvictionEvent.BLOCK_MINED,
Expand All @@ -60,8 +60,45 @@ describe('LowPriorityEvictionRule', () => {
txsEvicted: [],
});
});
});

describe('CHAIN_PRUNED events', () => {
it('evicts transactions when pool is over limit', async () => {
rule.updateConfig({ maxPendingTxCount: 2 });
pool = createPoolOps(4, ['0x3333', '0x4444']);

const context: EvictionContext = {
event: EvictionEvent.CHAIN_PRUNED,
blockNumber: BlockNumber(1),
};

const result = await rule.evict(context, pool);

expect(result.success).toBe(true);
expect(result.txsEvicted).toEqual(['0x3333', '0x4444']);
expect(deleteTxsMock).toHaveBeenCalledWith(['0x3333', '0x4444'], 'LowPriorityEviction');
});

it('returns empty result when pool is under limit', async () => {
pool = createPoolOps(50);

const context: EvictionContext = {
event: EvictionEvent.CHAIN_PRUNED,
blockNumber: BlockNumber(1),
};

const result = await rule.evict(context, pool);

expect(result).toEqual({
reason: 'low_priority',
success: true,
txsEvicted: [],
});
});

it('returns empty result when maxPoolSize is 0', async () => {
rule.updateConfig({ maxPendingTxCount: 0 });

it('returns empty result for CHAIN_PRUNED event', async () => {
const context: EvictionContext = {
event: EvictionEvent.CHAIN_PRUNED,
blockNumber: BlockNumber(1),
Expand All @@ -75,6 +112,23 @@ describe('LowPriorityEvictionRule', () => {
txsEvicted: [],
});
});

it('handles error from pool operations', async () => {
rule.updateConfig({ maxPendingTxCount: 1 });
pool = createPoolOps(2, ['0x3333']);
deleteTxsMock.mockRejectedValue(new Error('Test error'));

const context: EvictionContext = {
event: EvictionEvent.CHAIN_PRUNED,
blockNumber: BlockNumber(1),
};

const result = await rule.evict(context, pool);

expect(result.success).toBe(false);
expect(result.txsEvicted).toEqual([]);
expect(result.error?.message).toContain('Failed to evict low priority txs');
});
});

describe('TXS_ADDED events', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { EvictionEvent } from './interfaces.js';

/**
* Eviction rule that removes low-priority transactions when the pool exceeds configured limits.
* Only triggers on TXS_ADDED events.
* Triggers on TXS_ADDED and CHAIN_PRUNED events.
*/
export class LowPriorityEvictionRule implements EvictionRule {
public readonly name = 'LowPriorityEviction';
Expand All @@ -18,7 +18,7 @@ export class LowPriorityEvictionRule implements EvictionRule {
}

async evict(context: EvictionContext, pool: PoolOperations): Promise<EvictionResult> {
if (context.event !== EvictionEvent.TXS_ADDED) {
if (context.event !== EvictionEvent.TXS_ADDED && context.event !== EvictionEvent.CHAIN_PRUNED) {
return {
reason: 'low_priority',
success: true,
Expand Down Expand Up @@ -51,15 +51,19 @@ export class LowPriorityEvictionRule implements EvictionRule {
this.log.info(`Evicting low priority txs. Pending tx count above limit: ${currentTxCount} > ${this.maxPoolSize}`);
const numberToEvict = currentTxCount - this.maxPoolSize;
const txsToEvict = pool.getLowestPriorityPending(numberToEvict);
const toEvictSet = new Set(txsToEvict);
const numNewTxsEvicted = context.newTxHashes.filter(newTxHash => toEvictSet.has(newTxHash)).length;

if (txsToEvict.length > 0) {
this.log.info(`Evicted ${txsToEvict.length} low priority txs, including ${numNewTxsEvicted} newly added txs`);
if (context.event === EvictionEvent.TXS_ADDED) {
const toEvictSet = new Set(txsToEvict);
const numNewTxsEvicted = context.newTxHashes.filter(newTxHash => toEvictSet.has(newTxHash)).length;
this.log.info(`Evicted ${txsToEvict.length} low priority txs, including ${numNewTxsEvicted} newly added txs`);
} else {
this.log.info(`Evicted ${txsToEvict.length} low priority txs after chain prune`);
}
await pool.deleteTxs(txsToEvict, this.name);
}

this.log.debug(`Evicted ${txsToEvict.length} low priority txs, including ${numNewTxsEvicted} newly added txs`, {
this.log.debug(`Evicted ${txsToEvict.length} low priority txs`, {
txHashes: txsToEvict,
});

Expand Down
52 changes: 52 additions & 0 deletions yarn-project/p2p/src/mem_pools/tx_pool_v2/tx_pool_v2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1817,6 +1817,58 @@ describe('TxPoolV2', () => {
expect(await pool.getTxStatus(txMined.getTxHash())).toBe('deleted');
expectRemovedTxs(txMined); // txMined deleted
});

it('evicts low priority txs after chain prune when pool exceeds limit', async () => {
const txLow = await mockTxWithFee(1, 1);
const txMed = await mockTxWithFee(2, 5);
const txHigh = await mockTxWithFee(3, 10);

// Add all 3 txs (no pool limit by default)
await pool.addPendingTxs([txLow, txMed, txHigh]);
expectAddedTxs(txLow, txMed, txHigh);
expect(await pool.getPendingTxCount()).toBe(3);

// Mine all three
await pool.handleMinedBlock(makeBlock([txLow, txMed, txHigh], slot1Header));
expectNoCallbacks();
expect(await pool.getPendingTxCount()).toBe(0);

// Now set pool limit to 2
await pool.updateConfig({ maxPendingTxCount: 2 });

// Prune - all 3 txs return to pending, but pool limit is 2
await pool.handlePrunedBlocks(block0Id);

// Lowest priority tx should be evicted
const pending = toStrings(await pool.getPendingTxHashes());
expect(pending).toHaveLength(2);
expect(pending).toContain(hashOf(txMed));
expect(pending).toContain(hashOf(txHigh));
expect(await pool.getTxStatus(txLow.getTxHash())).toBe('deleted');
});

it('does not evict txs after chain prune when pool is within limit', async () => {
const tx1 = await mockTxWithFee(1, 1);
const tx2 = await mockTxWithFee(2, 2);

await pool.addPendingTxs([tx1, tx2]);
expectAddedTxs(tx1, tx2);
expect(await pool.getPendingTxCount()).toBe(2);

// Mine both
await pool.handleMinedBlock(makeBlock([tx1, tx2], slot1Header));
expectNoCallbacks();

// Set limit to 3 (above what will be restored)
await pool.updateConfig({ maxPendingTxCount: 3 });

// Prune - both txs return to pending, under the limit
await pool.handlePrunedBlocks(block0Id);

expect(await pool.getPendingTxCount()).toBe(2);
expect(await pool.getTxStatus(tx1.getTxHash())).toBe('pending');
expect(await pool.getTxStatus(tx2.getTxHash())).toBe('pending');
});
});

describe('validation during restore', () => {
Expand Down
Loading