@aztec/p2p 0.0.1-commit.c7c42ec → 0.0.1-commit.f295ac2

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 (188) hide show
  1. package/dest/client/interface.d.ts +18 -5
  2. package/dest/client/interface.d.ts.map +1 -1
  3. package/dest/client/p2p_client.d.ts +10 -13
  4. package/dest/client/p2p_client.d.ts.map +1 -1
  5. package/dest/client/p2p_client.js +449 -118
  6. package/dest/config.js +2 -2
  7. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +61 -42
  8. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  9. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts +1 -1
  10. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -1
  11. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +237 -263
  12. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts +21 -18
  13. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts.map +1 -1
  14. package/dest/mem_pools/attestation_pool/kv_attestation_pool.js +113 -108
  15. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts +17 -16
  16. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts.map +1 -1
  17. package/dest/mem_pools/attestation_pool/memory_attestation_pool.js +89 -128
  18. package/dest/mem_pools/attestation_pool/mocks.d.ts +9 -6
  19. package/dest/mem_pools/attestation_pool/mocks.d.ts.map +1 -1
  20. package/dest/mem_pools/attestation_pool/mocks.js +16 -12
  21. package/dest/mem_pools/instrumentation.d.ts +1 -1
  22. package/dest/mem_pools/instrumentation.d.ts.map +1 -1
  23. package/dest/mem_pools/instrumentation.js +4 -13
  24. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts +13 -8
  25. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts.map +1 -1
  26. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.js +91 -50
  27. package/dest/mem_pools/tx_pool/eviction/eviction_manager.d.ts +17 -4
  28. package/dest/mem_pools/tx_pool/eviction/eviction_manager.d.ts.map +1 -1
  29. package/dest/mem_pools/tx_pool/eviction/eviction_manager.js +59 -3
  30. package/dest/mem_pools/tx_pool/eviction/eviction_strategy.d.ts +77 -4
  31. package/dest/mem_pools/tx_pool/eviction/eviction_strategy.d.ts.map +1 -1
  32. package/dest/mem_pools/tx_pool/eviction/eviction_strategy.js +47 -0
  33. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts +16 -0
  34. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -0
  35. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.js +115 -0
  36. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.d.ts +2 -2
  37. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.d.ts.map +1 -1
  38. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_reorg_rule.d.ts +2 -2
  39. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_reorg_rule.d.ts.map +1 -1
  40. package/dest/mem_pools/tx_pool/eviction/low_priority_eviction_rule.d.ts +2 -2
  41. package/dest/mem_pools/tx_pool/eviction/low_priority_eviction_rule.d.ts.map +1 -1
  42. package/dest/mem_pools/tx_pool/eviction/nullifier_conflict_pre_add_rule.d.ts +25 -0
  43. package/dest/mem_pools/tx_pool/eviction/nullifier_conflict_pre_add_rule.d.ts.map +1 -0
  44. package/dest/mem_pools/tx_pool/eviction/nullifier_conflict_pre_add_rule.js +57 -0
  45. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +4 -4
  46. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -1
  47. package/dest/msg_validators/attestation_validator/attestation_validator.js +12 -10
  48. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts +5 -5
  49. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts.map +1 -1
  50. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.js +7 -10
  51. package/dest/msg_validators/index.d.ts +2 -2
  52. package/dest/msg_validators/index.d.ts.map +1 -1
  53. package/dest/msg_validators/index.js +1 -1
  54. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts +9 -0
  55. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts.map +1 -0
  56. package/dest/msg_validators/proposal_validator/block_proposal_validator.js +6 -0
  57. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts +9 -0
  58. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts.map +1 -0
  59. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.js +6 -0
  60. package/dest/msg_validators/proposal_validator/index.d.ts +4 -0
  61. package/dest/msg_validators/proposal_validator/index.d.ts.map +1 -0
  62. package/dest/msg_validators/proposal_validator/index.js +3 -0
  63. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +13 -0
  64. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -0
  65. package/dest/msg_validators/{block_proposal_validator/block_proposal_validator.js → proposal_validator/proposal_validator.js} +19 -21
  66. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts +23 -0
  67. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts.map +1 -0
  68. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.js +183 -0
  69. package/dest/msg_validators/tx_validator/data_validator.d.ts +1 -1
  70. package/dest/msg_validators/tx_validator/data_validator.d.ts.map +1 -1
  71. package/dest/msg_validators/tx_validator/fee_payer_balance.d.ts +10 -0
  72. package/dest/msg_validators/tx_validator/fee_payer_balance.d.ts.map +1 -0
  73. package/dest/msg_validators/tx_validator/fee_payer_balance.js +20 -0
  74. package/dest/msg_validators/tx_validator/gas_validator.d.ts +1 -1
  75. package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
  76. package/dest/msg_validators/tx_validator/gas_validator.js +8 -14
  77. package/dest/msg_validators/tx_validator/metadata_validator.d.ts +1 -1
  78. package/dest/msg_validators/tx_validator/metadata_validator.d.ts.map +1 -1
  79. package/dest/msg_validators/tx_validator/timestamp_validator.d.ts +1 -1
  80. package/dest/msg_validators/tx_validator/timestamp_validator.d.ts.map +1 -1
  81. package/dest/services/dummy_service.d.ts +6 -2
  82. package/dest/services/dummy_service.d.ts.map +1 -1
  83. package/dest/services/dummy_service.js +3 -0
  84. package/dest/services/encoding.d.ts +1 -1
  85. package/dest/services/encoding.d.ts.map +1 -1
  86. package/dest/services/encoding.js +4 -2
  87. package/dest/services/libp2p/instrumentation.d.ts +1 -1
  88. package/dest/services/libp2p/instrumentation.d.ts.map +1 -1
  89. package/dest/services/libp2p/instrumentation.js +20 -73
  90. package/dest/services/libp2p/libp2p_service.d.ts +27 -10
  91. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  92. package/dest/services/libp2p/libp2p_service.js +696 -137
  93. package/dest/services/peer-manager/metrics.d.ts +1 -1
  94. package/dest/services/peer-manager/metrics.d.ts.map +1 -1
  95. package/dest/services/peer-manager/metrics.js +6 -26
  96. package/dest/services/peer-manager/peer_manager.d.ts +2 -2
  97. package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
  98. package/dest/services/peer-manager/peer_manager.js +0 -10
  99. package/dest/services/peer-manager/peer_scoring.d.ts +1 -1
  100. package/dest/services/peer-manager/peer_scoring.d.ts.map +1 -1
  101. package/dest/services/peer-manager/peer_scoring.js +2 -5
  102. package/dest/services/reqresp/connection-sampler/connection_sampler.d.ts +1 -1
  103. package/dest/services/reqresp/connection-sampler/connection_sampler.d.ts.map +1 -1
  104. package/dest/services/reqresp/constants.d.ts +12 -0
  105. package/dest/services/reqresp/constants.d.ts.map +1 -0
  106. package/dest/services/reqresp/constants.js +7 -0
  107. package/dest/services/reqresp/interface.d.ts +3 -3
  108. package/dest/services/reqresp/interface.d.ts.map +1 -1
  109. package/dest/services/reqresp/interface.js +2 -2
  110. package/dest/services/reqresp/metrics.d.ts +1 -1
  111. package/dest/services/reqresp/metrics.d.ts.map +1 -1
  112. package/dest/services/reqresp/metrics.js +5 -21
  113. package/dest/services/reqresp/protocols/block_txs/bitvector.d.ts +1 -1
  114. package/dest/services/reqresp/protocols/block_txs/bitvector.d.ts.map +1 -1
  115. package/dest/services/reqresp/protocols/block_txs/bitvector.js +7 -0
  116. package/dest/services/reqresp/protocols/status.d.ts +1 -1
  117. package/dest/services/reqresp/protocols/status.d.ts.map +1 -1
  118. package/dest/services/reqresp/protocols/status.js +4 -1
  119. package/dest/services/reqresp/reqresp.js +402 -24
  120. package/dest/services/service.d.ts +16 -3
  121. package/dest/services/service.d.ts.map +1 -1
  122. package/dest/services/tx_collection/instrumentation.d.ts +1 -1
  123. package/dest/services/tx_collection/instrumentation.d.ts.map +1 -1
  124. package/dest/services/tx_collection/instrumentation.js +4 -14
  125. package/dest/services/tx_provider_instrumentation.d.ts +1 -1
  126. package/dest/services/tx_provider_instrumentation.d.ts.map +1 -1
  127. package/dest/services/tx_provider_instrumentation.js +6 -19
  128. package/dest/testbench/p2p_client_testbench_worker.js +27 -12
  129. package/dest/testbench/worker_client_manager.d.ts +1 -1
  130. package/dest/testbench/worker_client_manager.d.ts.map +1 -1
  131. package/dest/testbench/worker_client_manager.js +6 -1
  132. package/package.json +16 -16
  133. package/src/client/interface.ts +19 -4
  134. package/src/client/p2p_client.ts +78 -128
  135. package/src/config.ts +2 -2
  136. package/src/mem_pools/attestation_pool/attestation_pool.ts +68 -41
  137. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +239 -287
  138. package/src/mem_pools/attestation_pool/kv_attestation_pool.ts +162 -140
  139. package/src/mem_pools/attestation_pool/memory_attestation_pool.ts +141 -164
  140. package/src/mem_pools/attestation_pool/mocks.ts +19 -13
  141. package/src/mem_pools/instrumentation.ts +9 -18
  142. package/src/mem_pools/tx_pool/README.md +28 -13
  143. package/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts +128 -73
  144. package/src/mem_pools/tx_pool/eviction/eviction_manager.ts +64 -4
  145. package/src/mem_pools/tx_pool/eviction/eviction_strategy.ts +117 -3
  146. package/src/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.ts +159 -0
  147. package/src/mem_pools/tx_pool/eviction/nullifier_conflict_pre_add_rule.ts +75 -0
  148. package/src/msg_validators/attestation_validator/attestation_validator.ts +16 -13
  149. package/src/msg_validators/attestation_validator/fisherman_attestation_validator.ts +9 -12
  150. package/src/msg_validators/index.ts +1 -1
  151. package/src/msg_validators/proposal_validator/block_proposal_validator.ts +10 -0
  152. package/src/msg_validators/proposal_validator/checkpoint_proposal_validator.ts +13 -0
  153. package/src/msg_validators/proposal_validator/index.ts +3 -0
  154. package/src/msg_validators/{block_proposal_validator/block_proposal_validator.ts → proposal_validator/proposal_validator.ts} +23 -28
  155. package/src/msg_validators/proposal_validator/proposal_validator_test_suite.ts +206 -0
  156. package/src/msg_validators/tx_validator/data_validator.ts +12 -4
  157. package/src/msg_validators/tx_validator/fee_payer_balance.ts +40 -0
  158. package/src/msg_validators/tx_validator/gas_validator.ts +8 -25
  159. package/src/msg_validators/tx_validator/metadata_validator.ts +12 -4
  160. package/src/msg_validators/tx_validator/timestamp_validator.ts +3 -1
  161. package/src/services/dummy_service.ts +6 -0
  162. package/src/services/encoding.ts +3 -1
  163. package/src/services/libp2p/instrumentation.ts +19 -73
  164. package/src/services/libp2p/libp2p_service.ts +326 -102
  165. package/src/services/peer-manager/metrics.ts +5 -26
  166. package/src/services/peer-manager/peer_manager.ts +1 -2
  167. package/src/services/peer-manager/peer_scoring.ts +1 -5
  168. package/src/services/reqresp/connection-sampler/connection_sampler.ts +3 -1
  169. package/src/services/reqresp/constants.ts +14 -0
  170. package/src/services/reqresp/interface.ts +2 -2
  171. package/src/services/reqresp/metrics.ts +7 -23
  172. package/src/services/reqresp/protocols/block_txs/bitvector.ts +9 -0
  173. package/src/services/reqresp/protocols/status.ts +7 -4
  174. package/src/services/service.ts +19 -4
  175. package/src/services/tx_collection/instrumentation.ts +4 -21
  176. package/src/services/tx_provider_instrumentation.ts +11 -24
  177. package/src/testbench/p2p_client_testbench_worker.ts +35 -12
  178. package/src/testbench/worker_client_manager.ts +6 -1
  179. package/dest/mem_pools/tx_pool/eviction/insufficient_fee_payer_balance_rule.d.ts +0 -15
  180. package/dest/mem_pools/tx_pool/eviction/insufficient_fee_payer_balance_rule.d.ts.map +0 -1
  181. package/dest/mem_pools/tx_pool/eviction/insufficient_fee_payer_balance_rule.js +0 -88
  182. package/dest/msg_validators/block_proposal_validator/block_proposal_validator.d.ts +0 -12
  183. package/dest/msg_validators/block_proposal_validator/block_proposal_validator.d.ts.map +0 -1
  184. package/dest/msg_validators/block_proposal_validator/index.d.ts +0 -2
  185. package/dest/msg_validators/block_proposal_validator/index.d.ts.map +0 -1
  186. package/dest/msg_validators/block_proposal_validator/index.js +0 -1
  187. package/src/mem_pools/tx_pool/eviction/insufficient_fee_payer_balance_rule.ts +0 -108
  188. package/src/msg_validators/block_proposal_validator/index.ts +0 -1
