@aztec/p2p 0.0.1-commit.2e2504e2 → 0.0.1-commit.2eb6648a

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 (108) hide show
  1. package/dest/client/factory.d.ts +3 -3
  2. package/dest/client/factory.d.ts.map +1 -1
  3. package/dest/client/factory.js +2 -2
  4. package/dest/client/interface.d.ts +9 -2
  5. package/dest/client/interface.d.ts.map +1 -1
  6. package/dest/client/p2p_client.d.ts +4 -3
  7. package/dest/client/p2p_client.d.ts.map +1 -1
  8. package/dest/client/p2p_client.js +11 -5
  9. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +1 -1
  10. package/dest/config.d.ts +1 -1
  11. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +94 -87
  12. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  13. package/dest/mem_pools/attestation_pool/attestation_pool.js +411 -3
  14. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts +2 -2
  15. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -1
  16. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +351 -85
  17. package/dest/mem_pools/attestation_pool/index.d.ts +2 -3
  18. package/dest/mem_pools/attestation_pool/index.d.ts.map +1 -1
  19. package/dest/mem_pools/attestation_pool/index.js +1 -2
  20. package/dest/mem_pools/index.d.ts +2 -2
  21. package/dest/mem_pools/index.d.ts.map +1 -1
  22. package/dest/mem_pools/index.js +1 -1
  23. package/dest/mem_pools/interface.d.ts +3 -3
  24. package/dest/mem_pools/interface.d.ts.map +1 -1
  25. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +5 -1
  26. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  27. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +1 -1
  28. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
  29. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +2 -1
  30. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +99 -0
  31. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -0
  32. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +332 -0
  33. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +1 -1
  34. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
  35. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +6 -0
  36. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +1 -1
  37. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  38. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +193 -486
  39. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts +3 -3
  40. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts.map +1 -1
  41. package/dest/services/dummy_service.d.ts +6 -2
  42. package/dest/services/dummy_service.d.ts.map +1 -1
  43. package/dest/services/dummy_service.js +3 -0
  44. package/dest/services/libp2p/libp2p_service.d.ts +74 -33
  45. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  46. package/dest/services/libp2p/libp2p_service.js +299 -228
  47. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +4 -4
  48. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
  49. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +8 -8
  50. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts +6 -4
  51. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts.map +1 -1
  52. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.js +16 -11
  53. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts +15 -10
  54. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts.map +1 -1
  55. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.js +12 -11
  56. package/dest/services/service.d.ts +18 -1
  57. package/dest/services/service.d.ts.map +1 -1
  58. package/dest/services/tx_collection/config.d.ts +3 -3
  59. package/dest/services/tx_collection/config.js +3 -3
  60. package/dest/services/tx_collection/fast_tx_collection.d.ts +4 -5
  61. package/dest/services/tx_collection/fast_tx_collection.d.ts.map +1 -1
  62. package/dest/services/tx_collection/fast_tx_collection.js +10 -14
  63. package/dest/services/tx_collection/index.d.ts +1 -1
  64. package/dest/services/tx_collection/proposal_tx_collector.d.ts +12 -12
  65. package/dest/services/tx_collection/proposal_tx_collector.d.ts.map +1 -1
  66. package/dest/services/tx_collection/proposal_tx_collector.js +4 -5
  67. package/dest/test-helpers/testbench-utils.d.ts +10 -16
  68. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  69. package/dest/test-helpers/testbench-utils.js +32 -30
  70. package/dest/testbench/p2p_client_testbench_worker.js +1 -1
  71. package/package.json +14 -14
  72. package/src/client/factory.ts +3 -4
  73. package/src/client/interface.ts +13 -1
  74. package/src/client/p2p_client.ts +20 -8
  75. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +1 -1
  76. package/src/mem_pools/attestation_pool/attestation_pool.ts +444 -90
  77. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +436 -100
  78. package/src/mem_pools/attestation_pool/index.ts +9 -2
  79. package/src/mem_pools/index.ts +1 -1
  80. package/src/mem_pools/interface.ts +2 -2
  81. package/src/mem_pools/tx_pool_v2/README.md +28 -7
  82. package/src/mem_pools/tx_pool_v2/interfaces.ts +2 -0
  83. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +2 -1
  84. package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +417 -0
  85. package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +3 -0
  86. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +185 -568
  87. package/src/msg_validators/attestation_validator/fisherman_attestation_validator.ts +2 -2
  88. package/src/services/dummy_service.ts +6 -0
  89. package/src/services/libp2p/libp2p_service.ts +304 -230
  90. package/src/services/reqresp/batch-tx-requester/README.md +7 -7
  91. package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +11 -11
  92. package/src/services/reqresp/protocols/block_txs/block_txs_handler.ts +22 -13
  93. package/src/services/reqresp/protocols/block_txs/block_txs_reqresp.ts +21 -15
  94. package/src/services/service.ts +20 -0
  95. package/src/services/tx_collection/config.ts +6 -6
  96. package/src/services/tx_collection/fast_tx_collection.ts +14 -24
  97. package/src/services/tx_collection/index.ts +1 -1
  98. package/src/services/tx_collection/proposal_tx_collector.ts +12 -14
  99. package/src/test-helpers/testbench-utils.ts +18 -39
  100. package/src/testbench/p2p_client_testbench_worker.ts +1 -1
  101. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts +0 -40
  102. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts.map +0 -1
  103. package/dest/mem_pools/attestation_pool/kv_attestation_pool.js +0 -218
  104. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts +0 -31
  105. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts.map +0 -1
  106. package/dest/mem_pools/attestation_pool/memory_attestation_pool.js +0 -180
  107. package/src/mem_pools/attestation_pool/kv_attestation_pool.ts +0 -320
  108. package/src/mem_pools/attestation_pool/memory_attestation_pool.ts +0 -264
@@ -29,14 +29,8 @@ import {
29
29
  type TxPoolV2Config,
30
30
  type TxPoolV2Dependencies,
31
31
  } from './interfaces.js';
32
- import {
33
- type TxMetaData,
34
- type TxState,
35
- buildTxMetaData,
36
- checkNullifierConflict,
37
- compareFee,
38
- compareTxHash,
39
- } from './tx_metadata.js';
32
+ import { type TxMetaData, type TxState, buildTxMetaData, checkNullifierConflict } from './tx_metadata.js';
33
+ import { TxPoolIndices } from './tx_pool_indices.js';
40
34
 
41
35
  /**
42
36
  * Callbacks for the implementation to notify the outer class about events and metrics.
@@ -62,19 +56,7 @@ export class TxPoolV2Impl {
62
56
  #pendingTxValidator: TxValidator<Tx>;
63
57
 
64
58
  // === In-Memory Indices ===
65
- /** Primary metadata store: txHash -> TxMetaData */
66
- #metadata: Map<string, TxMetaData> = new Map();
67
- /** Nullifier to txHash index (pending txs only) */
68
- #nullifierToTxHash: Map<string, string> = new Map();
69
- /** Fee payer to txHashes index (pending txs only) */
70
- #feePayerToTxHashes: Map<string, Set<string>> = new Map();
71
- /**
72
- * Pending txHashes grouped by priority fee.
73
- * Outer map: priorityFee -> Set of txHashes at that fee level.
74
- */
75
- #pendingByPriority: Map<bigint, Set<string>> = new Map();
76
- /** Protected transactions: txHash -> slotNumber. Includes txs we have and txs we expect to receive. */
77
- #protectedTransactions: Map<string, SlotNumber> = new Map();
59
+ #indices: TxPoolIndices = new TxPoolIndices();
78
60
 
