@aztec/p2p 3.0.0-rc.5 → 4.0.0-nightly.20260107

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dest/client/factory.d.ts +2 -2
  2. package/dest/client/factory.d.ts.map +1 -1
  3. package/dest/client/factory.js +2 -3
  4. package/dest/client/p2p_client.d.ts +2 -2
  5. package/dest/client/p2p_client.d.ts.map +1 -1
  6. package/dest/client/p2p_client.js +395 -21
  7. package/dest/config.d.ts +4 -7
  8. package/dest/config.d.ts.map +1 -1
  9. package/dest/config.js +6 -9
  10. package/dest/mem_pools/instrumentation.d.ts +7 -1
  11. package/dest/mem_pools/instrumentation.d.ts.map +1 -1
  12. package/dest/mem_pools/instrumentation.js +29 -2
  13. package/dest/mem_pools/interface.d.ts +3 -4
  14. package/dest/mem_pools/interface.d.ts.map +1 -1
  15. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts +28 -24
  16. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts.map +1 -1
  17. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.js +261 -323
  18. package/dest/mem_pools/tx_pool/eviction/eviction_manager.d.ts +18 -0
  19. package/dest/mem_pools/tx_pool/eviction/eviction_manager.d.ts.map +1 -0
  20. package/dest/mem_pools/tx_pool/eviction/eviction_manager.js +56 -0
  21. package/dest/mem_pools/tx_pool/eviction/eviction_strategy.d.ts +83 -0
  22. package/dest/mem_pools/tx_pool/eviction/eviction_strategy.d.ts.map +1 -0
  23. package/dest/mem_pools/tx_pool/eviction/eviction_strategy.js +5 -0
  24. package/dest/mem_pools/tx_pool/eviction/insufficient_fee_payer_balance_rule.d.ts +15 -0
  25. package/dest/mem_pools/tx_pool/eviction/insufficient_fee_payer_balance_rule.d.ts.map +1 -0
  26. package/dest/mem_pools/tx_pool/eviction/insufficient_fee_payer_balance_rule.js +88 -0
  27. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.d.ts +17 -0
  28. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.d.ts.map +1 -0
  29. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.js +84 -0
  30. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_reorg_rule.d.ts +19 -0
  31. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_reorg_rule.d.ts.map +1 -0
  32. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_reorg_rule.js +76 -0
  33. package/dest/mem_pools/tx_pool/eviction/low_priority_eviction_rule.d.ts +26 -0
  34. package/dest/mem_pools/tx_pool/eviction/low_priority_eviction_rule.d.ts.map +1 -0
  35. package/dest/mem_pools/tx_pool/eviction/low_priority_eviction_rule.js +84 -0
  36. package/dest/mem_pools/tx_pool/index.d.ts +1 -2
  37. package/dest/mem_pools/tx_pool/index.d.ts.map +1 -1
  38. package/dest/mem_pools/tx_pool/index.js +0 -1
  39. package/dest/mem_pools/tx_pool/priority.d.ts +5 -1
  40. package/dest/mem_pools/tx_pool/priority.d.ts.map +1 -1
  41. package/dest/mem_pools/tx_pool/priority.js +6 -1
  42. package/dest/mem_pools/tx_pool/tx_pool.d.ts +8 -4
  43. package/dest/mem_pools/tx_pool/tx_pool.d.ts.map +1 -1
  44. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts +1 -1
  45. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts.map +1 -1
  46. package/dest/mem_pools/tx_pool/tx_pool_test_suite.js +25 -20
  47. package/dest/services/libp2p/libp2p_service.d.ts +4 -4
  48. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  49. package/dest/services/libp2p/libp2p_service.js +447 -64
  50. package/dest/services/peer-manager/metrics.d.ts +6 -1
  51. package/dest/services/peer-manager/metrics.d.ts.map +1 -1
  52. package/dest/services/peer-manager/metrics.js +17 -0
  53. package/dest/services/peer-manager/peer_manager.d.ts +1 -1
  54. package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
  55. package/dest/services/peer-manager/peer_manager.js +385 -9
  56. package/dest/services/reqresp/protocols/tx.d.ts +2 -3
  57. package/dest/services/reqresp/protocols/tx.d.ts.map +1 -1
  58. package/dest/services/reqresp/reqresp.js +402 -24
  59. package/dest/services/tx_provider.d.ts +2 -1
  60. package/dest/services/tx_provider.d.ts.map +1 -1
  61. package/dest/services/tx_provider.js +11 -2
  62. package/dest/services/tx_provider_instrumentation.d.ts +5 -2
  63. package/dest/services/tx_provider_instrumentation.d.ts.map +1 -1
  64. package/dest/services/tx_provider_instrumentation.js +14 -1
  65. package/dest/test-helpers/reqresp-nodes.d.ts +2 -2
  66. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
  67. package/dest/testbench/p2p_client_testbench_worker.js +1 -0
  68. package/package.json +14 -14
  69. package/src/client/factory.ts +5 -10
  70. package/src/client/p2p_client.ts +12 -17
  71. package/src/config.ts +8 -14
  72. package/src/mem_pools/instrumentation.ts +33 -0
  73. package/src/mem_pools/interface.ts +2 -4
  74. package/src/mem_pools/tx_pool/README.md +255 -0
  75. package/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts +308 -368
  76. package/src/mem_pools/tx_pool/eviction/eviction_manager.ts +71 -0
  77. package/src/mem_pools/tx_pool/eviction/eviction_strategy.ts +93 -0
  78. package/src/mem_pools/tx_pool/eviction/insufficient_fee_payer_balance_rule.ts +108 -0
  79. package/src/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.ts +104 -0
  80. package/src/mem_pools/tx_pool/eviction/invalid_txs_after_reorg_rule.ts +91 -0
  81. package/src/mem_pools/tx_pool/eviction/low_priority_eviction_rule.ts +106 -0
  82. package/src/mem_pools/tx_pool/index.ts +0 -1
  83. package/src/mem_pools/tx_pool/priority.ts +8 -1
  84. package/src/mem_pools/tx_pool/tx_pool.ts +8 -3
  85. package/src/mem_pools/tx_pool/tx_pool_test_suite.ts +18 -13
  86. package/src/services/libp2p/libp2p_service.ts +12 -17
  87. package/src/services/peer-manager/metrics.ts +22 -0
  88. package/src/services/peer-manager/peer_manager.ts +2 -0
  89. package/src/services/reqresp/protocols/tx.ts +1 -2
  90. package/src/services/tx_provider.ts +17 -2
  91. package/src/services/tx_provider_instrumentation.ts +19 -2
  92. package/src/test-helpers/mock-pubsub.ts +1 -1
  93. package/src/test-helpers/reqresp-nodes.ts +1 -1
  94. package/src/testbench/p2p_client_testbench_worker.ts +2 -1
  95. package/dest/mem_pools/tx_pool/memory_tx_pool.d.ts +0 -81
  96. package/dest/mem_pools/tx_pool/memory_tx_pool.d.ts.map +0 -1
  97. package/dest/mem_pools/tx_pool/memory_tx_pool.js +0 -239
  98. package/src/mem_pools/tx_pool/memory_tx_pool.ts +0 -285