@@ -5,7 +5,7 @@ import { toArray } from '@aztec/foundation/iterable';
5
5
  import { type Logger, createLogger } from '@aztec/foundation/log';
6
6
  import type { TypedEventEmitter } from '@aztec/foundation/types';
7
7
  import type { AztecAsyncKVStore, AztecAsyncMap, AztecAsyncMultiMap } from '@aztec/kv-store';
8
- import type { AztecAddress } from '@aztec/stdlib/aztec-address';
8
+ import { AztecAddress } from '@aztec/stdlib/aztec-address';
9
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';
@@ -18,11 +18,18 @@ import EventEmitter from 'node:events';
18
18
  import { ArchiveCache } from '../../msg_validators/tx_validator/archive_cache.js';
19
19
  import { PoolInstrumentation, PoolName, type PoolStatsCallback } from '../instrumentation.js';
20
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';
21
+ import {
22
+ FeePayerTxInfo,
23
+ type PendingTxInfo,
24
+ type PreAddPoolAccess,
25
+ type TxBlockReference,
26
+ type TxPoolOperations,
27
+ } from './eviction/eviction_strategy.js';
28
+ import { FeePayerBalanceEvictionRule } from './eviction/fee_payer_balance_eviction_rule.js';
23
29
  import { InvalidTxsAfterMiningRule } from './eviction/invalid_txs_after_mining_rule.js';