79
61
  // === Config & Services ===
80
62
  #config: TxPoolV2Config;
@@ -141,19 +123,29 @@ export class TxPoolV2Impl {
141
123
  await this.#markMinedStatusBatch(loaded.map(l => l.meta));
142
124
 
143
125
  // Step 3: Partition by mined status
144
- const { mined, nonMined } = this.#partitionByMinedStatus(loaded);
126
+ const mined: TxMetaData[] = [];
127
+ const nonMined: { tx: Tx; meta: TxMetaData }[] = [];
128
+ for (const entry of loaded) {
129
+ if (entry.meta.minedL2BlockId !== undefined) {
130
+ mined.push(entry.meta);
131
+ } else {
132
+ nonMined.push(entry);
133
+ }
134
+ }
145
135
 
146
136
  // Step 4: Validate non-mined transactions
147
- const { valid, invalid } = await this.#validateNonMinedTxs(nonMined);
137
+ const { valid, invalid } = await this.#validateTxBatch(nonMined, 'on startup');
148
138
 
149
139
  // Step 5: Populate mined indices (these don't need conflict resolution)
150
- this.#populateMinedIndices(mined);
140
+ for (const meta of mined) {
141
+ this.#indices.addMined(meta);
142
+ }
151
143
 
152
144
  // Step 6: Rebuild pending pool by running pre-add rules for each tx
153
145
  // This resolves nullifier conflicts, fee payer balance issues, and pool size limits
154
146
  const { rejected } = await this.#rebuildPendingPool(valid);
155
147
 
156
- // Step 7: Delete invalid and rejected txs from DB
148
+ // Step 7: Delete invalid and rejected txs from DB only (indices were never populated for these)
157
149
  const toDelete = [...deserializationErrors, ...invalid, ...rejected];
158
150
  if (toDelete.length === 0) {
159
151
  return;
@@ -170,7 +162,6 @@ export class TxPoolV2Impl {
170
162
  const accepted: TxHash[] = [];
171
163
  const ignored: TxHash[] = [];
172
164
  const rejected: TxHash[] = [];
173
- const newlyAdded: Tx[] = [];
174
165
  const acceptedPending = new Set<string>();
175
166
 
176
167
  const poolAccess = this.#createPreAddPoolAccess();
@@ -181,31 +172,28 @@ export class TxPoolV2Impl {
181
172
  const txHashStr = txHash.toString();
182
173
 
183
174
  // Skip duplicates
184
- if (this.#isDuplicateTx(txHashStr)) {
175
+ if (this.#indices.has(txHashStr)) {
185
176
  ignored.push(txHash);
186
177
  continue;
187
178
  }
188
179
 
189
180
  // Check mined status first (applies to all paths)
190
181
  const minedBlockId = await this.#getMinedBlockId(txHash);
191
- const preProtectedSlot = this.#protectedTransactions.get(txHashStr);
182
+ const preProtectedSlot = this.#indices.getProtectionSlot(txHashStr);
192
183
 
193
184
  if (minedBlockId) {
194
185
  // Already mined - add directly (protection already set if pre-protected)
195
- await this.#addNewMinedTx(tx, minedBlockId);
186
+ await this.#addTx(tx, { mined: minedBlockId }, opts);
196
187
  accepted.push(txHash);
197
- newlyAdded.push(tx);
198
188
  } else if (preProtectedSlot !== undefined) {
199
189
  // Pre-protected and not mined - add as protected (bypass validation)
200
- await this.#addNewProtectedTx(tx, preProtectedSlot);
190
+ await this.#addTx(tx, { protected: preProtectedSlot }, opts);
201
191
  accepted.push(txHash);
202
- newlyAdded.push(tx);
203
192
  } else {
204
193
  // Regular pending tx - validate and run pre-add rules
205
- const result = await this.#tryAddRegularPendingTx(tx, poolAccess, acceptedPending, ignored);
194
+ const result = await this.#tryAddRegularPendingTx(tx, opts, poolAccess, acceptedPending, ignored);
206
195
  if (result.status === 'accepted') {
207
196
  acceptedPending.add(txHashStr);
208
- newlyAdded.push(tx);
209
197
  } else if (result.status === 'rejected') {
210
198
  rejected.push(txHash);
211
199
  } else {
@@ -222,22 +210,18 @@ export class TxPoolV2Impl {
222
210
 
223
211
  // Run post-add eviction rules for pending txs
224
212
  if (acceptedPending.size > 0) {
225
- const feePayers = Array.from(acceptedPending).map(txHash => this.#metadata.get(txHash)!.feePayer);
213
+ const feePayers = Array.from(acceptedPending).map(txHash => this.#indices.getMetadata(txHash)!.feePayer);
226
214
  const uniqueFeePayers = new Set<string>(feePayers);
227
215
  await this.#evictionManager.evictAfterNewTxs(Array.from(acceptedPending), [...uniqueFeePayers]);
228
216
  }
229
217
 
230
- // Emit events
231
- if (newlyAdded.length > 0) {
232
- this.#callbacks.onTxsAdded(newlyAdded, opts);
233
- }
234
-
235
218
  return { accepted, ignored, rejected };
236
219
  }
237
220
 
238
221
  /** Validates and adds a regular pending tx. Returns status. */
239
222
  async #tryAddRegularPendingTx(
240
223
  tx: Tx,
224
+ opts: { source?: string },
241
225
  poolAccess: PreAddPoolAccess,
242
226
  acceptedPending: Set<string>,
243
227
  ignored: TxHash[],
@@ -246,9 +230,7 @@ export class TxPoolV2Impl {
246
230
  const txHashStr = txHash.toString();
247
231
 
248
232
  // Validate transaction
249
- const validationResult = await this.#pendingTxValidator.validateTx(tx);
250
- if (validationResult.result !== 'valid') {
251
- this.#log.info(`Rejecting tx ${txHashStr}: ${validationResult.reason?.join(', ')}`);
233
+ if (!(await this.#validateTx(tx))) {
252
234
  return { status: 'rejected' };
253
235
  }
254
236
 
@@ -261,18 +243,19 @@ export class TxPoolV2Impl {
261
243
  return { status: 'ignored' };
262
244
  }
263
245
 
264
- // Evict conflicts (tracking intra-batch evictions)
246
+ // Evict conflicts
265
247
  for (const evictHashStr of preAddResult.txHashesToEvict) {
266
248
  await this.#deleteTx(evictHashStr);
267
249
  this.#log.debug(`Evicted tx ${evictHashStr} due to higher-fee tx ${txHashStr}`);
268
250
  if (acceptedPending.has(evictHashStr)) {
251
+ // Evicted tx was from this batch - mark as ignored in result
269
252
  acceptedPending.delete(evictHashStr);
270
253
  ignored.push(TxHash.fromString(evictHashStr));
271
254
  }
272
255
  }
273
256
 
274
257
  // Add the transaction
275
- await this.#addNewPendingTx(tx);
258
+ await this.#addTx(tx, 'pending', opts);
276
259
  return { status: 'accepted' };
277
260
  }
278
261
 
@@ -280,11 +263,11 @@ export class TxPoolV2Impl {
280
263
  const txHashStr = tx.getTxHash().toString();
281
264
 
282
265
  // Check if already in pool
283
- if (this.#metadata.has(txHashStr)) {
266
+ if (this.#indices.has(txHashStr)) {
284
267
  return 'ignored';
285
268
  }
286
269
 
287
- // Validate transaction
270
+ // Validate transaction (no logging for dry-run check)
288
271
  const validationResult = await this.#pendingTxValidator.validateTx(tx);
289
272
  if (validationResult.result !== 'valid') {
290
273
  return 'rejected';
@@ -300,37 +283,32 @@ export class TxPoolV2Impl {
300
283
 
301
284
  async addProtectedTxs(txs: Tx[], block: BlockHeader, opts: { source?: string }): Promise<void> {
302
285
  const slotNumber = block.globalVariables.slotNumber;
303
- const newlyAdded: Tx[] = [];
304
286
 
305
287
  await this.#store.transactionAsync(async () => {
306
288
  for (const tx of txs) {
307
289
  const txHash = tx.getTxHash();
308
290
  const txHashStr = txHash.toString();
309
- const isNew = !this.#metadata.has(txHashStr);
291
+ const isNew = !this.#indices.has(txHashStr);
310
292
  const minedBlockId = await this.#getMinedBlockId(txHash);
311
293
 
312
294
  if (isNew) {
313
- // New tx - add as mined or protected
295
+ // New tx - add as mined or protected (callback emitted by #addTx)
314
296
  if (minedBlockId) {
315
- await this.#addNewMinedTx(tx, minedBlockId);
316
- this.#protectedTransactions.set(txHashStr, slotNumber);
297
+ await this.#addTx(tx, { mined: minedBlockId }, opts);
298
+ this.#indices.setProtection(txHashStr, slotNumber);
317
299
  } else {
318
- await this.#addNewProtectedTx(tx, slotNumber);
300
+ await this.#addTx(tx, { protected: slotNumber }, opts);
319
301
  }
320
- newlyAdded.push(tx);
321
302
  } else {
322
303
  // Existing tx - update protection and mined status
323
- this.#updateProtection(txHashStr, slotNumber);
304
+ this.#indices.updateProtection(txHashStr, slotNumber);
324
305
  if (minedBlockId) {
325
- this.#markAsMined(this.#metadata.get(txHashStr)!, minedBlockId);
306
+ const meta = this.#indices.getMetadata(txHashStr)!;
307
+ this.#indices.markAsMined(meta, minedBlockId);
326
308
  }
327
309
  }
328
310
  }
329
311
  });
330
-
331
- if (newlyAdded.length > 0) {
332
- this.#callbacks.onTxsAdded(newlyAdded, opts);
333
- }
334
312
  }
335
313
 
336
314
  protectTxs(txHashes: TxHash[], block: BlockHeader): TxHash[] {
@@ -340,12 +318,12 @@ export class TxPoolV2Impl {
340
318
  for (const txHash of txHashes) {
341
319
  const txHashStr = txHash.toString();
342
320
 
343
- if (this.#metadata.has(txHashStr)) {
344
- // Step 1a: Update protection for existing tx
345
- this.#updateProtection(txHashStr, slotNumber);
321
+ if (this.#indices.has(txHashStr)) {
322
+ // Update protection for existing tx
323
+ this.#indices.updateProtection(txHashStr, slotNumber);
346
324
  } else {
347
- // Step 1b: Pre-record protection for tx we don't have yet
348
- this.#protectedTransactions.set(txHashStr, slotNumber);
325
+ // Pre-record protection for tx we don't have yet
326
+ this.#indices.setProtection(txHashStr, slotNumber);
349
327
  missing.push(txHash);
350
328
  }
351
329
  }
@@ -356,28 +334,21 @@ export class TxPoolV2Impl {
356
334
  async addMinedTxs(txs: Tx[], block: BlockHeader, opts: { source?: string }): Promise<void> {
357
335
  // Step 1: Build block ID
358
336
  const blockId = await this.#buildBlockId(block);
359
- const newlyAdded: Tx[] = [];
360
337
 
361
338
  await this.#store.transactionAsync(async () => {
362
339
  for (const tx of txs) {
363
340
  const txHashStr = tx.getTxHash().toString();
364
- const existingMeta = this.#metadata.get(txHashStr);
341
+ const existingMeta = this.#indices.getMetadata(txHashStr);
365
342
 
366
343
  if (existingMeta) {
367
- // Step 2a: Mark existing tx as mined
368
- this.#markAsMined(existingMeta, blockId);
344
+ // Mark existing tx as mined
345
+ this.#indices.markAsMined(existingMeta, blockId);
369
346
  } else {
370
- // Step 2b: Add new mined tx
371
- await this.#addNewMinedTx(tx, blockId);
372
- newlyAdded.push(tx);
347
+ // Add new mined tx (callback emitted by #addTx)
348
+ await this.#addTx(tx, { mined: blockId }, opts);
373
349
  }
374
350
  }
375
351
  });
376
-
377
- // Step 3: Emit events for newly added txs
378
- if (newlyAdded.length > 0) {
379
- this.#callbacks.onTxsAdded(newlyAdded, opts);
380
- }
381
352
  }
382
353
 
383
354
  async handleMinedBlock(block: L2Block): Promise<void> {
@@ -392,7 +363,7 @@ export class TxPoolV2Impl {
392
363
  const feePayers: string[] = [];
393
364
  const found: TxMetaData[] = [];
394
365
  for (const txHash of txHashes) {
395
- const meta = this.#metadata.get(txHash.toString());
366
+ const meta = this.#indices.getMetadata(txHash.toString());
396
367
  if (meta) {
397
368
  feePayers.push(meta.feePayer);
398
369
  found.push(meta);
@@ -400,24 +371,25 @@ export class TxPoolV2Impl {
400
371
  }
401
372
 
402
373
  // Step 4: Mark txs as mined (only those we have in the pool)
403
- this.#markTxsAsMined(found, blockId);
374
+ for (const meta of found) {
375
+ this.#indices.markAsMined(meta, blockId);
376
+ }
404
377
 
405
378
  // Step 5: Run eviction rules (remove pending txs with conflicting nullifiers/expired timestamps)
406
379
  await this.#evictionManager.evictAfterNewBlock(block.header, nullifiers, feePayers);
407
380
 
408
- this.#callbacks.onTxsRemoved(txHashes.map(h => h.toBigInt()));
409
381
  this.#log.info(`Marked ${found.length} txs as mined in block ${blockId.number}`);
410
382
  }
411
383
 
412
384
  async prepareForSlot(slotNumber: SlotNumber): Promise<void> {
413
385
  // Step 1: Find expired protected txs
414
- const expiredProtected = this.#findExpiredProtectedTxs(slotNumber);
386
+ const expiredProtected = this.#indices.findExpiredProtectedTxs(slotNumber);
415
387
 
416
388
  // Step 2: Clear protection for all expired entries (including those without metadata)
417
- this.#clearProtection(expiredProtected);
389
+ this.#indices.clearProtection(expiredProtected);
418
390
 
419
391
  // Step 3: Filter to only txs that have metadata and are not mined
420
- const txsToRestore = this.#filterRestorable(expiredProtected);
392
+ const txsToRestore = this.#indices.filterRestorable(expiredProtected);
421
393
  if (txsToRestore.length === 0) {
422
394
  return;
423
395
  }
@@ -425,7 +397,7 @@ export class TxPoolV2Impl {
425
397
  this.#log.info(`Preparing for slot ${slotNumber}: unprotecting ${txsToRestore.length} txs`);
426
398
 
427
399
  // Step 4: Validate for pending pool
428
- const { valid, invalid } = await this.#validateForPending(txsToRestore);
400
+ const { valid, invalid } = await this.#loadAndValidateTxs(txsToRestore, 'during prepareForSlot');
429
401
 
430
402
  // Step 5: Resolve nullifier conflicts and add winners to pending indices
431
403
  const { added, toEvict } = this.#applyNullifierConflictResolution(valid);
@@ -446,7 +418,7 @@ export class TxPoolV2Impl {
446
418
 
447
419
  async handlePrunedBlocks(latestBlock: L2BlockId): Promise<void> {
448
420
  // Step 1: Find transactions mined after the prune point
449
- const txsToUnmine = this.#findTxsMinedAfter(latestBlock.number);
421
+ const txsToUnmine = this.#indices.findTxsMinedAfter(latestBlock.number);
450
422
  if (txsToUnmine.length === 0) {
451
423
  this.#log.debug(`No transactions to un-mine for prune to block ${latestBlock.number}`);
452
424
  return;
@@ -455,13 +427,15 @@ export class TxPoolV2Impl {
455
427
  this.#log.info(`Handling prune to block ${latestBlock.number}: un-mining ${txsToUnmine.length} txs`);
456
428
 
457
429
  // Step 2: Unmine - clear mined status from metadata
458
- this.#unmineTxs(txsToUnmine);
430
+ for (const meta of txsToUnmine) {
431
+ this.#indices.markAsUnmined(meta);
432
+ }
459
433
 
460
434
  // Step 3: Filter out protected txs (they'll be handled by prepareForSlot)
461
- const unprotectedTxs = this.#filterUnprotected(txsToUnmine);
435
+ const unprotectedTxs = this.#indices.filterUnprotected(txsToUnmine);
462
436
 
463
437
  // Step 4: Validate for pending pool
464
- const { valid, invalid } = await this.#validateForPending(unprotectedTxs);
438
+ const { valid, invalid } = await this.#loadAndValidateTxs(unprotectedTxs, 'during handlePrunedBlocks');
465
439
 
466
440
  // Step 5: Resolve nullifier conflicts and add winners to pending indices
467
441
  const { toEvict } = this.#applyNullifierConflictResolution(valid);
@@ -475,7 +449,7 @@ export class TxPoolV2Impl {
475
449
  }
476
450
 
477
451
  async handleFailedExecution(txHashes: TxHash[]): Promise<void> {
478
- // Step 1: Delete failed txs
452
+ // Delete failed txs
479
453
  await this.#deleteTxsBatch(txHashes.map(h => h.toString()));
480
454
 
481
455
  this.#log.info(`Deleted ${txHashes.length} failed txs`);
@@ -485,7 +459,7 @@ export class TxPoolV2Impl {
485
459
  const blockNumber = block.globalVariables.blockNumber;
486
460
 
487
461
  // Step 1: Find txs mined at or before finalized block
488
- const txsToFinalize = this.#findTxsMinedAtOrBefore(blockNumber);
462
+ const txsToFinalize = this.#indices.findTxsMinedAtOrBefore(blockNumber);
489
463
  if (txsToFinalize.length === 0) {
490
464
  return;
491
465
  }
@@ -529,42 +503,32 @@ export class TxPoolV2Impl {
529
503
  }
530
504
 
531
505
  hasTxs(txHashes: TxHash[]): boolean[] {
532
- return txHashes.map(h => this.#metadata.has(h.toString()));
506
+ return txHashes.map(h => this.#indices.has(h.toString()));
533
507
  }
534
508
 
535
509
  getTxStatus(txHash: TxHash): TxState | undefined {
536
- const meta = this.#metadata.get(txHash.toString());
510
+ const meta = this.#indices.getMetadata(txHash.toString());
537
511
  if (!meta) {
538
512
  return undefined;
539
513
  }
540
- return this.#getTxState(meta);
514
+ return this.#indices.getTxState(meta);
541
515
  }
542
516
 
543
517
  getPendingTxHashes(): TxHash[] {
544
- return [...this.#iteratePendingByPriority('desc')].map(hash => TxHash.fromString(hash));
518
+ return [...this.#indices.iteratePendingByPriority('desc')].map(hash => TxHash.fromString(hash));
545
519
  }
546
520
 
547
521
  getPendingTxCount(): number {
548
- let count = 0;
549
- for (const hashes of this.#pendingByPriority.values()) {
550
- count += hashes.size;
551
- }
552
- return count;
522
+ return this.#indices.getPendingTxCount();
553
523
  }
554
524
 
555
525
  getMinedTxHashes(): [TxHash, L2BlockId][] {
556
- const result: [TxHash, L2BlockId][] = [];
557
- for (const [txHash, meta] of this.#metadata) {
558
- if (meta.minedL2BlockId !== undefined) {
559
- result.push([TxHash.fromString(txHash), meta.minedL2BlockId]);
560
- }
561
- }
562
- return result;
526
+ return this.#indices.getMinedTxs().map(([hash, blockId]) => [TxHash.fromString(hash), blockId]);
563
527
  }
564
528
 
565
529
  getMinedTxCount(): number {
566
530
  let count = 0;
567
- for (const meta of this.#metadata.values()) {
531
+ for (const [, meta] of this.#indices.iterateMetadata()) {
568
532
  if (meta.minedL2BlockId !== undefined) {
569
533
  count++;
570
534
  }
@@ -573,11 +537,11 @@ export class TxPoolV2Impl {
573
537
  }
574
538
 
575
539
  isEmpty(): boolean {
576
- return this.#metadata.size === 0;
540
+ return this.#indices.isEmpty();
577
541
  }
578
542
 
579
543
  getTxCount(): number {
580
- return this.#metadata.size;
544
+ return this.#indices.getTxCount();
581
545
  }
582
546
 
583
547
  getArchivedTxByHash(txHash: TxHash): Promise<Tx | undefined> {
@@ -585,18 +549,7 @@ export class TxPoolV2Impl {
585
549
  }
586
550
 
587
551
  getLowestPriorityPending(limit: number): TxHash[] {
588
- if (limit <= 0) {
589
- return [];
590
- }
591
-
592
- const result: TxHash[] = [];
593
- for (const hash of this.#iteratePendingByPriority('asc')) {
594
- result.push(TxHash.fromString(hash));
595
- if (result.length >= limit) {
596
- break;
597
- }
598
- }
599
- return result;
552
+ return this.#indices.getLowestPriorityPending(limit).map(h => TxHash.fromString(h));
600
553
  }
601
554
 
602
555
  // === Configuration ===
@@ -617,155 +570,118 @@ export class TxPoolV2Impl {
617
570
 
618
571
  getPoolReadAccess(): PoolReadAccess {
619
572
  return {
620
- getMetadata: (txHash: string) => this.#metadata.get(txHash),
621
- getTxHashByNullifier: (nullifier: string) => this.#nullifierToTxHash.get(nullifier),
622
- getTxHashesByFeePayer: (feePayer: string) => this.#feePayerToTxHashes.get(feePayer),
623
- getPendingTxCount: () => this.getPendingTxCount(),
573
+ getMetadata: (txHash: string) => this.#indices.getMetadata(txHash),
574
+ getTxHashByNullifier: (nullifier: string) => this.#indices.getTxHashByNullifier(nullifier),
575
+ getTxHashesByFeePayer: (feePayer: string) => this.#indices.getTxHashesByFeePayer(feePayer),
576
+ getPendingTxCount: () => this.#indices.getPendingTxCount(),
624
577
  };
625
578
  }
626
579
 
627
580
  // === Metrics ===
628
581
 
629
582
  countTxs(): { pending: number; protected: number; mined: number } {
630
- let pending = 0;
631
- let protected_ = 0;
632
- let mined = 0;
633
-
634
- for (const meta of this.#metadata.values()) {
635
- const state = this.#getTxState(meta);
636
- if (state === 'pending') {
637
- pending++;
638
- } else if (state === 'protected') {
639
- protected_++;
640
- } else if (state === 'mined') {
641
- mined++;
642
- }
643
- }
644
-
645
- return { pending, protected: protected_, mined };
583
+ return this.#indices.countTxs();
646
584
  }
647
585
 
648
586
  // ============================================================================
649
- // PRIVATE QUERY IMPLEMENTATIONS
587
+ // PRIVATE HELPERS - Transaction Management
650
588
  // ============================================================================
651
589
 
652
590
  /**
653
- * Derives the transaction state from its metadata and protection status.
654
- * A transaction is:
655
- * - 'mined' if it has a minedL2BlockId
656
- * - 'protected' if it's in the protectedTransactions map (but not mined)
657
- * - 'pending' otherwise
591
+ * Adds a new transaction to the pool with the specified state.
592
+ * Emits onTxsAdded callback immediately after DB write.
658
593
  */
659
- #getTxState(meta: TxMetaData): TxState {
660
- if (meta.minedL2BlockId !== undefined) {
661
- return 'mined';
662
- } else if (this.#protectedTransactions.has(meta.txHash)) {
663
- return 'protected';
594
+ async #addTx(
595
+ tx: Tx,
596
+ state: 'pending' | { protected: SlotNumber } | { mined: L2BlockId },
597
+ opts: { source?: string } = {},
598
+ ): Promise<TxMetaData> {
599
+ const txHashStr = tx.getTxHash().toString();
600
+ const meta = await buildTxMetaData(tx);
601
+
602
+ await this.#txsDB.set(txHashStr, tx.toBuffer());
603
+ this.#callbacks.onTxsAdded([tx], opts);
604
+
605
+ if (state === 'pending') {
606
+ this.#indices.addPending(meta);
607
+ } else if ('protected' in state) {
608
+ this.#indices.addProtected(meta, state.protected);
664
609
  } else {
665
- return 'pending';
610
+ meta.minedL2BlockId = state.mined;
611
+ this.#indices.addMined(meta);
666
612
  }
613
+
614
+ const stateStr = typeof state === 'string' ? state : Object.keys(state)[0];
615
+ this.#log.verbose(`Added ${stateStr} tx ${txHashStr}`, {
616
+ eventName: 'tx-added-to-pool',
617
+ state: stateStr,
618
+ });
619
+
620
+ return meta;
667
621
  }
668
622
 
669
623
  /**
670
- * Iterates pending transaction hashes in priority order.
671
- * @param order - 'desc' for highest priority first, 'asc' for lowest priority first
624
+ * Deletes a transaction from both indices and DB.
625
+ * Emits onTxsRemoved callback immediately after DB delete.
672
626
  */
673
- *#iteratePendingByPriority(order: 'asc' | 'desc'): Generator<string> {
674
- // Use shared comparators, negating for descending order
675
- const feeCompareFn =
676
- order === 'desc' ? (a: bigint, b: bigint) => compareFee(b, a) : (a: bigint, b: bigint) => compareFee(a, b);
677
- const hashCompareFn =
678
- order === 'desc' ? (a: string, b: string) => compareTxHash(b, a) : (a: string, b: string) => compareTxHash(a, b);
679
-
680
- const sortedFees = [...this.#pendingByPriority.keys()].sort(feeCompareFn);
681
-
682
- for (const fee of sortedFees) {
683
- const hashesAtFee = this.#pendingByPriority.get(fee)!;
684
- const sortedHashes = [...hashesAtFee].sort(hashCompareFn);
685
- for (const hash of sortedHashes) {
686
- yield hash;
687
- }
627
+ async #deleteTx(txHashStr: string): Promise<void> {
628
+ this.#indices.remove(txHashStr);
629
+ await this.#txsDB.delete(txHashStr);
630
+ this.#callbacks.onTxsRemoved([txHashStr]);
631
+ }
632
+
633
+ /** Deletes a batch of transactions, emitting callbacks individually for each. */
634
+ async #deleteTxsBatch(txHashes: string[]): Promise<void> {
635
+ for (const txHashStr of txHashes) {
636
+ await this.#deleteTx(txHashStr);
688
637
  }
689
638
  }
690
639
 
691
640
  // ============================================================================
692
- // HELPER FUNCTIONS - Pipeline Step Functions
641
+ // PRIVATE HELPERS - Validation & Conflict Resolution
693
642
  // ============================================================================
694
643
 
695
- // --- Finding & Filtering Steps ---
696
-
697
- /** Finds all transactions mined in blocks after the given block number */
698
- #findTxsMinedAfter(blockNumber: number): TxMetaData[] {
699
- const result: TxMetaData[] = [];
700
- for (const meta of this.#metadata.values()) {
701
- if (meta.minedL2BlockId !== undefined && meta.minedL2BlockId.number > blockNumber) {
702
- result.push(meta);
703
- }
644
+ /** Validates a single transaction, returning true if valid */
645
+ async #validateTx(tx: Tx, context?: string): Promise<boolean> {
646
+ const result = await this.#pendingTxValidator.validateTx(tx);
647
+ if (result.result !== 'valid') {
648
+ const contextStr = context ? ` ${context}` : '';
649
+ this.#log.info(`Tx ${tx.getTxHash()}${contextStr} failed validation: ${result.reason?.join(', ')}`);
650
+ return false;
704
651
  }
705
- return result;
652
+ return true;
706
653
  }
707
654
 
708
- /** Finds tx hashes mined at or before the given block number */
709
- #findTxsMinedAtOrBefore(blockNumber: number): string[] {
710
- const result: string[] = [];
711
- for (const [txHashStr, meta] of this.#metadata) {
712
- if (meta.minedL2BlockId !== undefined && meta.minedL2BlockId.number <= blockNumber) {
713
- result.push(txHashStr);
714
- }
715
- }
716
- return result;
717
- }
655
+ /** Loads transactions from DB, returning loaded txs and missing hashes */
656
+ async #loadTxsFromDb(metas: TxMetaData[]): Promise<{ loaded: { tx: Tx; meta: TxMetaData }[]; missing: string[] }> {
657
+ const loaded: { tx: Tx; meta: TxMetaData }[] = [];
658
+ const missing: string[] = [];
718
659
 
719
- /** Finds protected tx hashes from slots earlier than the given slot number */
720
- #findExpiredProtectedTxs(slotNumber: SlotNumber): string[] {
721
- const result: string[] = [];
722
- for (const [txHashStr, protectedSlot] of this.#protectedTransactions) {
723
- if (protectedSlot < slotNumber) {
724
- result.push(txHashStr);
660
+ for (const meta of metas) {
661
+ const buffer = await this.#txsDB.getAsync(meta.txHash);
662
+ if (!buffer) {
663
+ this.#log.warn(`Tx ${meta.txHash} not found in DB`);
664
+ missing.push(meta.txHash);
665
+ continue;
725
666
  }
667
+ loaded.push({ tx: Tx.fromBuffer(buffer), meta });
726
668
  }
727
- return result;
728
- }
729
669
 
730
- /** Filters out transactions that are currently protected */
731
- #filterUnprotected(txs: TxMetaData[]): TxMetaData[] {
732
- return txs.filter(meta => !this.#protectedTransactions.has(meta.txHash));
670
+ return { loaded, missing };
733
671
  }
734
672
 
735
- /** Filters to transactions that have metadata and are not mined */
736
- #filterRestorable(txHashes: string[]): TxMetaData[] {
737
- const result: TxMetaData[] = [];
738
- for (const txHashStr of txHashes) {
739
- const meta = this.#metadata.get(txHashStr);
740
- if (meta && meta.minedL2BlockId === undefined) {
741
- result.push(meta);
742
- }
743
- }
744
- return result;
745
- }
746
-
747
- // --- Validation & Conflict Resolution Steps ---
748
-
749
- /** Validates transactions for pending pool, returning valid and invalid groups */
750
- async #validateForPending(txs: TxMetaData[]): Promise<{ valid: TxMetaData[]; invalid: string[] }> {
673
+ /** Validates a batch of transactions, returning valid and invalid groups */
674
+ async #validateTxBatch(
675
+ txs: { tx: Tx; meta: TxMetaData }[],
676
+ context?: string,
677
+ ): Promise<{ valid: TxMetaData[]; invalid: string[] }> {
751
678
  const valid: TxMetaData[] = [];
752
679
  const invalid: string[] = [];
753
680
 
754
- for (const meta of txs) {
755
- const buffer = await this.#txsDB.getAsync(meta.txHash);
756
- if (!buffer) {
757
- this.#log.warn(`Tx ${meta.txHash} not found in DB during validation`);
758
- invalid.push(meta.txHash);
759
- continue;
760
- }
761
-
762
- const tx = Tx.fromBuffer(buffer);
763
- const result = await this.#pendingTxValidator.validateTx(tx);
764
-
765
- if (result.result === 'valid') {
681
+ for (const { tx, meta } of txs) {
682
+ if (await this.#validateTx(tx, context)) {
766
683
  valid.push(meta);
767
684
  } else {
768
- this.#log.info(`Tx ${meta.txHash} failed validation: ${result.reason?.join(', ')}`);
769
685
  invalid.push(meta.txHash);
770
686
  }
771
687
  }
@@ -773,6 +689,16 @@ export class TxPoolV2Impl {
773
689
  return { valid, invalid };
774
690
  }
775
691
 
692
+ /** Loads transactions from DB and validates them */
693
+ async #loadAndValidateTxs(
694
+ metas: TxMetaData[],
695
+ context?: string,
696
+ ): Promise<{ valid: TxMetaData[]; invalid: string[] }> {
697
+ const { loaded, missing } = await this.#loadTxsFromDb(metas);
698
+ const { valid, invalid } = await this.#validateTxBatch(loaded, context);
699
+ return { valid, invalid: [...missing, ...invalid] };
700
+ }
701
+
776
702
  /**
777
703
  * Resolves nullifier conflicts between incoming txs and existing pending txs.
778
704
  * Modifies the pending indices during iteration to maintain consistent state
@@ -785,8 +711,8 @@ export class TxPoolV2Impl {
785
711
  for (const meta of txs) {
786
712
  const conflict = checkNullifierConflict(
787
713
  meta,
788
- nullifier => this.#nullifierToTxHash.get(nullifier),
789
- txHash => this.#metadata.get(txHash),
714
+ nullifier => this.#indices.getTxHashByNullifier(nullifier),
715
+ txHash => this.#indices.getMetadata(txHash),
790
716
  );
791
717
  if (conflict.shouldIgnore) {
792
718
  // Lower priority than existing - don't add, mark for deletion
@@ -796,13 +722,13 @@ export class TxPoolV2Impl {
796
722
  toEvict.push(...conflict.txHashesToEvict);
797
723
  // Remove evicted from indices immediately for subsequent checks
798
724
  for (const evictHash of conflict.txHashesToEvict) {
799
- const evictMeta = this.#metadata.get(evictHash);
725
+ const evictMeta = this.#indices.getMetadata(evictHash);
800
726
  if (evictMeta) {
801
- this.#removeFromPendingIndices(evictMeta);
727
+ this.#indices.removeFromPendingIndices(evictMeta);
802
728
  }
803
729
  }
804
730
  // Add to pending indices immediately so subsequent txs in the batch see this tx
805
- this.#addToPendingIndices(meta);
731
+ this.#indices.addToPendingIndices(meta);
806
732
  added.push(meta);
807
733
  }
808
734
  }
@@ -810,43 +736,10 @@ export class TxPoolV2Impl {
810
736
  return { added, toEvict };
811
737
  }
812
738
 
813
- // --- State Transition Steps ---
814
-
815
- /** Clears the mined status from transactions, returning them for further processing */
816
- #unmineTxs(txs: TxMetaData[]): TxMetaData[] {
817
- for (const meta of txs) {
818
- meta.minedL2BlockId = undefined;
819
- }
820
- return txs;
821
- }
822
-
823
- /** Removes protection from tx hashes and clears them from the protected map */
824
- #clearProtection(txHashes: string[]): void {
825
- for (const txHashStr of txHashes) {
826
- this.#protectedTransactions.delete(txHashStr);
827
- }
828
- }
829
-
830
- // --- Batch Operation Steps ---
831
-
832
- /** Deletes a batch of transactions permanently */
833
- async #deleteTxsBatch(txHashes: string[]): Promise<void> {
834
- if (txHashes.length === 0) {
835
- return;
836
- }
837
-
838
- await this.#store.transactionAsync(async () => {
839
- for (const txHashStr of txHashes) {
840
- await this.#deleteTx(txHashStr);
841
- }
842
- });
843
-
844
- this.#callbacks.onTxsRemoved(txHashes);
845
- }
846
-
847
- // --- Block & Tx Info Steps ---
739
+ // ============================================================================
740
+ // PRIVATE HELPERS - Block & Hydration
741
+ // ============================================================================
848
742
 
849
- /** Builds a block ID from a block header */
850
743
  async #buildBlockId(block: BlockHeader): Promise<L2BlockId> {
851
744
  return {
852
745
  number: block.globalVariables.blockNumber,
@@ -866,50 +759,6 @@ export class TxPoolV2Impl {
866
759
  };
867
760
  }
868
761
 
869
- /** Marks a batch of transactions as mined */
870
- #markTxsAsMined(metas: TxMetaData[], blockId: L2BlockId): void {
871
- for (const meta of metas) {
872
- this.#markAsMined(meta, blockId);
873
- }
874
- }
875
-
876
- // --- Add Transaction Steps ---
877
-
878
- /** Persists a transaction to the database */
879
- async #persistTx(txHashStr: string, tx: Tx): Promise<void> {
880
- await this.#txsDB.set(txHashStr, tx.toBuffer());
881
- }
882
-
883
- /** Adds a new transaction as protected, returning its metadata */
884
- async #addNewProtectedTx(tx: Tx, slotNumber: SlotNumber): Promise<TxMetaData> {
885
- const txHashStr = tx.getTxHash().toString();
886
- const meta = await buildTxMetaData(tx);
887
-
888
- this.#protectedTransactions.set(txHashStr, slotNumber);
889
- await this.#persistTx(txHashStr, tx);
890
- this.#metadata.set(txHashStr, meta);
891
- // Don't add to pending indices since it's protected
892
-
893
- this.#log.verbose(`Added protected tx ${txHashStr} for slot ${slotNumber}`);
894
- return meta;
895
- }
896
-
897
- /** Adds a new transaction as mined, returning its metadata */
898
- async #addNewMinedTx(tx: Tx, blockId: L2BlockId): Promise<TxMetaData> {
899
- const txHashStr = tx.getTxHash().toString();
900
- const meta = await buildTxMetaData(tx);
901
- meta.minedL2BlockId = blockId;
902
-
903
- await this.#persistTx(txHashStr, tx);
904
- this.#metadata.set(txHashStr, meta);
905
- // Don't add to pending indices since it's mined
906
-
907
- this.#log.verbose(`Added mined tx ${txHashStr} from block ${blockId.number}`);
908
- return meta;
909
- }
910
-
911
- // --- Hydration Steps ---
912
-
913
762
  /** Loads all transactions from the database, returning loaded txs and deserialization errors */
914
763
  async #loadAllTxsFromDb(): Promise<{
915
764
  loaded: { tx: Tx; meta: TxMetaData }[];
@@ -949,50 +798,6 @@ export class TxPoolV2Impl {
949
798
  }
950
799
  }
951
800
 
952
- /** Partitions transactions by mined status */
953
- #partitionByMinedStatus(txs: { tx: Tx; meta: TxMetaData }[]): {
954
- mined: TxMetaData[];
955
- nonMined: { tx: Tx; meta: TxMetaData }[];
956
- } {
957
- const mined: TxMetaData[] = [];
958
- const nonMined: { tx: Tx; meta: TxMetaData }[] = [];
959
-
960
- for (const entry of txs) {
961
- if (entry.meta.minedL2BlockId !== undefined) {
962
- mined.push(entry.meta);
963
- } else {
964
- nonMined.push(entry);
965
- }
966
- }
967
-
968
- return { mined, nonMined };
969
- }
970
-
971
- /** Validates non-mined transactions, returning valid metadata and invalid hashes */
972
- async #validateNonMinedTxs(txs: { tx: Tx; meta: TxMetaData }[]): Promise<{ valid: TxMetaData[]; invalid: string[] }> {
973
- const valid: TxMetaData[] = [];
974
- const invalid: string[] = [];
975
-
976
- for (const { tx, meta } of txs) {
977
- const result = await this.#pendingTxValidator.validateTx(tx);
978
- if (result.result === 'valid') {
979
- valid.push(meta);
980
- } else {
981
- this.#log.info(`Removing invalid tx ${meta.txHash} on startup: ${result.reason?.join(', ')}`);
982
- invalid.push(meta.txHash);
983
- }
984
- }
985
-
986
- return { valid, invalid };
987
- }
988
-
989
- /** Populates metadata index for mined transactions */
990
- #populateMinedIndices(metas: TxMetaData[]): void {
991
- for (const meta of metas) {
992
- this.#metadata.set(meta.txHash, meta);
993
- }
994
- }
995
-
996
801
  /**
997
802
  * Rebuilds the pending pool by processing each tx through pre-add rules.
998
803
  * Starts with an empty pending pool and adds txs one by one, resolving conflicts.
@@ -1016,18 +821,18 @@ export class TxPoolV2Impl {
1016
821
 
1017
822
  // Evict any conflicting txs identified by pre-add rules
1018
823
  for (const evictHashStr of preAddResult.txHashesToEvict) {
1019
- const evictMeta = this.#metadata.get(evictHashStr);
824
+ const evictMeta = this.#indices.getMetadata(evictHashStr);
1020
825
  if (evictMeta) {
1021
- this.#removeFromPendingIndices(evictMeta);
1022
- this.#metadata.delete(evictHashStr);
826
+ this.#indices.removeFromPendingIndices(evictMeta);
827
+ this.#indices.remove(evictHashStr);
1023
828
  rejected.push(evictHashStr);
1024
829
  accepted.delete(evictHashStr);
1025
830
  this.#log.debug(`Evicted tx ${evictHashStr} during rebuild due to conflict with ${meta.txHash}`);
1026
831
  }
1027
832
  }
1028
833
 
1029
- // Add to metadata and pending indices
1030
- this.#addToIndices(meta);
834
+ // Add to indices
835
+ this.#indices.addPending(meta);
1031
836
  accepted.add(meta.txHash);
1032
837
  }
1033
838
 
@@ -1035,207 +840,32 @@ export class TxPoolV2Impl {
1035
840
  return { accepted: [...accepted], rejected };
1036
841
  }
1037
842
 
1038
- // --- Add Pending Tx Steps ---
1039
-
1040
- /** Checks if a tx is a duplicate (already in pool) */
1041
- #isDuplicateTx(txHashStr: string): boolean {
1042
- return this.#metadata.has(txHashStr);
1043
- }
1044
-
1045
- /** Adds a new pending tx to the pool, returning its metadata */
1046
- async #addNewPendingTx(tx: Tx): Promise<TxMetaData> {
1047
- const txHashStr = tx.getTxHash().toString();
1048
- const meta = await buildTxMetaData(tx);
1049
-
1050
- await this.#persistTx(txHashStr, tx);
1051
- this.#addToIndices(meta);
1052
-
1053
- this.#log.verbose(`Added tx ${txHashStr} to pool`, {
1054
- eventName: 'tx-added-to-pool',
1055
- state: this.#getTxState(meta),
1056
- });
1057
-
1058
- return meta;
1059
- }
1060
-
1061
843
  // ============================================================================
1062
- // HELPER FUNCTIONS - Index Management
844
+ // PRIVATE HELPERS - Pool Access Adapters
1063
845
  // ============================================================================
1064
846
 
1065
- #addToIndices(meta: TxMetaData): void {
1066
- this.#metadata.set(meta.txHash, meta);
1067
-
1068
- if (this.#getTxState(meta) === 'pending') {
1069
- this.#addToPendingIndices(meta);
1070
- }
1071
- // Protected and mined txs don't go into pending indices
1072
- }
1073
-
1074
- #addToPendingIndices(meta: TxMetaData): void {
1075
- // Add to nullifier index
1076
- for (const nullifier of meta.nullifiers) {
1077
- this.#nullifierToTxHash.set(nullifier, meta.txHash);
1078
- }
1079
-
1080
- // Add to fee payer index
1081
- let feePayerSet = this.#feePayerToTxHashes.get(meta.feePayer);
1082
- if (!feePayerSet) {
1083
- feePayerSet = new Set();
1084
- this.#feePayerToTxHashes.set(meta.feePayer, feePayerSet);
1085
- }
1086
- feePayerSet.add(meta.txHash);
1087
-
1088
- // Add to priority bucket
1089
- let prioritySet = this.#pendingByPriority.get(meta.priorityFee);
1090
- if (!prioritySet) {
1091
- prioritySet = new Set();
1092
- this.#pendingByPriority.set(meta.priorityFee, prioritySet);
1093
- }
1094
- prioritySet.add(meta.txHash);
1095
- }
1096
-
1097
- #removeFromPendingIndices(meta: TxMetaData): void {
1098
- // Remove from nullifier index
1099
- for (const nullifier of meta.nullifiers) {
1100
- this.#nullifierToTxHash.delete(nullifier);
1101
- }
1102
-
1103
- // Remove from fee payer index
1104
- const feePayerSet = this.#feePayerToTxHashes.get(meta.feePayer);
1105
- if (feePayerSet) {
1106
- feePayerSet.delete(meta.txHash);
1107
- if (feePayerSet.size === 0) {
1108
- this.#feePayerToTxHashes.delete(meta.feePayer);
1109
- }
1110
- }
1111
-
1112
- // Remove from priority map
1113
- const hashSet = this.#pendingByPriority.get(meta.priorityFee);
1114
- if (hashSet) {
1115
- hashSet.delete(meta.txHash);
1116
- if (hashSet.size === 0) {
1117
- this.#pendingByPriority.delete(meta.priorityFee);
1118
- }
1119
- }
1120
- }
1121
-
1122
- #updateProtection(txHashStr: string, slotNumber: SlotNumber): void {
1123
- const currentSlot = this.#protectedTransactions.get(txHashStr);
1124
-
1125
- // Only update if not already protected at an equal or later slot
1126
- if (currentSlot !== undefined && currentSlot >= slotNumber) {
1127
- return;
1128
- }
1129
-
1130
- // Remove from pending indices if transitioning from pending to protected
1131
- if (currentSlot === undefined) {
1132
- const meta = this.#metadata.get(txHashStr);
1133
- if (meta) {
1134
- this.#removeFromPendingIndices(meta);
1135
- }
1136
- }
1137
-
1138
- this.#protectedTransactions.set(txHashStr, slotNumber);
1139
- }
1140
-
1141
- #markAsMined(meta: TxMetaData, blockId: L2BlockId): void {
1142
- meta.minedL2BlockId = blockId;
1143
- // Safe to call unconditionally - removeFromPendingIndices is idempotent
1144
- this.#removeFromPendingIndices(meta);
1145
- }
1146
-
1147
- async #deleteTx(txHashStr: string): Promise<void> {
1148
- const meta = this.#metadata.get(txHashStr);
1149
- if (!meta) {
1150
- return;
1151
- }
1152
-
1153
- // Remove from all indices
1154
- this.#metadata.delete(txHashStr);
1155
- this.#protectedTransactions.delete(txHashStr);
1156
- this.#removeFromPendingIndices(meta);
1157
-
1158
- // Remove from persistence
1159
- await this.#txsDB.delete(txHashStr);
1160
- }
1161
-
1162
- // ============================================================================
1163
- // HELPER FUNCTIONS - Adapters
1164
- // ============================================================================
1165
-
1166
- /** Gets all pending transactions for a given fee payer. */
1167
- #getFeePayerPendingTxs(feePayer: string): TxMetaData[] {
1168
- const txHashes = this.#feePayerToTxHashes.get(feePayer);
1169
- if (!txHashes) {
1170
- return [];
1171
- }
1172
- const result: TxMetaData[] = [];
1173
- for (const txHashStr of txHashes) {
1174
- const meta = this.#metadata.get(txHashStr);
1175
- if (meta && this.#getTxState(meta) === 'pending') {
1176
- result.push(meta);
1177
- }
1178
- }
1179
- return result;
1180
- }
1181
-
1182
- /**
1183
- * Creates a PoolOperations adapter for use with the eviction manager.
1184
- */
1185
847
  #createPoolOperations(): PoolOperations {
1186
848
  return {
1187
- getPendingTxs: (): TxMetaData[] => {
1188
- const result: TxMetaData[] = [];
1189
- for (const hashSet of this.#pendingByPriority.values()) {
1190
- for (const txHashStr of hashSet) {
1191
- const meta = this.#metadata.get(txHashStr);
1192
- if (meta) {
1193
- result.push(meta);
1194
- }
1195
- }
1196
- }
1197
- return result;
1198
- },
1199
- getPendingFeePayers: (): string[] => {
1200
- return Array.from(this.#feePayerToTxHashes.keys());
1201
- },
1202
- getFeePayerPendingTxs: (feePayer: string): TxMetaData[] => {
1203
- return this.#getFeePayerPendingTxs(feePayer);
1204
- },
1205
- getPendingTxCount: (): number => {
1206
- return this.getPendingTxCount();
1207
- },
1208
- getLowestPriorityPending: (limit: number): string[] => {
1209
- return this.getLowestPriorityPending(limit).map(h => h.toString());
1210
- },
1211
- deleteTxs: async (txHashes: string[]): Promise<void> => {
1212
- await this.#store.transactionAsync(async () => {
1213
- for (const txHashStr of txHashes) {
1214
- await this.#deleteTx(txHashStr);
1215
- }
1216
- });
1217
- this.#callbacks.onTxsRemoved(txHashes);
1218
- },
849
+ getPendingTxs: () => this.#indices.getPendingTxs(),
850
+ getPendingFeePayers: () => this.#indices.getPendingFeePayers(),
851
+ getFeePayerPendingTxs: (feePayer: string) => this.#indices.getFeePayerPendingTxs(feePayer),
852
+ getPendingTxCount: () => this.#indices.getPendingTxCount(),
853
+ getLowestPriorityPending: (limit: number) => this.#indices.getLowestPriorityPending(limit),
854
+ deleteTxs: (txHashes: string[]) => this.#deleteTxsBatch(txHashes),
1219
855
  };
1220
856
  }
1221
857
 
1222
- /**
1223
- * Creates a PreAddPoolAccess adapter for use with pre-add eviction rules.
1224
- * All methods work with strings and TxMetaData for efficiency.
1225
- */
1226
858
  #createPreAddPoolAccess(): PreAddPoolAccess {
1227
859
  return {
1228
- getMetadata: (txHashStr: string): TxMetaData | undefined => {
1229
- const meta = this.#metadata.get(txHashStr);
1230
- if (!meta || this.#getTxState(meta) !== 'pending') {
860
+ getMetadata: (txHashStr: string) => {
861
+ const meta = this.#indices.getMetadata(txHashStr);
862
+ if (!meta || this.#indices.getTxState(meta) !== 'pending') {
1231
863
  return undefined;
1232
864
  }
1233
865
  return meta;
1234
866
  },
1235
- getTxHashByNullifier: (nullifier: string): string | undefined => {
1236
- return this.#nullifierToTxHash.get(nullifier);
1237
- },
1238
- getFeePayerBalance: async (feePayer: string): Promise<bigint> => {
867
+ getTxHashByNullifier: (nullifier: string) => this.#indices.getTxHashByNullifier(nullifier),
868
+ getFeePayerBalance: async (feePayer: string) => {
1239
869
  const db = this.#worldStateSynchronizer.getCommitted();
1240
870
  const publicStateSource = new DatabasePublicStateSource(db);
1241
871
  const balance = await publicStateSource.storageRead(
@@ -1244,22 +874,9 @@ export class TxPoolV2Impl {
1244
874
  );
1245
875
  return balance.toBigInt();
1246
876
  },
1247
- getFeePayerPendingTxs: (feePayer: string): TxMetaData[] => {
1248
- return this.#getFeePayerPendingTxs(feePayer);
1249
- },
1250
- getPendingTxCount: (): number => {
1251
- return this.getPendingTxCount();
1252
- },
1253
- getLowestPriorityPendingTx: (): TxMetaData | undefined => {
1254
- // Iterate in ascending order to find the lowest priority
1255
- for (const txHashStr of this.#iteratePendingByPriority('asc')) {
1256
- const meta = this.#metadata.get(txHashStr);
1257
- if (meta) {
1258
- return meta;
1259
- }
1260
- }
1261
- return undefined;
1262
- },
877
+ getFeePayerPendingTxs: (feePayer: string) => this.#indices.getFeePayerPendingTxs(feePayer),
878
+ getPendingTxCount: () => this.#indices.getPendingTxCount(),
879
+ getLowestPriorityPendingTx: () => this.#indices.getLowestPriorityPendingTx(),
1263
880
  };
1264
881
  }
1265
882
  }