@@ -1,15 +1,14 @@
1
+ import { insertIntoSortedArray } from '@aztec/foundation/array';
1
2
  import { BlockNumber } from '@aztec/foundation/branded-types';
2
3
  import { Fr } from '@aztec/foundation/curves/bn254';
3
4
  import { toArray } from '@aztec/foundation/iterable';
4
5
  import { type Logger, createLogger } from '@aztec/foundation/log';
5
6
  import type { TypedEventEmitter } from '@aztec/foundation/types';
6
- import type { AztecAsyncKVStore, AztecAsyncMap, AztecAsyncMultiMap, AztecAsyncSingleton } from '@aztec/kv-store';
7
- import { ProtocolContractAddress } from '@aztec/protocol-contracts';
8
- import { GasFees } from '@aztec/stdlib/gas';
9
- import type { MerkleTreeReadOperations, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
7
+ import type { AztecAsyncKVStore, AztecAsyncMap, AztecAsyncMultiMap } from '@aztec/kv-store';
8
+ import type { AztecAddress } from '@aztec/stdlib/aztec-address';
9
+ import type { MerkleTreeReadOperations, ReadonlyWorldStateAccess } from '@aztec/stdlib/interfaces/server';
10
10
  import { ChonkProof } from '@aztec/stdlib/proofs';
11
11
  import type { TxAddedToPoolStats } from '@aztec/stdlib/stats';
12
- import { DatabasePublicStateSource } from '@aztec/stdlib/trees';
13
12
  import { BlockHeader, Tx, TxHash } from '@aztec/stdlib/tx';
14
13
  import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
15
14
 
@@ -17,25 +16,30 @@ import assert from 'assert';
17
16
  import EventEmitter from 'node:events';
18
17
 
19
18
  import { ArchiveCache } from '../../msg_validators/tx_validator/archive_cache.js';
20
- import { GasTxValidator } from '../../msg_validators/tx_validator/gas_validator.js';
21
19
  import { PoolInstrumentation, PoolName, type PoolStatsCallback } from '../instrumentation.js';
20
+ import { EvictionManager } from './eviction/eviction_manager.js';
21
+ import type { PendingTxInfo, TxBlockReference, TxPoolOperations } from './eviction/eviction_strategy.js';
22
+ import { InsufficientFeePayerBalanceRule } from './eviction/insufficient_fee_payer_balance_rule.js';
23
+ import { InvalidTxsAfterMiningRule } from './eviction/invalid_txs_after_mining_rule.js';
24
+ import { InvalidTxsAfterReorgRule } from './eviction/invalid_txs_after_reorg_rule.js';
25
+ import { LowPriorityEvictionRule } from './eviction/low_priority_eviction_rule.js';
22
26
  import { getPendingTxPriority } from './priority.js';
23
27
  import type { TxPool, TxPoolEvents, TxPoolOptions } from './tx_pool.js';
24
28
 
25
29
  /**
26
30
  * KV implementation of the Transaction Pool.
27
31
  */
28
- export class AztecKVTxPool extends (EventEmitter as new () => TypedEventEmitter<TxPoolEvents>) implements TxPool {
32
+ export class AztecKVTxPool
33
+ extends (EventEmitter as new () => TypedEventEmitter<TxPoolEvents>)
34
+ implements TxPool, TxPoolOperations
35
+ {
29
36
  #store: AztecAsyncKVStore;
30
37
 
31
38
  /** Our tx pool, stored as a Map, with K: tx hash and V: the transaction. */
32
39
  #txs: AztecAsyncMap<string, Buffer>;
33
40
 
34
- /** The maximum cumulative tx size that the pending txs in the pool take up. */
35
- #maxTxPoolSize: number = 0;
36
-
37
- /** The tx evicion logic will kick after pool size is greater than maxTxPoolSize * txPoolOverflowFactor */
38
- txPoolOverflowFactor: number = 1;
41
+ /** Holds the historical block for each tx */
42
+ #pendingTxHashToHistoricalBlockHeaderHash: AztecAsyncMap<string, string>;
39
43
 
40
44
  /** Index from tx hash to the block number in which they were mined, filtered by mined txs. */
41
45
  #minedTxHashToBlock: AztecAsyncMap<string, BlockNumber>;
@@ -43,23 +47,15 @@ export class AztecKVTxPool extends (EventEmitter as new () => TypedEventEmitter<
43
47
  /** Index from tx priority (stored as hex) to its tx hash, filtered by pending txs. */
44
48
  #pendingTxPriorityToHash: AztecAsyncMultiMap<string, string>;
45
49
 
46
- /** Index from tx hash to its tx size (in bytes), filtered by pending txs. */
47
- #pendingTxHashToSize: AztecAsyncMap<string, number>;
48
-
49
- /** Index from tx hash to its header hash, filtered by pending txs. */
50
- #pendingTxHashToHeaderHash: AztecAsyncMap<string, string>;
51
-
52
50
  /** Map from tx hash to the block number it was originally mined in (for soft-deleted txs). */
53
51
  #deletedMinedTxHashes: AztecAsyncMap<string, BlockNumber>;
54
52
 
55
53
  /** MultiMap from block number to deleted mined tx hashes for efficient cleanup. */
56
54
  #blockToDeletedMinedTxHash: AztecAsyncMultiMap<BlockNumber, string>;
57
55
 
58
- /** The cumulative tx size in bytes that the pending txs in the pool take up. */
59
- #pendingTxSize: AztecAsyncSingleton<number>;
56
+ #historicalHeaderToTxHash: AztecAsyncMultiMap<string, string>;
60
57
 
61
- /** In-memory mapping of pending tx hashes to the hydrated pending tx in the pool. */
62
- #pendingTxs: Map<string, Tx>;
58
+ #feePayerToTxHash: AztecAsyncMultiMap<string, string>;
63
59
 
64
60
  /** In-memory set of txs that should not be evicted from the pool. */
65
61
  #nonEvictableTxs: Set<string>;
@@ -76,8 +72,7 @@ export class AztecKVTxPool extends (EventEmitter as new () => TypedEventEmitter<
76
72
  /** Number of txs to archive. */
77
73
  #archivedTxLimit: number = 0;
78
74
 
79
- /** The world state synchronizer used in the node. */
80
- #worldStateSynchronizer: WorldStateSynchronizer;
75
+ #evictionManager: EvictionManager;
81
76
 
82
77
  #log: Logger;
83
78
 
@@ -94,7 +89,7 @@ export class AztecKVTxPool extends (EventEmitter as new () => TypedEventEmitter<
94
89
  constructor(
95
90
  store: AztecAsyncKVStore,
96
91
  archive: AztecAsyncKVStore,
97
- worldStateSynchronizer: WorldStateSynchronizer,
92
+ worldState: ReadonlyWorldStateAccess,
98
93
  telemetry: TelemetryClient = getTelemetryClient(),
99
94
  config: TxPoolOptions = {},
100
95
  log = createLogger('p2p:tx_pool'),
@@ -102,18 +97,30 @@ export class AztecKVTxPool extends (EventEmitter as new () => TypedEventEmitter<
102
97
  super();
103
98
 
104
99
  this.#log = log;
100
+
101
+ this.#evictionManager = new EvictionManager(this);
102
+ this.#evictionManager.registerRule(new InvalidTxsAfterMiningRule());
103
+ this.#evictionManager.registerRule(new InvalidTxsAfterReorgRule(worldState));
104
+ this.#evictionManager.registerRule(new InsufficientFeePayerBalanceRule(worldState));
105
+ this.#evictionManager.registerRule(
106
+ new LowPriorityEvictionRule({
107
+ //NOTE: 0 effectively disables low priority eviction
108
+ maxPoolSize: config.maxPendingTxCount ?? 0,
109
+ }),
110
+ );
111
+
105
112
  this.updateConfig(config);
106
113
 
107
114
  this.#txs = store.openMap('txs');
108
115
  this.#minedTxHashToBlock = store.openMap('txHashToBlockMined');
109
116
  this.#pendingTxPriorityToHash = store.openMultiMap('pendingTxFeeToHash');
110
- this.#pendingTxHashToSize = store.openMap('pendingTxHashToSize');
111
- this.#pendingTxHashToHeaderHash = store.openMap('pendingTxHashToHeaderHash');
112
- this.#pendingTxSize = store.openSingleton('pendingTxSize');
113
117
  this.#deletedMinedTxHashes = store.openMap('deletedMinedTxHashes');
114
118
  this.#blockToDeletedMinedTxHash = store.openMultiMap('blockToDeletedMinedTxHash');
115
119
 
116
- this.#pendingTxs = new Map<string, Tx>();
120
+ this.#pendingTxHashToHistoricalBlockHeaderHash = store.openMap('txHistoricalBlock');
121
+ this.#historicalHeaderToTxHash = store.openMultiMap('historicalHeaderToPendingTxHash');
122
+ this.#feePayerToTxHash = store.openMultiMap('feePayerToPendingTxHash');
123
+
117
124
  this.#nonEvictableTxs = new Set<string>();
118
125
 
119
126
  this.#archivedTxs = archive.openMap('archivedTxs');
@@ -121,7 +128,7 @@ export class AztecKVTxPool extends (EventEmitter as new () => TypedEventEmitter<
121
128
 
122
129
  this.#store = store;
123
130
  this.#archive = archive;
124
- this.#worldStateSynchronizer = worldStateSynchronizer;
131
+
125
132
  this.#metrics = new PoolInstrumentation(telemetry, PoolName.TX_POOL, this.countTxs, () => store.estimateSize());
126
133
  }
127
134
 
@@ -142,6 +149,7 @@ export class AztecKVTxPool extends (EventEmitter as new () => TypedEventEmitter<
142
149
  }
143
150
  return true;
144
151
  }
152
+
145
153
  /**
146
154
  * Marks transactions as mined in a block and updates the pool state accordingly.
147
155
  * Removes the transactions from the pending set and adds them to the mined set.
@@ -154,68 +162,72 @@ export class AztecKVTxPool extends (EventEmitter as new () => TypedEventEmitter<
154
162
  return Promise.resolve();
155
163
  }
156
164
 
157
- const minedNullifiers = new Set<string>();
158
- const minedFeePayers = new Set<string>();
165
+ const uniqueMinedNullifiers: Fr[] = [];
166
+ const uniqueMinedFeePayers: AztecAddress[] = [];
159
167
 
160
- await this.#store.transactionAsync(async () => {
161
- let pendingTxSize = (await this.#pendingTxSize.getAsync()) ?? 0;
162
- for (const hash of txHashes) {
163
- const key = hash.toString();
168
+ try {
169
+ await this.#store.transactionAsync(async () => {
170
+ for (const hash of txHashes) {
171
+ const key = hash.toString();
172
+ await this.#minedTxHashToBlock.set(key, blockHeader.globalVariables.blockNumber);
173
+
174
+ const tx = await this.getPendingTxByHash(hash);
175
+ if (tx) {
176
+ const nullifiers = tx.data.getNonEmptyNullifiers();
177
+
178
+ nullifiers.forEach(nullifier => insertIntoSortedArray(uniqueMinedNullifiers, nullifier, Fr.cmp, false));
179
+ insertIntoSortedArray(
180
+ uniqueMinedFeePayers,
181
+ tx.data.feePayer,
182
+ (a, b) => a.toField().cmp(b.toField()),
183
+ false,
184
+ );
185
+
186
+ await this.removePendingTxIndicesInDbTx(tx, key);
187
+ }
164
188
 
165
- // If this tx was previously soft-deleted, remove it from the deleted sets
166
- if (await this.#deletedMinedTxHashes.hasAsync(key)) {
167
- const originalBlock = await this.#deletedMinedTxHashes.getAsync(key);
168
- await this.#deletedMinedTxHashes.delete(key);
169
- // Remove from block-to-hash mapping
170
- if (originalBlock !== undefined) {
171
- await this.#blockToDeletedMinedTxHash.deleteValue(originalBlock, key);
189
+ // If this tx was previously soft-deleted, remove it from the deleted sets
190
+ if (await this.#deletedMinedTxHashes.hasAsync(key)) {
191
+ const originalBlock = await this.#deletedMinedTxHashes.getAsync(key);
192
+ await this.#deletedMinedTxHashes.delete(key);
193
+ // Remove from block-to-hash mapping
194
+ if (originalBlock !== undefined) {
195
+ await this.#blockToDeletedMinedTxHash.deleteValue(originalBlock, key);
196
+ }
172
197
  }
173
198
  }
199
+ });
174
200
 
175
- await this.#minedTxHashToBlock.set(key, blockHeader.globalVariables.blockNumber);
176
-
177
- const tx = await this.getPendingTxByHash(hash);
178
- if (tx) {
179
- const nullifiers = tx.data.getNonEmptyNullifiers();
180
- nullifiers.forEach(nullifier => minedNullifiers.add(nullifier.toString()));
181
- minedFeePayers.add(tx.data.feePayer.toString());
182
- pendingTxSize -= tx.getSize();
183
- await this.removePendingTxIndices(tx, key);
184
- }
185
- }
186
- await this.#pendingTxSize.set(pendingTxSize);
201
+ await this.#evictionManager.evictAfterNewBlock(blockHeader, uniqueMinedNullifiers, uniqueMinedFeePayers);
187
202
 
188
- await this.evictInvalidTxsAfterMining(txHashes, blockHeader, minedNullifiers, minedFeePayers);
189
- });
190
- // We update this after the transaction above. This ensures that the non-evictable transactions are not evicted
191
- // until any that have been mined are marked as such.
192
- // The non-evictable set is not considered when evicting transactions that are invalid after a block is mined.
193
- this.#nonEvictableTxs.clear();
203
+ this.#metrics.transactionsRemoved(txHashes.map(hash => hash.toBigInt()));
204
+ } catch (err) {
205
+ this.#log.warn('Unexpected error when marking txs as mined', { err });
206
+ }
194
207
  }
195
208
 
196
- public async markMinedAsPending(txHashes: TxHash[]): Promise<void> {
209
+ public async markMinedAsPending(txHashes: TxHash[], latestBlock: BlockNumber): Promise<void> {
197
210
  if (txHashes.length === 0) {
198
211
  return Promise.resolve();
199
212
  }
200
- await this.#store.transactionAsync(async () => {
201
- let pendingTxSize = (await this.#pendingTxSize.getAsync()) ?? 0;
202
- for (const hash of txHashes) {
203
- const key = hash.toString();
204
- await this.#minedTxHashToBlock.delete(key);
205
-
206
- // Rehydrate the tx in the in-memory pending txs mapping
207
- const tx = await this.getPendingTxByHash(hash);
208
- if (tx) {
209
- await this.addPendingTxIndices(tx, key);
210
- pendingTxSize += tx.getSize();
213
+ try {
214
+ await this.#store.transactionAsync(async () => {
215
+ for (const hash of txHashes) {
216
+ const key = hash.toString();
217
+ await this.#minedTxHashToBlock.delete(key);
218
+
219
+ // Rehydrate the tx in the in-memory pending txs mapping
220
+ const tx = await this.getPendingTxByHash(hash);
221
+ if (tx) {
222
+ await this.addPendingTxIndicesInDbTx(tx, key);
223
+ }
211
224
  }
212
- }
213
-
214
- await this.#pendingTxSize.set(pendingTxSize);
215
- });
225
+ });
216
226
 
217
- await this.evictInvalidTxsAfterReorg(txHashes);
218
- await this.evictLowPriorityTxs(txHashes);
227
+ await this.#evictionManager.evictAfterChainPrune(latestBlock);
228
+ } catch (err) {
229
+ this.#log.warn('Unexpected error when marking mined txs as pending', { err });
230
+ }
219
231
  }
220
232
 
221
233
  public async getPendingTxHashes(): Promise<TxHash[]> {
@@ -223,38 +235,6 @@ export class AztecKVTxPool extends (EventEmitter as new () => TypedEventEmitter<
223
235
  return vals.map(TxHash.fromString);
224
236
  }
225
237
 
226
- public async getMinedTxHashes(): Promise<[TxHash, BlockNumber][]> {
227
- const vals = await toArray(this.#minedTxHashToBlock.entriesAsync());
228
- return vals.map(([txHash, blockNumber]) => [TxHash.fromString(txHash), blockNumber]);
229
- }
230
-
231
- public async getPendingTxCount(): Promise<number> {
232
- return (await this.#pendingTxHashToHeaderHash.sizeAsync()) ?? 0;
233
- }
234
-
235
- public async getMinedTxCount(): Promise<number> {
236
- return (await this.#minedTxHashToBlock.sizeAsync()) ?? 0;
237
- }
238
-
239
- public async getTxStatus(txHash: TxHash): Promise<'pending' | 'mined' | 'deleted' | undefined> {
240
- const key = txHash.toString();
241
- const [isMined, isKnown, isDeleted] = await Promise.all([
242
- this.#minedTxHashToBlock.hasAsync(key),
243
- this.#txs.hasAsync(key),
244
- this.#deletedMinedTxHashes.hasAsync(key),
245
- ]);
246
-
247
- if (isDeleted) {
248
- return 'deleted';
249
- } else if (isMined) {
250
- return 'mined';
251
- } else if (isKnown) {
252
- return 'pending';
253
- } else {
254
- return undefined;
255
- }
256
- }
257
-
258
238
  /**
259
239
  * Checks if a transaction exists in the pool and returns it.
260
240
  * @param txHash - The generated tx hash.
@@ -292,43 +272,53 @@ export class AztecKVTxPool extends (EventEmitter as new () => TypedEventEmitter<
292
272
  /**
293
273
  * Adds a list of transactions to the pool. Duplicates are ignored.
294
274
  * @param txs - An array of txs to be added to the pool.
295
- * @returns Empty promise.
275
+ * @returns count of added transactions
296
276
  */
297
277
  public async addTxs(txs: Tx[], opts: { source?: string } = {}): Promise<number> {
278
+ if (txs.length === 0) {
279
+ return Promise.resolve(0);
280
+ }
281
+
298
282
  const addedTxs: Tx[] = [];
299
283
  const hashesAndStats = txs.map(tx => ({ txHash: tx.getTxHash(), txStats: tx.getStats() }));
300
- await this.#store.transactionAsync(async () => {
301
- let pendingTxSize = (await this.#pendingTxSize.getAsync()) ?? 0;
302
- await Promise.all(
303
- txs.map(async (tx, i) => {
304
- const { txHash, txStats } = hashesAndStats[i];
305
- const key = txHash.toString();
306
- if (await this.#txs.hasAsync(key)) {
307
- this.#log.debug(`Tx ${txHash.toString()} already exists in the pool`);
308
- return;
309
- }
310
-
311
- this.#log.verbose(`Adding tx ${txHash.toString()} to pool`, {
312
- eventName: 'tx-added-to-pool',
313
- ...txStats,
314
- } satisfies TxAddedToPoolStats);
315
-
316
- await this.#txs.set(key, tx.toBuffer());
317
- addedTxs.push(tx as Tx);
284
+ try {
285
+ await this.#store.transactionAsync(async () => {
286
+ await Promise.all(
287
+ txs.map(async (tx, i) => {
288
+ const { txHash, txStats } = hashesAndStats[i];
289
+ const key = txHash.toString();
290
+ if (await this.#txs.hasAsync(key)) {
291
+ this.#log.debug(`Tx ${txHash.toString()} already exists in the pool`);
292
+ return;
293
+ }
318
294
 
319
- if (!(await this.#minedTxHashToBlock.hasAsync(key))) {
320
- pendingTxSize += tx.getSize();
321
- await this.addPendingTxIndices(tx, key);
322
- this.#metrics.recordSize(tx);
323
- }
324
- }),
325
- );
295
+ this.#log.verbose(`Adding tx ${txHash.toString()} to pool`, {
296
+ eventName: 'tx-added-to-pool',
297
+ ...txStats,
298
+ } satisfies TxAddedToPoolStats);
299
+
300
+ await this.#txs.set(key, tx.toBuffer());
301
+ addedTxs.push(tx as Tx);
302
+ await this.#pendingTxHashToHistoricalBlockHeaderHash.set(
303
+ key,
304
+ (await tx.data.constants.anchorBlockHeader.hash()).toString(),
305
+ );
306
+
307
+ if (!(await this.#minedTxHashToBlock.hasAsync(key))) {
308
+ await this.addPendingTxIndicesInDbTx(tx, key);
309
+ this.#metrics.recordSize(tx);
310
+ }
311
+ }),
312
+ );
313
+ });
326
314
 
327
- await this.#pendingTxSize.set(pendingTxSize);
328
- await this.evictLowPriorityTxs(hashesAndStats.map(({ txHash }) => txHash));
329
- });
315
+ await this.#evictionManager.evictAfterNewTxs(addedTxs.map(({ txHash }) => txHash));
316
+ } catch (err) {
317
+ this.#log.warn('Unexpected error when adding txs', { err });
318
+ }
330
319
 
331
320
  if (addedTxs.length > 0) {
321
+ this.#metrics.transactionsAdded(addedTxs);
332
322
  this.emit('txs-added', { ...opts, txs: addedTxs });
333
323
  }
334
324
  return addedTxs.length;
@@ -340,54 +330,62 @@ export class AztecKVTxPool extends (EventEmitter as new () => TypedEventEmitter<
340
330
  * @param txHashes - An array of tx hashes to be deleted from the tx pool.
341
331
  * @returns Empty promise.
342
332
  */
343
- public deleteTxs(txHashes: TxHash[], opts: { eviction?: boolean; permanently?: boolean } = {}): Promise<void> {
333
+ public deleteTxs(txHashes: TxHash[], opts?: { permanently?: boolean }): Promise<void> {
344
334
  if (txHashes.length === 0) {
345
335
  return Promise.resolve();
346
336
  }
337
+
347
338
  const deletedTxs: Tx[] = [];
348
339
  const poolDbTx = this.#store.transactionAsync(async () => {
349
- let pendingTxSize = (await this.#pendingTxSize.getAsync()) ?? 0;
350
340
  for (const hash of txHashes) {
351
341
  const key = hash.toString();
352
342
  const tx = await this.getTxByHash(hash);
343
+ if (!tx) {
344
+ this.#log.trace(`Skipping deletion of missing tx ${key} from pool`);
345
+ continue;
346
+ }
353
347
 
354
- if (tx) {
355
- const minedBlockNumber = await this.#minedTxHashToBlock.getAsync(key);
356
-
357
- if (minedBlockNumber !== undefined) {
358
- await this.#minedTxHashToBlock.delete(key);
359
- if (opts.permanently) {
360
- // Permanently delete mined transactions if specified
361
- this.#log.trace(`Deleting mined tx ${key} from pool`);
362
- await this.#txs.delete(key);
363
- } else {
364
- // Soft-delete mined transactions: remove from mined set but keep in storage
365
- this.#log.trace(`Soft-deleting mined tx ${key} from pool`);
366
- await this.#deletedMinedTxHashes.set(key, minedBlockNumber);
367
- await this.#blockToDeletedMinedTxHash.set(minedBlockNumber, key);
368
- }
369
- } else {
370
- // Permanently delete pending transactions
371
- this.#log.trace(`Deleting pending tx ${key} from pool`);
372
- pendingTxSize -= tx.getSize();
373
- await this.removePendingTxIndices(tx, key);
374
- await this.#txs.delete(key);
375
- }
376
-
377
- if (!opts.eviction && this.#archivedTxLimit) {
348
+ const minedBlockNumber = await this.#minedTxHashToBlock.getAsync(key);
349
+ const txIsPending = minedBlockNumber === undefined;
350
+ if (txIsPending) {
351
+ await this.deletePendingTx(tx, key);
352
+ } else {
353
+ await this.deleteMinedTx(key, minedBlockNumber!, opts?.permanently ?? false);
354
+ const shouldArchiveTx = this.#archivedTxLimit && !opts?.permanently;
355
+ if (shouldArchiveTx) {
378
356
  deletedTxs.push(tx);
379
357
  }
380
- } else {
381
- this.#log.trace(`Skipping deletion of missing tx ${key} from pool`);
382
358
  }
383
359
  }
384
-
385
- await this.#pendingTxSize.set(pendingTxSize);
386
360
  });
361
+ this.#metrics.transactionsRemoved(txHashes.map(hash => hash.toBigInt()));
387
362
  this.#log.debug(`Deleted ${txHashes.length} txs from pool`, { txHashes });
363
+
388
364
  return this.#archivedTxLimit ? poolDbTx.then(() => this.archiveTxs(deletedTxs)) : poolDbTx;
389
365
  }
390
366
 
367
+ private async deleteMinedTx(txHash: `0x${string}`, minedBlockNumber: BlockNumber, permanently: boolean) {
368
+ await this.#minedTxHashToBlock.delete(txHash);
369
+ if (permanently) {
370
+ this.#log.trace(`Deleting mined tx ${txHash} from pool`);
371
+ await this.#txs.delete(txHash);
372
+ return;
373
+ }
374
+
375
+ // Soft-delete mined transactions: remove from mined set but keep in storage
376
+ this.#log.trace(`Soft-deleting mined tx ${txHash} from pool`);
377
+ await this.#deletedMinedTxHashes.set(txHash, minedBlockNumber);
378
+ await this.#blockToDeletedMinedTxHash.set(minedBlockNumber, txHash);
379
+ }
380
+
381
+ private async deletePendingTx(tx: Tx, txHash: `0x${string}`) {
382
+ // We always permanently delete pending transactions
383
+ this.#log.trace(`Deleting pending tx ${txHash} from pool`);
384
+ await this.removePendingTxIndices(tx, txHash);
385
+ await this.#txs.delete(txHash);
386
+ await this.#pendingTxHashToHistoricalBlockHeaderHash.delete(txHash);
387
+ }
388
+
391
389
  /**
392
390
  * Gets all the transactions stored in the pool.
393
391
  * @returns Array of tx objects in the order they were added to the pool.
@@ -406,30 +404,101 @@ export class AztecKVTxPool extends (EventEmitter as new () => TypedEventEmitter<
406
404
  return vals.map(x => TxHash.fromString(x));
407
405
  }
408
406
 
409
- public updateConfig({ maxTxPoolSize, txPoolOverflowFactor, archivedTxLimit }: TxPoolOptions): void {
410
- if (typeof maxTxPoolSize === 'number') {
411
- assert(maxTxPoolSize >= 0, 'maxTxPoolSize must be greater or equal to 0');
412
- this.#maxTxPoolSize = maxTxPoolSize;
407
+ public async getPendingTxInfos(): Promise<PendingTxInfo[]> {
408
+ const vals = await toArray(this.#pendingTxPriorityToHash.valuesAsync());
409
+ const results = await Promise.all(vals.map(val => this.getPendingTxInfo(TxHash.fromString(val))));
410
+ return results.filter((info): info is PendingTxInfo => info !== undefined);
411
+ }
413
412
 
414
- if (maxTxPoolSize === 0) {
415
- this.#log.info(`Disabling maximum tx mempool size. Tx eviction stopped`);
416
- } else {
417
- this.#log.info(`Setting maximum tx mempool size`, { maxTxPoolSize });
413
+ private async getPendingTxInfo(txHash: TxHash): Promise<PendingTxInfo | undefined> {
414
+ let historicalBlockHash = await this.#pendingTxHashToHistoricalBlockHeaderHash.getAsync(txHash.toString());
415
+ // Not all tx might have this index created.
416
+ if (!historicalBlockHash) {
417
+ const tx = await this.getPendingTxByHash(txHash);
418
+ if (!tx) {
419
+ this.#log.warn(`PendingTxInfo:tx ${txHash} not found`);
420
+ return undefined;
418
421
  }
422
+
423
+ historicalBlockHash = (await tx.data.constants.anchorBlockHeader.hash()).toString();
424
+ await this.#pendingTxHashToHistoricalBlockHeaderHash.set(txHash.toString(), historicalBlockHash);
425
+ }
426
+
427
+ return {
428
+ txHash,
429
+ blockHash: Fr.fromString(historicalBlockHash),
430
+ isEvictable: !this.#nonEvictableTxs.has(txHash.toString()),
431
+ };
432
+ }
433
+
434
+ public async getPendingTxsReferencingBlocks(blockHashes: Fr[]): Promise<TxBlockReference[]> {
435
+ const result: TxBlockReference[] = [];
436
+ for (const blockHash of blockHashes) {
437
+ const chunk = await toArray(this.#historicalHeaderToTxHash.getValuesAsync(blockHash.toString()));
438
+ result.push(
439
+ ...chunk.map(txHash => ({
440
+ txHash: TxHash.fromString(txHash),
441
+ blockHash,
442
+ isEvictable: !this.#nonEvictableTxs.has(txHash),
443
+ })),
444
+ );
419
445
  }
420
446
 
421
- if (typeof txPoolOverflowFactor === 'number') {
422
- assert(txPoolOverflowFactor >= 1, 'txPoolOveflowFactor must be greater or equal to 1');
423
- this.txPoolOverflowFactor = txPoolOverflowFactor;
424
- this.#log.info(`Allowing tx pool size to grow above limit`, { maxTxPoolSize, txPoolOverflowFactor });
447
+ return result;
448
+ }
449
+
450
+ public async getPendingTxsWithFeePayer(feePayers: AztecAddress[]): Promise<PendingTxInfo[]> {
451
+ const result: PendingTxInfo[] = [];
452
+ for (const feePayer of feePayers) {
453
+ const chunk = await toArray(this.#feePayerToTxHash.getValuesAsync(feePayer.toString()));
454
+ const infos = await Promise.all(chunk.map(txHash => this.getPendingTxInfo(TxHash.fromString(txHash))));
455
+ result.push(...infos.filter((info): info is PendingTxInfo => info !== undefined));
456
+ }
457
+
458
+ return result;
459
+ }
460
+
461
+ public async getMinedTxHashes(): Promise<[TxHash, BlockNumber][]> {
462
+ const vals = await toArray(this.#minedTxHashToBlock.entriesAsync());
463
+ return vals.map(([txHash, blockNumber]) => [TxHash.fromString(txHash), blockNumber]);
464
+ }
465
+
466
+ public async getPendingTxCount(): Promise<number> {
467
+ return (await this.#pendingTxPriorityToHash.sizeAsync()) ?? 0;
468
+ }
469
+
470
+ public async getMinedTxCount(): Promise<number> {
471
+ return (await this.#minedTxHashToBlock.sizeAsync()) ?? 0;
472
+ }
473
+
474
+ public async getTxStatus(txHash: TxHash): Promise<'pending' | 'mined' | 'deleted' | undefined> {
475
+ const key = txHash.toString();
476
+ const [isMined, isKnown, isDeleted] = await Promise.all([
477
+ this.#minedTxHashToBlock.hasAsync(key),
478
+ this.#txs.hasAsync(key),
479
+ this.#deletedMinedTxHashes.hasAsync(key),
480
+ ]);
481
+
482
+ if (isDeleted) {
483
+ return 'deleted';
484
+ } else if (isMined) {
485
+ return 'mined';
486
+ } else if (isKnown) {
487
+ return 'pending';
488
+ } else {
489
+ return undefined;
425
490
  }
491
+ }
426
492
 
427
- if (typeof archivedTxLimit === 'number') {
428
- assert(archivedTxLimit >= 0, 'archivedTxLimit must be greater or equal to 0');
429
- this.#archivedTxLimit = archivedTxLimit;
493
+ public updateConfig(cfg: TxPoolOptions): void {
494
+ if (typeof cfg.archivedTxLimit === 'number') {
495
+ assert(cfg.archivedTxLimit >= 0, 'archivedTxLimit must be greater or equal to 0');
496
+ this.#archivedTxLimit = cfg.archivedTxLimit;
430
497
  }
431
498
 
432
- // deletedMinedCleanupThresholdMs is no longer used in block-based cleanup
499
+ if (this.#evictionManager) {
500
+ this.#evictionManager.updateConfig(cfg);
501
+ }
433
502
  }
434
503
 
435
504
  public markTxsAsNonEvictable(txHashes: TxHash[]): Promise<void> {
@@ -437,6 +506,15 @@ export class AztecKVTxPool extends (EventEmitter as new () => TypedEventEmitter<
437
506
  return Promise.resolve();
438
507
  }
439
508
 
509
+ public clearNonEvictableTxs(): Promise<void> {
510
+ // Clear the non-evictable set after completing the DB updates above.
511
+ // This ensures pinned (non-evictable) txs are protected while we mark mined txs,
512
+ // but they won't remain pinned indefinitely across blocks. Note that eviction rules
513
+ // (including post-mining invalidation) respect the non-evictable flag while it is set.
514
+ this.#nonEvictableTxs.clear();
515
+ return Promise.resolve();
516
+ }
517
+
440
518
  /**
441
519
  * Permanently deletes deleted mined transactions from blocks up to and including the specified block number.
442
520
  * @param blockNumber - Block number threshold. Deleted mined txs from this block or earlier will be permanently deleted.
@@ -444,10 +522,10 @@ export class AztecKVTxPool extends (EventEmitter as new () => TypedEventEmitter<
444
522
  */
445
523
  public async cleanupDeletedMinedTxs(blockNumber: BlockNumber): Promise<number> {
446
524
  let deletedCount = 0;
447
- const txHashesToDelete: string[] = [];
448
- const blocksToDelete: BlockNumber[] = [];
449
-
450
525
  await this.#store.transactionAsync(async () => {
526
+ const txHashesToDelete: string[] = [];
527
+ const blocksToDelete: BlockNumber[] = [];
528
+
451
529
  // Iterate through all entries and check block numbers
452
530
  for await (const [block, txHash] of this.#blockToDeletedMinedTxHash.entriesAsync()) {
453
531
  if (block <= blockNumber) {
@@ -461,6 +539,7 @@ export class AztecKVTxPool extends (EventEmitter as new () => TypedEventEmitter<
461
539
  deletedCount++;
462
540
  }
463
541
  }
542
+ this.#metrics.transactionsRemoved(txHashesToDelete);
464
543
 
465
544
  // Clean up block-to-hash mapping - delete all values for each block
466
545
  for (const block of blocksToDelete) {
@@ -477,15 +556,6 @@ export class AztecKVTxPool extends (EventEmitter as new () => TypedEventEmitter<
477
556
  return deletedCount;
478
557
  }
479
558
 
480
- /**
481
- * Creates a GasTxValidator instance.
482
- * @param db - DB for the validator to use
483
- * @returns A GasTxValidator instance
484
- */
485
- protected createGasTxValidator(db: MerkleTreeReadOperations): GasTxValidator {
486
- return new GasTxValidator(new DatabasePublicStateSource(db), ProtocolContractAddress.FeeJuice, GasFees.empty());
487
- }
488
-
489
559
  /**
490
560
  * Creates an ArchiveCache instance.
491
561
  * @param db - DB for the cache to use
@@ -502,20 +572,12 @@ export class AztecKVTxPool extends (EventEmitter as new () => TypedEventEmitter<
502
572
  * @returns The transaction, if found, 'undefined' otherwise.
503
573
  */
504
574
  private async getPendingTxByHash(txHash: TxHash | string): Promise<Tx | undefined> {
505
- let key;
506
575
  if (typeof txHash === 'string') {
507
- key = txHash;
508
576
  txHash = TxHash.fromString(txHash);
509
- } else {
510
- key = txHash.toString();
511
577
  }
512
578
 
513
- if (this.#pendingTxs.has(key)) {
514
- return this.#pendingTxs.get(key);
515
- }
516
579
  const tx = await this.getTxByHash(txHash);
517
580
  if (tx) {
518
- this.#pendingTxs.set(key, tx);
519
581
  return tx;
520
582
  }
521
583
  return undefined;
@@ -523,6 +585,7 @@ export class AztecKVTxPool extends (EventEmitter as new () => TypedEventEmitter<
523
585
 
524
586
  /**
525
587
  * Archives a list of txs for future reference. The number of archived txs is limited by the specified archivedTxLimit.
588
+ * Note: Pending txs should not be archived, only finalized txs
526
589
  * @param txs - The list of transactions to archive.
527
590
  * @returns Empty promise.
528
591
  */
@@ -530,6 +593,10 @@ export class AztecKVTxPool extends (EventEmitter as new () => TypedEventEmitter<
530
593
  if (txs.length === 0) {
531
594
  return;
532
595
  }
596
+ if (this.#archivedTxLimit === 0) {
597
+ return;
598
+ }
599
+
533
600
  try {
534
601
  const txHashes = await Promise.all(txs.map(tx => tx.getTxHash()));
535
602
  await this.#archive.transactionAsync(async () => {
@@ -569,183 +636,56 @@ export class AztecKVTxPool extends (EventEmitter as new () => TypedEventEmitter<
569
636
  }
570
637
  }
571
638
 
572
- /**
573
- * Evicts pending txs with the lowest priority fees from the pool to accomodate the max tx count and cumulative max tx size
574
- * after new txs are added.
575
- *
576
- * @param newTxHashes - The tx hashes of the new txs added to the pool.
577
- * @returns The total number of txs evicted from the pool and the number of new txs that were evicted.
578
- */
579
- private async evictLowPriorityTxs(
580
- newTxHashes: TxHash[],
581
- ): Promise<{ numLowPriorityTxsEvicted: number; numNewTxsEvicted: number }> {
582
- if (this.#maxTxPoolSize === undefined || this.#maxTxPoolSize === 0) {
583
- return { numLowPriorityTxsEvicted: 0, numNewTxsEvicted: 0 };
584
- }
585
-
586
- let numNewTxsEvicted = 0;
587
- const txsToEvict: TxHash[] = [];
588
-
589
- let pendingTxsSize = (await this.#pendingTxSize.getAsync()) ?? 0;
590
- if (pendingTxsSize > this.#maxTxPoolSize * this.txPoolOverflowFactor) {
591
- for await (const txHash of this.#pendingTxPriorityToHash.valuesAsync()) {
592
- if (this.#nonEvictableTxs.has(txHash.toString())) {
593
- continue;
594
- }
595
- const txSize =
596
- (await this.#pendingTxHashToSize.getAsync(txHash.toString())) ??
597
- (await this.getPendingTxByHash(txHash))?.getSize();
598
-
599
- this.#log.verbose(`Evicting tx ${txHash} from pool due to low priority to satisfy max tx size limit`, {
600
- txHash,
601
- txSize,
602
- });
603
-
604
- txsToEvict.push(TxHash.fromString(txHash));
605
-
606
- if (txSize) {
607
- pendingTxsSize -= txSize;
608
- if (pendingTxsSize <= this.#maxTxPoolSize) {
609
- break;
610
- }
611
- }
612
- }
613
- numNewTxsEvicted += newTxHashes.filter(txHash => txsToEvict.includes(txHash)).length;
614
- }
615
-
616
- if (txsToEvict.length > 0) {
617
- await this.deleteTxs(txsToEvict, { eviction: true });
618
- }
619
- return {
620
- numLowPriorityTxsEvicted: txsToEvict.length,
621
- numNewTxsEvicted,
622
- };
639
+ // Assumes being called within a DB transaction
640
+ private async addPendingTxIndicesInDbTx(tx: Tx, txHash: string): Promise<void> {
641
+ await this.#pendingTxPriorityToHash.set(getPendingTxPriority(tx), txHash);
642
+ await this.#historicalHeaderToTxHash.set((await tx.data.constants.anchorBlockHeader.hash()).toString(), txHash);
643
+ await this.#feePayerToTxHash.set(tx.data.feePayer.toString(), txHash);
623
644
  }
624
645
 
625
- /**
626
- * Evicts invalid pending txs from the pool after a txs from a block are mined.
627
- * Eviction criteria includes:
628
- * - txs with nullifiers that are already included in the mined block
629
- * - txs with an insufficient fee payer balance
630
- * - txs with an expiration timestamp lower than that of the mined block
631
- *
632
- * @param minedTxHashes - The tx hashes of the txs mined in the block.
633
- * @param blockHeader - The header of the mined block.
634
- * @returns The total number of txs evicted from the pool.
635
- */
636
- private async evictInvalidTxsAfterMining(
637
- minedTxHashes: TxHash[],
638
- blockHeader: BlockHeader,
639
- minedNullifiers: Set<string>,
640
- minedFeePayers: Set<string>,
641
- ): Promise<number> {
642
- if (minedTxHashes.length === 0) {
643
- return 0;
644
- }
645
-
646
- const { blockNumber, timestamp } = blockHeader.globalVariables;
647
-
648
- // Wait for world state to be synced to at least the mined block number
649
- await this.#worldStateSynchronizer.syncImmediate(blockNumber);
650
-
651
- const db = this.#worldStateSynchronizer.getCommitted();
652
- const gasTxValidator = this.createGasTxValidator(db);
653
-
654
- const txsToEvict: TxHash[] = [];
655
- for await (const txHash of this.#pendingTxPriorityToHash.valuesAsync()) {
656
- const tx = await this.getPendingTxByHash(txHash);
657
- if (!tx) {
658
- continue;
659
- }
660
-
661
- // Evict pending txs that share nullifiers with mined txs
662
- const txNullifiers = tx.data.getNonEmptyNullifiers();
663
- if (txNullifiers.some(nullifier => minedNullifiers.has(nullifier.toString()))) {
664
- this.#log.verbose(`Evicting tx ${txHash} from pool due to a duplicate nullifier with a mined tx`);
665
- txsToEvict.push(TxHash.fromString(txHash));
666
- continue;
667
- }
668
-
669
- // Evict pending txs with an insufficient fee payer balance
670
- if (
671
- minedFeePayers.has(tx.data.feePayer.toString()) &&
672
- (await gasTxValidator.validateTxFee(tx)).result === 'invalid'
673
- ) {
674
- this.#log.verbose(`Evicting tx ${txHash} from pool due to an insufficient fee payer balance`);
675
- txsToEvict.push(TxHash.fromString(txHash));
676
- continue;
677
- }
646
+ private async addPendingTxIndices(tx: Tx, txHash: string): Promise<void> {
647
+ return await this.#store.transactionAsync(async () => {
648
+ await this.addPendingTxIndicesInDbTx(tx, txHash);
649
+ });
650
+ }
678
651
 
679
- // Evict pending txs with an expiration timestamp less than or equal to the mined block timestamp
680
- const includeByTimestamp = tx.data.includeByTimestamp;
681
- if (includeByTimestamp <= timestamp) {
682
- this.#log.verbose(
683
- `Evicting tx ${txHash} from pool due to the tx being expired (includeByTimestamp: ${includeByTimestamp}, mined block timestamp: ${timestamp})`,
684
- );
685
- txsToEvict.push(TxHash.fromString(txHash));
686
- continue;
687
- }
688
- }
652
+ // Assumes being called within a DB transaction
653
+ private async removePendingTxIndicesInDbTx(tx: Tx, txHash: string): Promise<void> {
654
+ await this.#pendingTxPriorityToHash.deleteValue(getPendingTxPriority(tx), txHash);
655
+ await this.#historicalHeaderToTxHash.deleteValue(
656
+ (await tx.data.constants.anchorBlockHeader.hash()).toString(),
657
+ txHash,
658
+ );
659
+ await this.#feePayerToTxHash.deleteValue(tx.data.feePayer.toString(), txHash);
660
+ }
689
661
 
690
- if (txsToEvict.length > 0) {
691
- await this.deleteTxs(txsToEvict, { eviction: true });
692
- }
693
- return txsToEvict.length;
662
+ private async removePendingTxIndices(tx: Tx, txHash: string): Promise<void> {
663
+ return await this.#store.transactionAsync(async () => {
664
+ await this.removePendingTxIndicesInDbTx(tx, txHash);
665
+ });
694
666
  }
695
667
 
696
668
  /**
697
- * Evicts pending txs that no longer have valid archive roots or fee payer balances from the pool after a reorg.
698
- *
699
- * @param txHashes - The tx hashes of the txs that were moved from mined to pending.
700
- * @returns The total number of txs evicted from the pool.
669
+ * Returns up to `limit` lowest-priority evictable pending tx hashes without hydrating transactions.
670
+ * Iterates the priority index in ascending order and skips non-evictable txs.
701
671
  */
702
- private async evictInvalidTxsAfterReorg(txHashes: TxHash[]): Promise<number> {
703
- if (txHashes.length === 0) {
704
- return 0;
705
- }
706
-
707
- await this.#worldStateSynchronizer.syncImmediate();
708
- const db = this.#worldStateSynchronizer.getCommitted();
709
- const archiveCache = this.createArchiveCache(db);
710
- const gasTxValidator = this.createGasTxValidator(db);
711
-
672
+ public async getLowestPriorityEvictable(limit: number): Promise<TxHash[]> {
712
673
  const txsToEvict: TxHash[] = [];
674
+ if (limit <= 0) {
675
+ return txsToEvict;
676
+ }
713
677
 
714
- for await (const [txHash, headerHash] of this.#pendingTxHashToHeaderHash.entriesAsync()) {
715
- const tx = await this.getPendingTxByHash(txHash);
716
- if (!tx) {
717
- continue;
718
- }
719
-
720
- const [index] = await archiveCache.getArchiveIndices([Fr.fromString(headerHash)]);
721
- if (index === undefined) {
722
- this.#log.verbose(`Evicting tx ${txHash} from pool due to an invalid archive root`);
723
- txsToEvict.push(TxHash.fromString(txHash));
678
+ for await (const txHashStr of this.#pendingTxPriorityToHash.valuesAsync()) {
679
+ if (this.#nonEvictableTxs.has(txHashStr)) {
724
680
  continue;
725
681
  }
726
682
 
727
- if ((await gasTxValidator.validateTxFee(tx)).result === 'invalid') {
728
- this.#log.verbose(`Evicting tx ${txHash} from pool due to an insufficient fee payer balance`);
729
- txsToEvict.push(TxHash.fromString(txHash));
683
+ txsToEvict.push(TxHash.fromString(txHashStr));
684
+ if (txsToEvict.length >= limit) {
685
+ break;
730
686
  }
731
687
  }
732
688
 
733
- if (txsToEvict.length > 0) {
734
- await this.deleteTxs(txsToEvict, { eviction: true });
735
- }
736
- return txsToEvict.length;
737
- }
738
-
739
- private async addPendingTxIndices(tx: Tx, txHash: string): Promise<void> {
740
- await this.#pendingTxPriorityToHash.set(getPendingTxPriority(tx), txHash);
741
- await this.#pendingTxHashToSize.set(txHash, tx.getSize());
742
- await this.#pendingTxHashToHeaderHash.set(txHash, (await tx.data.constants.anchorBlockHeader.hash()).toString());
743
- }
744
-
745
- private async removePendingTxIndices(tx: Tx, txHash: string): Promise<void> {
746
- await this.#pendingTxPriorityToHash.deleteValue(getPendingTxPriority(tx), txHash);
747
- await this.#pendingTxHashToSize.delete(txHash);
748
- await this.#pendingTxHashToHeaderHash.delete(txHash);
749
- this.#pendingTxs.delete(txHash);
689
+ return txsToEvict;
750
690
  }
751
691
  }