24
30
  import { InvalidTxsAfterReorgRule } from './eviction/invalid_txs_after_reorg_rule.js';
25
31
  import { LowPriorityEvictionRule } from './eviction/low_priority_eviction_rule.js';
32
+ import { NullifierConflictPreAddRule } from './eviction/nullifier_conflict_pre_add_rule.js';
26
33
  import { getPendingTxPriority } from './priority.js';
27
34
  import type { TxPool, TxPoolEvents, TxPoolOptions } from './tx_pool.js';
28
35
 
@@ -55,7 +62,10 @@ export class AztecKVTxPool
55
62
 
56
63
  #historicalHeaderToTxHash: AztecAsyncMultiMap<string, string>;
57
64
 
58
- #feePayerToTxHash: AztecAsyncMultiMap<string, string>;
65
+ #feePayerToBalanceEntry: AztecAsyncMultiMap<string, Buffer>;
66
+
67
+ /** Index from nullifier to pending tx hash */
68
+ #pendingNullifierToTxHash: AztecAsyncMap<string, string>;
59
69
 
60
70
  /** In-memory set of txs that should not be evicted from the pool. */
61
71
  #nonEvictableTxs: Set<string>;
@@ -101,13 +111,14 @@ export class AztecKVTxPool
101
111
  this.#evictionManager = new EvictionManager(this);
102
112
  this.#evictionManager.registerRule(new InvalidTxsAfterMiningRule());
103
113
  this.#evictionManager.registerRule(new InvalidTxsAfterReorgRule(worldState));
104
- this.#evictionManager.registerRule(new InsufficientFeePayerBalanceRule(worldState));
114
+ this.#evictionManager.registerRule(new FeePayerBalanceEvictionRule(worldState));
105
115
  this.#evictionManager.registerRule(
106
116
  new LowPriorityEvictionRule({
107
117
  //NOTE: 0 effectively disables low priority eviction
108
118
  maxPoolSize: config.maxPendingTxCount ?? 0,
109
119
  }),
110
120
  );
121
+ this.#evictionManager.registerPreAddRule(new NullifierConflictPreAddRule());
111
122
 
112
123
  this.updateConfig(config);
113
124
 
@@ -119,7 +130,8 @@ export class AztecKVTxPool
119
130
 
120
131
  this.#pendingTxHashToHistoricalBlockHeaderHash = store.openMap('txHistoricalBlock');
121
132
  this.#historicalHeaderToTxHash = store.openMultiMap('historicalHeaderToPendingTxHash');
122
- this.#feePayerToTxHash = store.openMultiMap('feePayerToPendingTxHash');
133
+ this.#feePayerToBalanceEntry = store.openMultiMap('feePayerToBalanceEntry');
134
+ this.#pendingNullifierToTxHash = store.openMap('pendingNullifierToTxHash');
123
135
 
124
136
  this.#nonEvictableTxs = new Set<string>();
125
137
 
@@ -171,7 +183,7 @@ export class AztecKVTxPool
171
183
  const key = hash.toString();
172
184
  await this.#minedTxHashToBlock.set(key, blockHeader.globalVariables.blockNumber);
173
185
 
174
- const tx = await this.getPendingTxByHash(hash);
186
+ const tx = await this.getTxByHash(hash);
175
187
  if (tx) {
176
188
  const nullifiers = tx.data.getNonEmptyNullifiers();
177
189
 
@@ -216,8 +228,16 @@ export class AztecKVTxPool
216
228
  const key = hash.toString();
217
229
  await this.#minedTxHashToBlock.delete(key);
218
230
 
231
+ // Clear soft-delete metadata if this tx was previously soft-deleted,
232
+ // so cleanupDeletedMinedTxs won't later hard-delete it while it's pending
233
+ const deletedBlock = await this.#deletedMinedTxHashes.getAsync(key);
234
+ if (deletedBlock !== undefined) {
235
+ await this.#deletedMinedTxHashes.delete(key);
236
+ await this.#blockToDeletedMinedTxHash.deleteValue(deletedBlock, key);
237
+ }
238
+
219
239
  // Rehydrate the tx in the in-memory pending txs mapping
220
- const tx = await this.getPendingTxByHash(hash);
240
+ const tx = await this.getTxByHash(hash);
221
241
  if (tx) {
222
242
  await this.addPendingTxIndicesInDbTx(tx, key);
223
243
  }
@@ -271,6 +291,8 @@ export class AztecKVTxPool
271
291
 
272
292
  /**
273
293
  * Adds a list of transactions to the pool. Duplicates are ignored.
294
+ * Handles nullifier deduplication: if an incoming tx has a nullifier conflict with
295
+ * existing pending txs, it will either replace them (if higher fee) or be rejected.
274
296
  * @param txs - An array of txs to be added to the pool.
275
297
  * @returns count of added transactions
276
298
  */
@@ -280,43 +302,69 @@ export class AztecKVTxPool
280
302
  }
281
303
 
282
304
  const addedTxs: Tx[] = [];
305
+ const uniqueFeePayers: AztecAddress[] = [];
306
+ const replacedTxHashes: TxHash[] = [];
283
307
  const hashesAndStats = txs.map(tx => ({ txHash: tx.getTxHash(), txStats: tx.getStats() }));
284
308
  try {
285
309
  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;
310
+ for (let i = 0; i < txs.length; i++) {
311
+ const tx = txs[i];
312
+ const { txHash, txStats } = hashesAndStats[i];
313
+ const key = txHash.toString();
314
+ if (await this.#txs.hasAsync(key)) {
315
+ this.#log.debug(`Tx ${key} already exists in the pool`);
316
+ continue;
317
+ }
318
+
319
+ const poolAccess = this.getPreAddPoolAccess();
320
+ const { shouldReject, txHashesToEvict } = await this.#evictionManager.runPreAddRules(tx, poolAccess);
321
+ if (shouldReject) {
322
+ continue;
323
+ }
324
+
325
+ for (const txHashToEvict of txHashesToEvict) {
326
+ const txToDelete = await this.getTxByHash(txHashToEvict);
327
+ if (txToDelete) {
328
+ const evictedKey = txHashToEvict.toString();
329
+ await this.deletePendingTxInDbTx(txToDelete, evictedKey);
330
+ replacedTxHashes.push(txHashToEvict);
331
+ this.#log.verbose(`Evicted tx ${evictedKey} due to higher-fee tx ${key}`);
293
332
  }
333
+ }
294
334
 
295
- this.#log.verbose(`Adding tx ${txHash.toString()} to pool`, {
296
- eventName: 'tx-added-to-pool',
297
- ...txStats,
298
- } satisfies TxAddedToPoolStats);
335
+ this.#log.verbose(`Adding tx ${key} to pool`, {
336
+ eventName: 'tx-added-to-pool',
337
+ ...txStats,
338
+ } satisfies TxAddedToPoolStats);
299
339
 
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
- );
340
+ await this.#txs.set(key, tx.toBuffer());
341
+ addedTxs.push(tx);
342
+ insertIntoSortedArray(uniqueFeePayers, tx.data.feePayer, (a, b) => a.toField().cmp(b.toField()), false);
306
343
 
307
- if (!(await this.#minedTxHashToBlock.hasAsync(key))) {
308
- await this.addPendingTxIndicesInDbTx(tx, key);
309
- this.#metrics.recordSize(tx);
310
- }
311
- }),
312
- );
344
+ await this.#pendingTxHashToHistoricalBlockHeaderHash.set(
345
+ key,
346
+ (await tx.data.constants.anchorBlockHeader.hash()).toString(),
347
+ );
348
+
349
+ if (!(await this.#minedTxHashToBlock.hasAsync(key))) {
350
+ await this.addPendingTxIndicesInDbTx(tx, key);
351
+ this.#metrics.recordSize(tx);
352
+ }
353
+ }
313
354
  });
314
355
 
315
- await this.#evictionManager.evictAfterNewTxs(addedTxs.map(({ txHash }) => txHash));
356
+ await this.#evictionManager.evictAfterNewTxs(
357
+ addedTxs.map(({ txHash }) => txHash),
358
+ uniqueFeePayers,
359
+ );
316
360
  } catch (err) {
317
361
  this.#log.warn('Unexpected error when adding txs', { err });
318
362
  }
319
363
 
364
+ if (replacedTxHashes.length > 0) {
365
+ this.#metrics.transactionsRemoved(replacedTxHashes.map(hash => hash.toBigInt()));
366
+ }
367
+
320
368
  if (addedTxs.length > 0) {
321
369
  this.#metrics.transactionsAdded(addedTxs);
322
370
  this.emit('txs-added', { ...opts, txs: addedTxs });
@@ -348,7 +396,7 @@ export class AztecKVTxPool
348
396
  const minedBlockNumber = await this.#minedTxHashToBlock.getAsync(key);
349
397
  const txIsPending = minedBlockNumber === undefined;
350
398
  if (txIsPending) {
351
- await this.deletePendingTx(tx, key);
399
+ await this.deletePendingTxInDbTx(tx, key);
352
400
  } else {
353
401
  await this.deleteMinedTx(key, minedBlockNumber!, opts?.permanently ?? false);
354
402
  const shouldArchiveTx = this.#archivedTxLimit && !opts?.permanently;
@@ -378,10 +426,11 @@ export class AztecKVTxPool
378
426
  await this.#blockToDeletedMinedTxHash.set(minedBlockNumber, txHash);
379
427
  }
380
428
 
381
- private async deletePendingTx(tx: Tx, txHash: `0x${string}`) {
429
+ // Assumes being called within a DB transaction
430
+ private async deletePendingTxInDbTx(tx: Tx, txHash: `0x${string}`) {
382
431
  // We always permanently delete pending transactions
383
432
  this.#log.trace(`Deleting pending tx ${txHash} from pool`);
384
- await this.removePendingTxIndices(tx, txHash);
433
+ await this.removePendingTxIndicesInDbTx(tx, txHash);
385
434
  await this.#txs.delete(txHash);
386
435
  await this.#pendingTxHashToHistoricalBlockHeaderHash.delete(txHash);
387
436
  }
@@ -414,7 +463,7 @@ export class AztecKVTxPool
414
463
  let historicalBlockHash = await this.#pendingTxHashToHistoricalBlockHeaderHash.getAsync(txHash.toString());
415
464
  // Not all tx might have this index created.
416
465
  if (!historicalBlockHash) {
417
- const tx = await this.getPendingTxByHash(txHash);
466
+ const tx = await this.getTxByHash(txHash);
418
467
  if (!tx) {
419
468
  this.#log.warn(`PendingTxInfo:tx ${txHash} not found`);
420
469
  return undefined;
@@ -447,15 +496,21 @@ export class AztecKVTxPool
447
496
  return result;
448
497
  }
449
498
 
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));
499
+ public async getPendingFeePayers(): Promise<AztecAddress[]> {
500
+ const feePayers: AztecAddress[] = [];
501
+ for await (const feePayer of this.#feePayerToBalanceEntry.keysAsync()) {
502
+ const address = AztecAddress.fromString(feePayer);
503
+ insertIntoSortedArray(feePayers, address, (a, b) => a.toField().cmp(b.toField()), false);
456
504
  }
505
+ return feePayers;
506
+ }
457
507
 
458
- return result;
508
+ public async *getFeePayerTxInfos(feePayer: AztecAddress): AsyncIterable<FeePayerTxInfo> {
509
+ for await (const value of this.#feePayerToBalanceEntry.getValuesAsync(feePayer.toString())) {
510
+ const info = FeePayerTxInfo.decode(value);
511
+ info.isEvictable = !this.#nonEvictableTxs.has(info.txHash.toString());
512
+ yield info;
513
+ }
459
514
  }
460
515
 
461
516
  public async getMinedTxHashes(): Promise<[TxHash, BlockNumber][]> {
@@ -565,24 +620,6 @@ export class AztecKVTxPool
565
620
  return new ArchiveCache(db);
566
621
  }
567
622
 
568
- /**
569
- * Checks if a cached transaction exists in the in-memory pending tx pool and returns it.
570
- * Otherwise, it checks the tx pool, updates the pending tx pool, and returns the tx.
571
- * @param txHash - The generated tx hash.
572
- * @returns The transaction, if found, 'undefined' otherwise.
573
- */
574
- private async getPendingTxByHash(txHash: TxHash | string): Promise<Tx | undefined> {
575
- if (typeof txHash === 'string') {
576
- txHash = TxHash.fromString(txHash);
577
- }
578
-
579
- const tx = await this.getTxByHash(txHash);
580
- if (tx) {
581
- return tx;
582
- }
583
- return undefined;
584
- }
585
-
586
623
  /**
587
624
  * Archives a list of txs for future reference. The number of archived txs is limited by the specified archivedTxLimit.
588
625
  * Note: Pending txs should not be archived, only finalized txs
@@ -640,13 +677,13 @@ export class AztecKVTxPool
640
677
  private async addPendingTxIndicesInDbTx(tx: Tx, txHash: string): Promise<void> {
641
678
  await this.#pendingTxPriorityToHash.set(getPendingTxPriority(tx), txHash);
642
679
  await this.#historicalHeaderToTxHash.set((await tx.data.constants.anchorBlockHeader.hash()).toString(), txHash);
643
- await this.#feePayerToTxHash.set(tx.data.feePayer.toString(), txHash);
644
- }
680
+ await this.#feePayerToBalanceEntry.set(tx.data.feePayer.toString(), await FeePayerTxInfo.encode(tx, txHash));
645
681
 
646
- private async addPendingTxIndices(tx: Tx, txHash: string): Promise<void> {
647
- return await this.#store.transactionAsync(async () => {
648
- await this.addPendingTxIndicesInDbTx(tx, txHash);
649
- });
682
+ // Add nullifier entries for conflict detection
683
+ const nullifiers = tx.data.getNonEmptyNullifiers();
684
+ for (const nullifier of nullifiers) {
685
+ await this.#pendingNullifierToTxHash.set(nullifier.toString(), txHash);
686
+ }
650
687
  }
651
688
 
652
689
  // Assumes being called within a DB transaction
@@ -656,13 +693,16 @@ export class AztecKVTxPool
656
693
  (await tx.data.constants.anchorBlockHeader.hash()).toString(),
657
694
  txHash,
658
695
  );
659
- await this.#feePayerToTxHash.deleteValue(tx.data.feePayer.toString(), txHash);
660
- }
696
+ await this.#feePayerToBalanceEntry.deleteValue(
697
+ tx.data.feePayer.toString(),
698
+ await FeePayerTxInfo.encode(tx, txHash),
699
+ );
661
700
 
662
- private async removePendingTxIndices(tx: Tx, txHash: string): Promise<void> {
663
- return await this.#store.transactionAsync(async () => {
664
- await this.removePendingTxIndicesInDbTx(tx, txHash);
665
- });
701
+ // Remove nullifier entries
702
+ const nullifiers = tx.data.getNonEmptyNullifiers();
703
+ for (const nullifier of nullifiers) {
704
+ await this.#pendingNullifierToTxHash.delete(nullifier.toString());
705
+ }
666
706
  }
667
707
 
668
708
  /**
@@ -688,4 +728,19 @@ export class AztecKVTxPool
688
728
 
689
729
  return txsToEvict;
690
730
  }
731
+
732
+ /**
733
+ * Creates a PreAddPoolAccess object for use by pre-add eviction rules.
734
+ * Provides read-only access to pool state during addTxs transaction.
735
+ */
736
+ private getPreAddPoolAccess(): PreAddPoolAccess {
737
+ return {
738
+ getTxHashByNullifier: async nullifier => {
739
+ const hashStr = await this.#pendingNullifierToTxHash.getAsync(nullifier.toString());
740
+ return hashStr ? TxHash.fromString(hashStr) : undefined;
741
+ },
742
+ getPendingTxByHash: this.getTxByHash.bind(this),
743
+ getTxPriority: getPendingTxPriority,
744
+ };
745
+ }
691
746
  }
@@ -1,23 +1,36 @@
1
+ import { findIndexInSortedArray, insertIntoSortedArray } from '@aztec/foundation/array';
1
2
  import { Fr } from '@aztec/foundation/curves/bn254';
2
3
  import { createLogger } from '@aztec/foundation/log';
3
4
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
4
- import { BlockHeader, TxHash } from '@aztec/stdlib/tx';
5
+ import { BlockHeader, Tx, TxHash } from '@aztec/stdlib/tx';
5
6
 
6
7
  import type { TxPoolOptions } from '../tx_pool.js';
7
- import { type EvictionContext, EvictionEvent, type EvictionRule, type TxPoolOperations } from './eviction_strategy.js';
8
+ import {
9
+ type EvictionContext,
10
+ EvictionEvent,
11
+ type EvictionRule,
12
+ type PreAddEvictionResult,
13
+ type PreAddEvictionRule,
14
+ type PreAddPoolAccess,
15
+ type TxPoolOperations,
16
+ } from './eviction_strategy.js';
8
17
 
9
18
  export class EvictionManager {
10
19
  private rules: EvictionRule[] = [];
11
20
 
21
+ /** Pre-add eviction rules (run inside addTxs transaction) */
22
+ private preAddRules: PreAddEvictionRule[] = [];
23
+
12
24
  constructor(
13
25
  private txPool: TxPoolOperations,
14
26
  private log = createLogger('p2p:mempool:tx_pool:eviction_manager'),
15
27
  ) {}
16
28
 
17
- public async evictAfterNewTxs(newTxs: TxHash[]): Promise<void> {
29
+ public async evictAfterNewTxs(newTxs: TxHash[], feePayers: AztecAddress[]): Promise<void> {
18
30
  const ctx: EvictionContext = {
19
31
  event: EvictionEvent.TXS_ADDED,
20
32
  newTxs,
33
+ feePayers,
21
34
  };
22
35
  await this.runEvictionRules(ctx);
23
36
  }
@@ -31,7 +44,7 @@ export class EvictionManager {
31
44
  event: EvictionEvent.BLOCK_MINED,
32
45
  block,
33
46
  newNullifiers,
34
- minedFeePayers,
47
+ feePayers: minedFeePayers,
35
48
  };
36
49
 
37
50
  await this.runEvictionRules(ctx);
@@ -45,14 +58,61 @@ export class EvictionManager {
45
58
  await this.runEvictionRules(ctx);
46
59
  }
47
60
 
61
+ /**
62
+ * Runs pre-add eviction rules to determine if an incoming tx should be added
63
+ * and which existing txs should be evicted.
64
+ * Called from inside the addTxs database transaction for atomicity.
65
+ *
66
+ * @param tx - The incoming transaction
67
+ * @param poolAccess - Read-only access to pool state
68
+ * @returns Combined result from all pre-add rules
69
+ */
70
+ public async runPreAddRules(tx: Tx, poolAccess: PreAddPoolAccess): Promise<PreAddEvictionResult> {
71
+ const allTxHashesToEvict: TxHash[] = [];
72
+ const cmpTxHash = (a: TxHash, b: TxHash) => Fr.cmp(a.hash, b.hash);
73
+
74
+ for (const rule of this.preAddRules) {
75
+ try {
76
+ const result = await rule.check(tx, poolAccess);
77
+
78
+ if (result.shouldReject) {
79
+ return { shouldReject: true, txHashesToEvict: [], reason: result.reason };
80
+ }
81
+
82
+ for (const txHashToEvict of result.txHashesToEvict) {
83
+ // Only add if not already present (dedup)
84
+ if (findIndexInSortedArray(allTxHashesToEvict, txHashToEvict, cmpTxHash) === -1) {
85
+ insertIntoSortedArray(allTxHashesToEvict, txHashToEvict, cmpTxHash);
86
+ }
87
+ }
88
+ } catch (err) {
89
+ this.log.warn(`Pre-add eviction rule ${rule.name} unexpected error: ${String(err)}`, {
90
+ err,
91
+ preAddRule: rule.name,
92
+ });
93
+ // On error, reject the tx to be safe
94
+ return { shouldReject: true, txHashesToEvict: [], reason: `rule error: ${String(err)}` };
95
+ }
96
+ }
97
+
98
+ return { shouldReject: false, txHashesToEvict: allTxHashesToEvict };
99
+ }
100
+
48
101
  public registerRule(rule: EvictionRule) {
49
102
  this.rules.push(rule);
50
103
  }
51
104
 
105
+ public registerPreAddRule(rule: PreAddEvictionRule) {
106
+ this.preAddRules.push(rule);
107
+ }
108
+
52
109
  public updateConfig(config: TxPoolOptions): void {
53
110
  for (const rule of this.rules) {
54
111
  rule.updateConfig(config);
55
112
  }
113
+ for (const rule of this.preAddRules) {
114
+ rule.updateConfig?.(config);
115
+ }
56
116
  }
57
117
 
58
118
  private async runEvictionRules(ctx: EvictionContext): Promise<void> {
@@ -1,7 +1,11 @@
1
+ import { Buffer32 } from '@aztec/foundation/buffer';
1
2
  import { Fr } from '@aztec/foundation/curves/bn254';
3
+ import { ProtocolContractAddress } from '@aztec/protocol-contracts';
2
4
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
3
- import type { BlockHeader, Tx, TxHash } from '@aztec/stdlib/tx';
5
+ import { type BlockHeader, type Tx, TxHash } from '@aztec/stdlib/tx';
4
6
 
7
+ import { getFeePayerBalanceDelta } from '../../../msg_validators/tx_validator/fee_payer_balance.js';
8
+ import { getTxPriorityFee } from '../priority.js';
5
9
  import type { TxPoolOptions } from '../tx_pool.js';
6
10
 
7
11
  export const EvictionEvent = {
@@ -16,6 +20,7 @@ export type EvictionContext =
16
20
  | {
17
21
  event: typeof EvictionEvent.TXS_ADDED;
18
22
  newTxs: TxHash[];
23
+ feePayers: AztecAddress[];
19
24
  }
20
25
  | {
21
26
  event: typeof EvictionEvent.CHAIN_PRUNED;
@@ -25,7 +30,7 @@ export type EvictionContext =
25
30
  event: typeof EvictionEvent.BLOCK_MINED;
26
31
  block: BlockHeader;
27
32
  newNullifiers: Fr[];
28
- minedFeePayers: AztecAddress[];
33
+ feePayers: AztecAddress[];
29
34
  };
30
35
 
31
36
  /**
@@ -63,7 +68,8 @@ export interface TxPoolOperations {
63
68
  getTxByHash(txHash: TxHash): Promise<Tx | undefined>;
64
69
  getPendingTxInfos(): Promise<PendingTxInfo[]>;
65
70
  getPendingTxsReferencingBlocks(blockHashes: Fr[]): Promise<TxBlockReference[]>;
66
- getPendingTxsWithFeePayer(feePayer: AztecAddress[]): Promise<PendingTxInfo[]>;
71
+ getPendingFeePayers(): Promise<AztecAddress[]>;
72
+ getFeePayerTxInfos(feePayer: AztecAddress): AsyncIterable<FeePayerTxInfo>;
67
73
  /** Cheap count of current pending transactions. */
68
74
  getPendingTxCount(): Promise<number>;
69
75
  /**
@@ -91,3 +97,111 @@ export interface EvictionRule {
91
97
  */
92
98
  updateConfig(config: TxPoolOptions): void;
93
99
  }
100
+
101
+ /**
102
+ * Balance-related information about a transaction for a fee payer.
103
+ */
104
+ export class FeePayerTxInfo {
105
+ txHash: TxHash;
106
+ priority: bigint;
107
+ feeLimit: bigint;
108
+ claimAmount: bigint;
109
+ isEvictable: boolean;
110
+
111
+ constructor(fields: {
112
+ txHash: TxHash;
113
+ priority: bigint;
114
+ feeLimit: bigint;
115
+ claimAmount: bigint;
116
+ isEvictable: boolean;
117
+ }) {
118
+ this.txHash = fields.txHash;
119
+ this.priority = fields.priority;
120
+ this.feeLimit = fields.feeLimit;
121
+ this.claimAmount = fields.claimAmount;
122
+ this.isEvictable = fields.isEvictable;
123
+ }
124
+
125
+ static async encode(tx: Tx, txHash: string | TxHash): Promise<Buffer> {
126
+ const { feeLimit, claimAmount } = await getFeePayerBalanceDelta(tx, ProtocolContractAddress.FeeJuice);
127
+ const priority = Buffer32.fromBigInt(getTxPriorityFee(tx)).toBuffer();
128
+ const hashBuffer = (typeof txHash === 'string' ? TxHash.fromString(txHash) : txHash).toBuffer();
129
+ const feeLimitBuffer = Buffer32.fromBigInt(feeLimit).toBuffer();
130
+ const claimAmountBuffer = Buffer32.fromBigInt(claimAmount).toBuffer();
131
+ return Buffer.concat([priority, hashBuffer, feeLimitBuffer, claimAmountBuffer]);
132
+ }
133
+
134
+ static decode(value: Buffer, isEvictable = true): FeePayerTxInfo {
135
+ const priority = Buffer32.fromBuffer(value.subarray(0, Buffer32.SIZE)).toBigInt();
136
+ const hashOffset = Buffer32.SIZE;
137
+ const feeLimitOffset = hashOffset + TxHash.SIZE;
138
+ const claimOffset = feeLimitOffset + Buffer32.SIZE;
139
+
140
+ return new FeePayerTxInfo({
141
+ txHash: TxHash.fromBuffer(value.subarray(hashOffset, feeLimitOffset)),
142
+ priority,
143
+ feeLimit: Buffer32.fromBuffer(value.subarray(feeLimitOffset, claimOffset)).toBigInt(),
144
+ claimAmount: Buffer32.fromBuffer(value.subarray(claimOffset, claimOffset + Buffer32.SIZE)).toBigInt(),
145
+ isEvictable,
146
+ });
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Read-only access to pool state for pre-add eviction checks.
152
+ * Passed to pre-add rules during the addTxs transaction.
153
+ */
154
+ export interface PreAddPoolAccess {
155
+ /**
156
+ * Get the pending tx hash that uses a specific nullifier, if any.
157
+ * Returns undefined if no pending tx uses this nullifier.
158
+ */
159
+ getTxHashByNullifier(nullifier: Fr): Promise<TxHash | undefined>;
160
+
161
+ /**
162
+ * Get a pending transaction by its hash.
163
+ */
164
+ getPendingTxByHash(hash: TxHash): Promise<Tx | undefined>;
165
+
166
+ /**
167
+ * Get the priority string for a transaction (for fee comparison).
168
+ */
169
+ getTxPriority(tx: Tx): string;
170
+ }
171
+
172
+ /**
173
+ * Result of a pre-add eviction check for a single transaction.
174
+ */
175
+ export interface PreAddEvictionResult {
176
+ /** Whether the incoming tx should be rejected */
177
+ readonly shouldReject: boolean;
178
+ /** Sorted array of existing tx hashes that should be evicted if this tx is added */
179
+ readonly txHashesToEvict: TxHash[];
180
+ /** Optional reason for rejection */
181
+ readonly reason?: string;
182
+ }
183
+
184
+ /**
185
+ * Strategy interface for pre-add eviction rules.
186
+ * These run inside the addTxs transaction before a tx is added,
187
+ * deciding whether to evict existing txs or reject the incoming tx.
188
+ */
189
+ export interface PreAddEvictionRule {
190
+ readonly name: string;
191
+
192
+ /**
193
+ * Check if incoming tx should be added and which existing txs to evict.
194
+ * Called inside the addTxs database transaction for atomicity.
195
+ *
196
+ * @param tx - The incoming transaction to check
197
+ * @param poolAccess - Read-only access to current pool state
198
+ * @returns Result indicating whether to reject and what to evict
199
+ */
200
+ check(tx: Tx, poolAccess: PreAddPoolAccess): Promise<PreAddEvictionResult>;
201
+
202
+ /**
203
+ * Updates the configuration for this rule.
204
+ * Rules should ignore config options that don't apply to them.
205
+ */
206
+ updateConfig?(config: TxPoolOptions): void;
207
+ }