@aztec/p2p 0.0.1-commit.ffe5b04ea → 0.0.1-commit.fff30aa

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 (144) hide show
  1. package/README.md +129 -3
  2. package/dest/client/factory.d.ts +2 -2
  3. package/dest/client/factory.d.ts.map +1 -1
  4. package/dest/client/factory.js +23 -11
  5. package/dest/client/p2p_client.d.ts +1 -1
  6. package/dest/client/p2p_client.d.ts.map +1 -1
  7. package/dest/client/p2p_client.js +16 -6
  8. package/dest/config.d.ts +15 -3
  9. package/dest/config.d.ts.map +1 -1
  10. package/dest/config.js +21 -1
  11. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +1 -1
  12. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  13. package/dest/mem_pools/attestation_pool/attestation_pool.js +1 -5
  14. package/dest/mem_pools/instrumentation.d.ts +2 -4
  15. package/dest/mem_pools/instrumentation.d.ts.map +1 -1
  16. package/dest/mem_pools/instrumentation.js +14 -16
  17. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
  18. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
  19. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.js +2 -1
  20. package/dest/mem_pools/tx_pool/priority.d.ts +2 -2
  21. package/dest/mem_pools/tx_pool/priority.d.ts.map +1 -1
  22. package/dest/mem_pools/tx_pool/priority.js +4 -4
  23. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts +1 -1
  24. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts.map +1 -1
  25. package/dest/mem_pools/tx_pool/tx_pool_test_suite.js +3 -1
  26. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
  27. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
  28. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +2 -1
  29. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +3 -3
  30. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  31. package/dest/mem_pools/tx_pool_v2/interfaces.js +0 -1
  32. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +14 -6
  33. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
  34. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +7 -1
  35. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +1 -1
  36. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -1
  37. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +43 -26
  38. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +4 -2
  39. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
  40. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +3 -3
  41. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +1 -2
  42. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  43. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +11 -14
  44. package/dest/msg_validators/tx_validator/contract_instance_validator.d.ts +9 -0
  45. package/dest/msg_validators/tx_validator/contract_instance_validator.d.ts.map +1 -0
  46. package/dest/msg_validators/tx_validator/contract_instance_validator.js +48 -0
  47. package/dest/msg_validators/tx_validator/data_validator.d.ts +1 -1
  48. package/dest/msg_validators/tx_validator/data_validator.d.ts.map +1 -1
  49. package/dest/msg_validators/tx_validator/data_validator.js +35 -2
  50. package/dest/msg_validators/tx_validator/factory.d.ts +23 -4
  51. package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
  52. package/dest/msg_validators/tx_validator/factory.js +36 -10
  53. package/dest/msg_validators/tx_validator/gas_validator.d.ts +13 -4
  54. package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
  55. package/dest/msg_validators/tx_validator/gas_validator.js +39 -9
  56. package/dest/msg_validators/tx_validator/metadata_validator.d.ts +1 -1
  57. package/dest/msg_validators/tx_validator/metadata_validator.d.ts.map +1 -1
  58. package/dest/msg_validators/tx_validator/metadata_validator.js +4 -4
  59. package/dest/msg_validators/tx_validator/phases_validator.d.ts +21 -1
  60. package/dest/msg_validators/tx_validator/phases_validator.d.ts.map +1 -1
  61. package/dest/msg_validators/tx_validator/phases_validator.js +28 -1
  62. package/dest/services/encoding.d.ts +5 -1
  63. package/dest/services/encoding.d.ts.map +1 -1
  64. package/dest/services/encoding.js +7 -1
  65. package/dest/services/libp2p/libp2p_service.d.ts +1 -1
  66. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  67. package/dest/services/libp2p/libp2p_service.js +15 -5
  68. package/dest/services/peer-manager/metrics.d.ts +1 -3
  69. package/dest/services/peer-manager/metrics.d.ts.map +1 -1
  70. package/dest/services/peer-manager/metrics.js +0 -6
  71. package/dest/services/peer-manager/peer_manager.d.ts +1 -1
  72. package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
  73. package/dest/services/peer-manager/peer_manager.js +1 -2
  74. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +1 -1
  75. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
  76. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +37 -14
  77. package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts +11 -17
  78. package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts.map +1 -1
  79. package/dest/services/reqresp/batch-tx-requester/peer_collection.js +15 -49
  80. package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts +5 -4
  81. package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts.map +1 -1
  82. package/dest/services/reqresp/rate-limiter/rate_limiter.js +10 -8
  83. package/dest/services/reqresp/reqresp.d.ts +1 -1
  84. package/dest/services/reqresp/reqresp.d.ts.map +1 -1
  85. package/dest/services/reqresp/reqresp.js +18 -11
  86. package/dest/services/tx_collection/file_store_tx_source.d.ts +5 -4
  87. package/dest/services/tx_collection/file_store_tx_source.d.ts.map +1 -1
  88. package/dest/services/tx_collection/file_store_tx_source.js +39 -29
  89. package/dest/services/tx_collection/tx_source.d.ts +6 -5
  90. package/dest/services/tx_collection/tx_source.d.ts.map +1 -1
  91. package/dest/services/tx_collection/tx_source.js +9 -7
  92. package/dest/test-helpers/make-test-p2p-clients.d.ts +1 -1
  93. package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
  94. package/dest/test-helpers/reqresp-nodes.d.ts +1 -1
  95. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
  96. package/dest/test-helpers/testbench-utils.d.ts +1 -1
  97. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  98. package/dest/test-helpers/testbench-utils.js +2 -1
  99. package/dest/testbench/worker_client_manager.d.ts +1 -1
  100. package/dest/testbench/worker_client_manager.d.ts.map +1 -1
  101. package/dest/testbench/worker_client_manager.js +1 -2
  102. package/dest/util.d.ts +1 -1
  103. package/package.json +14 -14
  104. package/src/client/factory.ts +42 -15
  105. package/src/client/p2p_client.ts +16 -8
  106. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +1 -1
  107. package/src/config.ts +35 -2
  108. package/src/mem_pools/attestation_pool/attestation_pool.ts +4 -5
  109. package/src/mem_pools/instrumentation.ts +13 -17
  110. package/src/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.ts +2 -1
  111. package/src/mem_pools/tx_pool/priority.ts +4 -4
  112. package/src/mem_pools/tx_pool/tx_pool_test_suite.ts +3 -1
  113. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +2 -1
  114. package/src/mem_pools/tx_pool_v2/interfaces.ts +2 -3
  115. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +15 -5
  116. package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +43 -29
  117. package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +13 -4
  118. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +12 -15
  119. package/src/msg_validators/attestation_validator/README.md +49 -0
  120. package/src/msg_validators/proposal_validator/README.md +123 -0
  121. package/src/msg_validators/tx_validator/README.md +5 -1
  122. package/src/msg_validators/tx_validator/contract_instance_validator.ts +56 -0
  123. package/src/msg_validators/tx_validator/data_validator.ts +42 -1
  124. package/src/msg_validators/tx_validator/factory.ts +43 -3
  125. package/src/msg_validators/tx_validator/gas_validator.ts +41 -8
  126. package/src/msg_validators/tx_validator/metadata_validator.ts +4 -12
  127. package/src/msg_validators/tx_validator/phases_validator.ts +31 -1
  128. package/src/services/encoding.ts +9 -1
  129. package/src/services/libp2p/libp2p_service.ts +16 -5
  130. package/src/services/peer-manager/metrics.ts +0 -7
  131. package/src/services/peer-manager/peer_manager.ts +1 -2
  132. package/src/services/reqresp/README.md +229 -0
  133. package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +42 -14
  134. package/src/services/reqresp/batch-tx-requester/peer_collection.ts +24 -63
  135. package/src/services/reqresp/rate-limiter/rate_limiter.ts +13 -9
  136. package/src/services/reqresp/reqresp.ts +20 -14
  137. package/src/services/tx_collection/file_store_tx_source.ts +43 -31
  138. package/src/services/tx_collection/tx_source.ts +8 -7
  139. package/src/test-helpers/make-test-p2p-clients.ts +1 -1
  140. package/src/test-helpers/reqresp-nodes.ts +1 -1
  141. package/src/test-helpers/testbench-utils.ts +1 -0
  142. package/src/testbench/p2p_client_testbench_worker.ts +1 -1
  143. package/src/testbench/worker_client_manager.ts +1 -2
  144. package/src/util.ts +1 -1
@@ -44,8 +44,6 @@ export type TxPoolV2Config = {
44
44
  minTxPoolAgeMs: number;
45
45
  /** Maximum number of evicted tx hashes to remember for metrics tracking */
46
46
  evictedTxCacheSize: number;
47
- /** The probability (0-1) that a transaction is discarded. 0 disables dropping. For testing purposes only. */
48
- dropTransactionsProbability: number;
49
47
  /** Minimum percentage fee increase required to replace an existing tx via RPC (0 = no bump). */
50
48
  priceBumpPercentage: bigint;
51
49
  };
@@ -58,7 +56,6 @@ export const DEFAULT_TX_POOL_V2_CONFIG: TxPoolV2Config = {
58
56
  archivedTxLimit: 0, // 0 = disabled
59
57
  minTxPoolAgeMs: 2_000,
60
58
  evictedTxCacheSize: 10_000,
61
- dropTransactionsProbability: 0,
62
59
  priceBumpPercentage: 10n,
63
60
  };
64
61
 
@@ -72,6 +69,8 @@ export type TxPoolV2Dependencies = {
72
69
  worldStateSynchronizer: WorldStateSynchronizer;
73
70
  /** Factory that creates a validator for re-validating pool transactions using metadata */
74
71
  createTxValidator: () => Promise<TxValidator<TxMetaData>>;
72
+ /** Checks whether a tx's setup-phase calls are on the allow list. Precomputed at receipt time. */
73
+ checkAllowedSetupCalls: (tx: Tx) => Promise<boolean>;
75
74
  };
76
75
 
77
76
  /**
@@ -67,6 +67,9 @@ export type TxMetaData = {
67
67
  /** Timestamp by which the transaction must be included (for expiration checks) */
68
68
  readonly expirationTimestamp: bigint;
69
69
 
70
+ /** Whether the tx's setup-phase calls pass the allow list check. Computed at receipt time. */
71
+ readonly allowedSetupCalls: boolean;
72
+
70
73
  /** Validator-compatible data, providing the same access patterns as Tx.data */
71
74
  readonly data: TxMetaValidationData;
72
75
 
@@ -84,8 +87,12 @@ export type TxState = 'pending' | 'protected' | 'mined' | 'deleted';
84
87
  * Builds TxMetaData from a full Tx object.
85
88
  * Extracts all relevant fields for efficient in-memory storage and querying.
86
89
  * Fr values are captured in closures for zero-cost re-validation.
90
+ *
91
+ * @param allowedSetupCalls - Whether the tx's setup-phase calls pass the allow list.
92
+ * For gossip/RPC txs this is always `true` (already validated by PhasesTxValidator).
93
+ * For req/resp txs this should be computed by the caller using the phases validator.
87
94
  */
88
- export async function buildTxMetaData(tx: Tx): Promise<TxMetaData> {
95
+ export async function buildTxMetaData(tx: Tx, allowedSetupCalls: boolean = true): Promise<TxMetaData> {
89
96
  const txHashObj = tx.getTxHash();
90
97
  const txHash = txHashObj.toString();
91
98
  const txHashBigInt = txHashObj.toBigInt();
@@ -112,6 +119,7 @@ export async function buildTxMetaData(tx: Tx): Promise<TxMetaData> {
112
119
  feeLimit,
113
120
  nullifiers,
114
121
  expirationTimestamp,
122
+ allowedSetupCalls,
115
123
  receivedAt: 0,
116
124
  estimatedSizeBytes,
117
125
  data: {
@@ -158,13 +166,13 @@ export function txHashFromBigInt(value: bigint): string {
158
166
  }
159
167
 
160
168
  /** Minimal fields required for priority comparison. */
161
- export type PriorityComparable = Pick<TxMetaData, 'txHash' | 'txHashBigInt' | 'priorityFee'>;
169
+ type PriorityComparable = Pick<TxMetaData, 'txHashBigInt' | 'priorityFee'>;
162
170
 
163
171
  /**
164
172
  * Compares two priority fees in ascending order.
165
173
  * Returns negative if a < b, positive if a > b, 0 if equal.
166
174
  */
167
- export function compareFee(a: bigint, b: bigint): -1 | 0 | 1 {
175
+ export function compareFee(a: bigint, b: bigint): number {
168
176
  return a < b ? -1 : a > b ? 1 : 0;
169
177
  }
170
178
 
@@ -173,7 +181,7 @@ export function compareFee(a: bigint, b: bigint): -1 | 0 | 1 {
173
181
  * Uses field element comparison for deterministic ordering.
174
182
  * Returns negative if a < b, positive if a > b, 0 if equal.
175
183
  */
176
- export function compareTxHash(a: bigint, b: bigint): -1 | 0 | 1 {
184
+ export function compareTxHash(a: bigint, b: bigint): number {
177
185
  return Fr.cmpAsBigInt(a, b);
178
186
  }
179
187
 
@@ -182,7 +190,7 @@ export function compareTxHash(a: bigint, b: bigint): -1 | 0 | 1 {
182
190
  * Returns negative if a < b, positive if a > b, 0 if equal.
183
191
  * Use with sort() for ascending order, or negate/reverse for descending.
184
192
  */
185
- export function comparePriority(a: PriorityComparable, b: PriorityComparable): -1 | 0 | 1 {
193
+ export function comparePriority(a: PriorityComparable, b: PriorityComparable): number {
186
194
  const feeComparison = compareFee(a.priorityFee, b.priorityFee);
187
195
  if (feeComparison !== 0) {
188
196
  return feeComparison;
@@ -304,6 +312,7 @@ export function stubTxMetaData(
304
312
  nullifiers?: string[];
305
313
  expirationTimestamp?: bigint;
306
314
  anchorBlockHeaderHash?: string;
315
+ allowedSetupCalls?: boolean;
307
316
  } = {},
308
317
  ): TxMetaData {
309
318
  const txHashBigInt = Fr.fromHexString(txHash).toBigInt();
@@ -320,6 +329,7 @@ export function stubTxMetaData(
320
329
  feeLimit: overrides.feeLimit ?? 100n,
321
330
  nullifiers: overrides.nullifiers ?? [`0x${normalizedTxHash.slice(2)}null1`],
322
331
  expirationTimestamp,
332
+ allowedSetupCalls: overrides.allowedSetupCalls ?? true,
323
333
  receivedAt: 0,
324
334
  estimatedSizeBytes: 0,
325
335
  data: stubTxMetaValidationData({ expirationTimestamp }),
@@ -1,8 +1,7 @@
1
- import { insertIntoSortedArray, removeFromSortedArray } from '@aztec/foundation/array';
2
1
  import { SlotNumber } from '@aztec/foundation/branded-types';
3
2
  import type { L2BlockId } from '@aztec/stdlib/block';
4
3
 
5
- import { type PriorityComparable, type TxMetaData, type TxState, comparePriority } from './tx_metadata.js';
4
+ import { type TxMetaData, type TxState, compareFee, compareTxHash, txHashFromBigInt } from './tx_metadata.js';
6
5
 
7
6
  /**
8
7
  * Manages in-memory indices for the transaction pool.
@@ -23,8 +22,8 @@ export class TxPoolIndices {
23
22
  #nullifierToTxHash: Map<string, string> = new Map();
24
23
  /** Fee payer to txHashes index (pending txs only) */
25
24
  #feePayerToTxHashes: Map<string, Set<string>> = new Map();
26
- /** Pending transactions sorted ascending by priority fee, ties broken by txHash */
27
- #pendingByPriority: PriorityComparable[] = [];
25
+ /** Pending txHash bigints grouped by priority fee */
26
+ #pendingByPriority: Map<bigint, Set<bigint>> = new Map();
28
27
  /** Protected transactions: txHash -> slotNumber */
29
28
  #protectedTransactions: Map<string, SlotNumber> = new Map();
30
29
 
@@ -74,14 +73,20 @@ export class TxPoolIndices {
74
73
  * @param order - 'desc' for highest priority first, 'asc' for lowest priority first
75
74
  */
76
75
  *iteratePendingByPriority(order: 'asc' | 'desc', filter?: (hash: string) => boolean): Generator<string> {
77
- const arr = this.#pendingByPriority;
78
- const start = order === 'asc' ? 0 : arr.length - 1;
79
- const step = order === 'asc' ? 1 : -1;
80
- const inBounds = order === 'asc' ? (i: number) => i < arr.length : (i: number) => i >= 0;
81
-
82
- for (let i = start; inBounds(i); i += step) {
83
- if (filter === undefined || filter(arr[i].txHash)) {
84
- yield arr[i].txHash;
76
+ const feeCompareFn = order === 'desc' ? (a: bigint, b: bigint) => compareFee(b, a) : compareFee;
77
+ const hashCompareFn =
78
+ order === 'desc' ? (a: bigint, b: bigint) => compareTxHash(b, a) : (a: bigint, b: bigint) => compareTxHash(a, b);
79
+
80
+ const sortedFees = [...this.#pendingByPriority.keys()].sort(feeCompareFn);
81
+
82
+ for (const fee of sortedFees) {
83
+ const hashesAtFee = this.#pendingByPriority.get(fee)!;
84
+ const sortedHashes = [...hashesAtFee].sort(hashCompareFn);
85
+ for (const hashBigInt of sortedHashes) {
86
+ const hash = txHashFromBigInt(hashBigInt);
87
+ if (filter === undefined || filter(hash)) {
88
+ yield hash;
89
+ }
85
90
  }
86
91
  }
87
92
  }
@@ -222,7 +227,11 @@ export class TxPoolIndices {
222
227
 
223
228
  /** Gets the count of pending transactions */
224
229
  getPendingTxCount(): number {
225
- return this.#pendingByPriority.length;
230
+ let count = 0;
231
+ for (const hashes of this.#pendingByPriority.values()) {
232
+ count += hashes.size;
233
+ }
234
+ return count;
226
235
  }
227
236
 
228
237
  /** Gets the lowest priority pending transaction hashes (up to limit) */
@@ -255,10 +264,12 @@ export class TxPoolIndices {
255
264
  /** Gets all pending transactions */
256
265
  getPendingTxs(): TxMetaData[] {
257
266
  const result: TxMetaData[] = [];
258
- for (const entry of this.#pendingByPriority) {
259
- const meta = this.#metadata.get(entry.txHash);
260
- if (meta) {
261
- result.push(meta);
267
+ for (const hashSet of this.#pendingByPriority.values()) {
268
+ for (const txHashBigInt of hashSet) {
269
+ const meta = this.#metadata.get(txHashFromBigInt(txHashBigInt));
270
+ if (meta) {
271
+ result.push(meta);
272
+ }
262
273
  }
263
274
  }
264
275
  return result;
@@ -397,12 +408,13 @@ export class TxPoolIndices {
397
408
  }
398
409
  feePayerSet.add(meta.txHash);
399
410
 
400
- insertIntoSortedArray(
401
- this.#pendingByPriority,
402
- { txHash: meta.txHash, priorityFee: meta.priorityFee, txHashBigInt: meta.txHashBigInt },
403
- comparePriority,
404
- false,
405
- );
411
+ // Add to priority bucket
412
+ let prioritySet = this.#pendingByPriority.get(meta.priorityFee);
413
+ if (!prioritySet) {
414
+ prioritySet = new Set();
415
+ this.#pendingByPriority.set(meta.priorityFee, prioritySet);
416
+ }
417
+ prioritySet.add(meta.txHashBigInt);
406
418
  }
407
419
 
408
420
  #removeFromPendingIndices(meta: TxMetaData): void {
@@ -420,11 +432,13 @@ export class TxPoolIndices {
420
432
  }
421
433
  }
422
434
 
423
- // Remove from priority array
424
- removeFromSortedArray(
425
- this.#pendingByPriority,
426
- { txHash: meta.txHash, priorityFee: meta.priorityFee, txHashBigInt: meta.txHashBigInt },
427
- comparePriority,
428
- );
435
+ // Remove from priority map
436
+ const hashSet = this.#pendingByPriority.get(meta.priorityFee);
437
+ if (hashSet) {
438
+ hashSet.delete(meta.txHashBigInt);
439
+ if (hashSet.size === 0) {
440
+ this.#pendingByPriority.delete(meta.priorityFee);
441
+ }
442
+ }
429
443
  }
430
444
  }
@@ -11,7 +11,14 @@ import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-clien
11
11
  import EventEmitter from 'node:events';
12
12
 
13
13
  import { PoolInstrumentation, PoolName } from '../instrumentation.js';
14
- import type { AddTxsResult, TxPoolV2, TxPoolV2Config, TxPoolV2Dependencies, TxPoolV2Events } from './interfaces.js';
14
+ import type {
15
+ AddTxsResult,
16
+ PoolReadAccess,
17
+ TxPoolV2,
18
+ TxPoolV2Config,
19
+ TxPoolV2Dependencies,
20
+ TxPoolV2Events,
21
+ } from './interfaces.js';
15
22
  import type { TxState } from './tx_metadata.js';
16
23
  import { TxPoolV2Impl } from './tx_pool_v2_impl.js';
17
24
 
@@ -58,9 +65,6 @@ export class AztecKVTxPoolV2 extends (EventEmitter as new () => TypedEventEmitte
58
65
  const hashes = txHashes.map(h => (typeof h === 'string' ? TxHash.fromString(h) : TxHash.fromBigInt(h)));
59
66
  this.emit('txs-removed', { txHashes: hashes });
60
67
  },
61
- onTxsMined: (txHashes: string[]) => {
62
- this.#metrics?.transactionsRemoved(txHashes);
63
- },
64
68
  };
65
69
 
66
70
  // Create the implementation
@@ -165,6 +169,11 @@ export class AztecKVTxPoolV2 extends (EventEmitter as new () => TypedEventEmitte
165
169
  return this.#queue.put(() => Promise.resolve(this.#impl.getLowestPriorityPending(limit)));
166
170
  }
167
171
 
172
+ /** Returns read-only access to the pool. Used for testing. */
173
+ getPoolReadAccess(): PoolReadAccess {
174
+ return this.#impl.getPoolReadAccess();
175
+ }
176
+
168
177
  // === Configuration ===
169
178
 
170
179
  updateConfig(config: Partial<TxPoolV2Config>): Promise<void> {
@@ -45,7 +45,6 @@ import { TxPoolIndices } from './tx_pool_indices.js';
45
45
  export interface TxPoolV2Callbacks {
46
46
  onTxsAdded: (txs: Tx[], opts: { source?: string }) => void;
47
47
  onTxsRemoved: (txHashes: string[] | bigint[]) => void;
48
- onTxsMined: (txHashes: string[]) => void;
49
48
  }
50
49
 
51
50
  /**
@@ -62,6 +61,7 @@ export class TxPoolV2Impl {
62
61
  #l2BlockSource: L2BlockSource;
63
62
  #worldStateSynchronizer: WorldStateSynchronizer;
64
63
  #createTxValidator: TxPoolV2Dependencies['createTxValidator'];
64
+ #checkAllowedSetupCalls: TxPoolV2Dependencies['checkAllowedSetupCalls'];
65
65
 
66
66
  // === In-Memory Indices ===
67
67
  #indices: TxPoolIndices = new TxPoolIndices();
@@ -93,6 +93,7 @@ export class TxPoolV2Impl {
93
93
  this.#l2BlockSource = deps.l2BlockSource;
94
94
  this.#worldStateSynchronizer = deps.worldStateSynchronizer;
95
95
  this.#createTxValidator = deps.createTxValidator;
96
+ this.#checkAllowedSetupCalls = deps.checkAllowedSetupCalls;
96
97
 
97
98
  this.#config = { ...DEFAULT_TX_POOL_V2_CONFIG, ...config };
98
99
  this.#archive = new TxArchive(archiveStore, this.#config.archivedTxLimit, log);
@@ -338,12 +339,6 @@ export class TxPoolV2Impl {
338
339
  }
339
340
  }
340
341
 
341
- // Randomly drop the transaction for testing purposes (report as accepted so it propagates)
342
- if (this.#config.dropTransactionsProbability > 0 && Math.random() < this.#config.dropTransactionsProbability) {
343
- this.#log.debug(`Dropping tx ${txHashStr} (simulated drop for testing)`);
344
- return { status: 'accepted' };
345
- }
346
-
347
342
  // Add the transaction
348
343
  await this.#addTx(tx, 'pending', opts, precomputedMeta);
349
344
  return { status: 'accepted' };
@@ -368,20 +363,25 @@ export class TxPoolV2Impl {
368
363
  async addProtectedTxs(txs: Tx[], block: BlockHeader, opts: { source?: string }): Promise<void> {
369
364
  const slotNumber = block.globalVariables.slotNumber;
370
365
 
366
+ // Precompute setup-call allow-list flags outside the store transaction
367
+ const allowedFlags = await Promise.all(txs.map(tx => this.#checkAllowedSetupCalls(tx)));
368
+
371
369
  await this.#store.transactionAsync(async () => {
372
- for (const tx of txs) {
370
+ for (let i = 0; i < txs.length; i++) {
371
+ const tx = txs[i];
373
372
  const txHash = tx.getTxHash();
374
373
  const txHashStr = txHash.toString();
375
374
  const isNew = !this.#indices.has(txHashStr);
376
375
  const minedBlockId = await this.#getMinedBlockId(txHash);
377
376
 
378
377
  if (isNew) {
378
+ const meta = await buildTxMetaData(tx, allowedFlags[i]);
379
379
  // New tx - add as mined or protected (callback emitted by #addTx)
380
380
  if (minedBlockId) {
381
- await this.#addTx(tx, { mined: minedBlockId }, opts);
381
+ await this.#addTx(tx, { mined: minedBlockId }, opts, meta);
382
382
  this.#indices.setProtection(txHashStr, slotNumber);
383
383
  } else {
384
- await this.#addTx(tx, { protected: slotNumber }, opts);
384
+ await this.#addTx(tx, { protected: slotNumber }, opts, meta);
385
385
  }
386
386
  } else {
387
387
  // Existing tx - update protection and mined status
@@ -501,10 +501,6 @@ export class TxPoolV2Impl {
501
501
  await this.#evictionManager.evictAfterNewBlock(block.header, nullifiers, feePayers);
502
502
  });
503
503
 
504
- if (found.length > 0) {
505
- this.#callbacks.onTxsMined(found.map(m => m.txHash));
506
- }
507
-
508
504
  this.#log.info(`Marked ${found.length} txs as mined in block ${blockId.number}`);
509
505
  }
510
506
 
@@ -976,7 +972,8 @@ export class TxPoolV2Impl {
976
972
 
977
973
  try {
978
974
  const tx = Tx.fromBuffer(buffer);
979
- const meta = await buildTxMetaData(tx);
975
+ const allowedSetupCalls = await this.#checkAllowedSetupCalls(tx);
976
+ const meta = await buildTxMetaData(tx, allowedSetupCalls);
980
977
  loaded.push({ tx, meta });
981
978
  } catch (err) {
982
979
  this.#log.warn(`Failed to deserialize tx ${txHashStr}, deleting`, { err });
@@ -0,0 +1,49 @@
1
+ # Attestation Validation
2
+
3
+ This module validates `CheckpointAttestation` gossipsub messages. Attestations are signatures from committee members endorsing a checkpoint proposal.
4
+
5
+ **Topic**: `checkpoint_attestation` | **Snappy size limit**: 5 KB
6
+
7
+ ## Stage 1: AttestationValidator (Gossipsub Validation)
8
+
9
+ | # | Rule | Consequence | Severity | File |
10
+ |---|------|-------------|----------|------|
11
+ | 1 | **Slot timeliness**: `currentSlot` or `nextSlot`. Previous slot within 500ms: IGNORE. Older: REJECT. | REJECT or IGNORE | HighToleranceError | `attestation_validator.ts` |
12
+ | 2 | **Attester signature**: `getSender()` must recover valid address | REJECT | LowToleranceError | same |
13
+ | 3 | **Attester in committee**: recovered address in committee for slot | REJECT | HighToleranceError | same |
14
+ | 4 | **Proposer exists**: `getProposerAttesterAddressInSlot` must return defined | REJECT | HighToleranceError | same |
15
+ | 5 | **Proposer signature**: `getProposer()` must recover valid address | REJECT | LowToleranceError | same |
16
+ | 6 | **Proposer matches expected**: recovered proposer = expected for slot | REJECT | HighToleranceError | same |
17
+ | 7 | **NoCommitteeError**: committee unavailable | REJECT | LowToleranceError | same |
18
+
19
+ **Fisherman mode extension** (`FishermanAttestationValidator`): if a checkpoint proposal for the same archive exists in pool, the attestation's `ConsensusPayload` must `.equals()` the stored proposal's payload. On mismatch: REJECT + LowToleranceError.
20
+
21
+ ## Stage 2: Pool Admission
22
+
23
+ | # | Rule | Consequence |
24
+ |---|------|-------------|
25
+ | 8 | Sender recoverable (pool-side) | Silent drop |
26
+ | 9 | Not a duplicate (same slot + proposalId + signer) | IGNORE |
27
+ | 10 | Per-signer cap: `MAX_ATTESTATIONS_PER_SLOT_AND_SIGNER` = 3 | IGNORE |
28
+
29
+ Own attestations added via `addOwnCheckpointAttestations` bypass the per-signer cap.
30
+
31
+ ## Stage 3: Equivocation Detection
32
+
33
+ When a signer's attestation count for a slot reaches exactly 2 (different proposals): `duplicateAttestationCallback` fires -> `WANT_TO_SLASH_EVENT` with `OffenseType.DUPLICATE_ATTESTATION`. Attestation still ACCEPTED and rebroadcast. Callback fires once (not again at count 3+).
34
+
35
+ ## Validation at L1 Checkpoint Submission (Archiver)
36
+
37
+ | Rule | Consequence | File |
38
+ |------|-------------|------|
39
+ | Each attestation must have recoverable signature (or address-only is allowed but does not count toward quorum) | Checkpoint rejected as invalid | `archiver/src/modules/validation.ts` |
40
+ | Attestation at index `i` must correspond to committee member at index `i` | Checkpoint rejected as invalid | same |
41
+ | Valid attestation count >= floor(committee * 2/3) + 1 | Checkpoint rejected as invalid | same |
42
+ | No committee / escape hatch open | Accepted unconditionally | same |
43
+
44
+ Note: `skipValidateCheckpointAttestations` config flag bypasses all archiver attestation validation.
45
+
46
+ ## Gossipsub Topic Scoring
47
+
48
+ P3 enabled with expected messages per slot = `targetCommitteeSize`. Conservative threshold (30% of convergence value). Max P3 penalty = -34 per topic.
49
+
@@ -0,0 +1,123 @@
1
+ # Proposal Validation
2
+
3
+ This module validates `BlockProposal` and `CheckpointProposal` gossipsub messages. Both share the same base `ProposalValidator` (neither subclass overrides `validate()`), with checkpoint-specific logic layered on top in the gossipsub handler.
4
+
5
+ ## BlockProposal
6
+
7
+ **Topic**: `block_proposal` | **Snappy size limit**: 10 MB
8
+
9
+ ### Stage 1: Gossipsub Validation (ProposalValidator)
10
+
11
+ File: `proposal_validator.ts`
12
+
13
+ | # | Rule | Consequence | Severity |
14
+ |---|------|-------------|----------|
15
+ | 1 | **Slot check**: must be `currentSlot` or `nextSlot`. Previous slot within 500ms tolerance: IGNORE. | REJECT | HighToleranceError |
16
+ | 2 | **Signature**: `getSender()` must recover a valid address. If `signedTxs` present, its recovered sender must match. | REJECT | MidToleranceError |
17
+ | 3 | **Txs permitted**: if `disableTransactions`, must have 0 txHashes and 0 embedded txs | REJECT | MidToleranceError |
18
+ | 4 | **Max txs**: `txHashes.length <= maxTxsPerBlock` | REJECT | MidToleranceError |
19
+ | 5 | **Embedded txs in txHashes**: every embedded tx's hash must appear in `txHashes` | REJECT | MidToleranceError |
20
+ | 6 | **Proposer check**: signer must match expected proposer for slot (skipped if committee size = 0) | REJECT | MidToleranceError |
21
+ | 7 | **Tx hash integrity**: each embedded tx's recomputed hash must match declared hash | REJECT | LowToleranceError |
22
+ | 8 | **NoCommitteeError**: epoch cache cannot determine committee | REJECT | LowToleranceError |
23
+
24
+ Deserialization guards: `BlockProposal.fromBuffer` and `SignedTxs.fromBuffer` both enforce `txCount <= MAX_TXS_PER_BLOCK` (65536). Violation -> REJECT + LowToleranceError.
25
+
26
+ ### Stage 2: Mempool (Attestation Pool)
27
+
28
+ | # | Rule | Consequence |
29
+ |---|------|-------------|
30
+ | 9 | **Duplicate**: same archive root already stored | IGNORE (no penalty) |
31
+ | 10 | **Per-position cap**: max 3 proposals per (slot, indexWithinCheckpoint) | REJECT + HighToleranceError |
32
+ | 11 | **Equivocation**: >1 distinct proposal for same (slot, index) | ACCEPT (rebroadcast for detection). At count=2: `duplicateProposalCallback` fires -> slash event (`OffenseType.DUPLICATE_PROPOSAL`, configured via `slashDuplicateProposalPenalty`) |
33
+
34
+ ### Stage 3: Validator-Client Processing (BlockProposalHandler)
35
+
36
+ Only runs on validator nodes. Non-validator nodes use a default handler that triggers tx collection without deep validation.
37
+
38
+ | # | Rule | Failure Reason |
39
+ |---|------|----------------|
40
+ | 12 | Signature re-check | `invalid_proposal` |
41
+ | 13 | ProposalValidator re-run | `invalid_proposal` |
42
+ | 14 | Self-proposal filter | Ignored silently |
43
+ | 15 | Parent block exists (`lastArchive.root` matches known block or genesis) | `parent_block_not_found` |
44
+ | 16 | Parent block slot <= proposal slot | `parent_block_wrong_slot` |
45
+ | 17 | Block number not already in archiver | `block_number_already_exists` |
46
+ | 18 | Checkpoint number consistency (multiple sub-rules for first/non-first blocks) | `invalid_proposal` |
47
+ | 19 | Global variables consistency (non-first block: chainId, version, slot, timestamp, coinbase, feeRecipient, gasFees match parent) | `global_variables_mismatch` |
48
+ | 20 | L1-to-L2 message hash matches `proposal.inHash` | `in_hash_mismatch` |
49
+ | 21 | All txs referenced by `txHashes` obtainable | `txs_not_available` |
50
+ | 22 | **Re-execution**: processed tx count matches `txHashes.length` | `timeout` (ReExTimeoutError) |
51
+ | 23 | **Re-execution**: no failed txs | `failed_txs` (ReExFailedTxsError) -- **SLASHABLE** |
52
+ | 24 | **Re-execution**: archive root and header match proposal | `state_mismatch` (ReExStateMismatchError) -- **SLASHABLE** |
53
+
54
+ **Escape hatch**: during escape hatch periods (`isEscapeHatchOpenAtSlot`), re-execution and slashing are both disabled, and the proposal is rejected locally.
55
+
56
+ **Conditional re-execution**: rules 22-24 only run when at least one condition is true: `fishermanMode` enabled, `slashBroadcastedInvalidBlockPenalty > 0` with `validatorReexecute`, committee membership with `validatorReexecute`, `alwaysReexecuteBlockProposals`, or `blobClient.canUpload()`.
57
+
58
+ **Slashing**: only `state_mismatch` and `failed_txs` trigger on-chain slashing (`OffenseType.BROADCASTED_INVALID_BLOCK_PROPOSAL`, gated by `slashBroadcastedInvalidBlockPenalty > 0`). Unknown errors during re-execution do NOT slash.
59
+
60
+ **Embedded tx validation**: txs in `signedTxs` are validated via `createTxValidatorForBlockProposalReceivedTxs` (well-formedness only) when stored in the tx pool. Invalid embedded txs are rejected from the pool but do not cause the block proposal itself to be rejected at gossipsub level.
61
+
62
+ ### Gossipsub Topic Scoring
63
+
64
+ | Parameter | Effect |
65
+ |-----------|--------|
66
+ | P4 (invalidMessageDeliveries) | weight = -20, decay over 4 slots |
67
+ | P3 (meshMessageDeliveries) | Enabled only when `expectedBlockProposalsPerSlot > 0` (MBPS mode) |
68
+ | P1/P2 | Only active when P3 is enabled |
69
+
70
+ ---
71
+
72
+ ## CheckpointProposal
73
+
74
+ **Topic**: `checkpoint_proposal` | **Snappy size limit**: 10 MB
75
+
76
+ ### Stage 1: Gossipsub Validation (ProposalValidator)
77
+
78
+ Same `ProposalValidator.validate()` as BlockProposal (shared implementation, neither subclass overrides it). See BlockProposal Stage 1 rules 1-8.
79
+
80
+ ### Stage 2: Embedded Block Proposal Validation (if `lastBlock` present)
81
+
82
+ The checkpoint's embedded `lastBlock` is extracted via `getBlockProposal()` and validated through `BlockProposalValidator.validate()` plus block mempool checks.
83
+
84
+ | Rule | Consequence | File |
85
+ |------|-------------|------|
86
+ | Block proposal must pass `BlockProposalValidator.validate()` | If REJECT: entire checkpoint REJECTED | `libp2p_service.ts` |
87
+ | Block proposal must not exceed per-position cap (3) | Checkpoint REJECTED + HighToleranceError | same |
88
+ | Block equivocation detected (>1 proposals for same slot+index) | Checkpoint REJECTED (block itself is ACCEPT for re-broadcast) | same |
89
+
90
+ ### Stage 3: Mempool (Attestation Pool)
91
+
92
+ | Rule | Consequence | File |
93
+ |------|-------------|------|
94
+ | Duplicate (same archive ID) | IGNORE (no penalty). Embedded block still processed if valid. | `attestation_pool.ts` |
95
+ | Per-slot cap: `MAX_CHECKPOINT_PROPOSALS_PER_SLOT` = 5 | REJECT + HighToleranceError. Embedded block still processed. | same |
96
+
97
+ ### Stage 4: Equivocation Detection
98
+
99
+ When >1 checkpoint proposals exist for same slot (count > 1): ACCEPT (re-broadcast). At count == 2 (exactly): `duplicateProposalCallback` fires. Proposal NOT further processed. Callback fires only once per equivocation pair.
100
+
101
+ ### Stage 5: Validator-Client Consensus Validation
102
+
103
+ Determines whether the validator signs an attestation.
104
+
105
+ | Rule | Consequence | File |
106
+ |------|-------------|------|
107
+ | Escape hatch open | No attestation | `validator-client/src/validator.ts` |
108
+ | Signature invalid (re-check) | No attestation | same |
109
+ | Self-proposal | No attestation (ignored) | same |
110
+ | `feeAssetPriceModifier` outside [-100, +100] bps | No attestation | same |
111
+ | Not in committee (unless fisherman mode) | No attestation | same |
112
+ | Checkpoint header mismatch (computed vs proposal) | No attestation | same |
113
+ | Archive root mismatch | No attestation | same |
114
+ | Epoch out hash mismatch | No attestation | same |
115
+ | Last block not found / not matching | No attestation | same |
116
+ | Already attested to this or earlier slot | No attestation (unless `attestToEquivocatedProposals`) | same |
117
+
118
+ **`skipCheckpointProposalValidation` config**: when true, the re-execution checks (header/archive/epoch hash) are all skipped. Signature, fee modifier, committee, escape hatch, and equivocation checks still apply.
119
+
120
+ ### Gossipsub Topic Scoring
121
+
122
+ P3 enabled with expected rate of 1 message per slot. P4 weight = -20, max P3 penalty = -34 per topic.
123
+
@@ -75,10 +75,12 @@ This validator is invoked on **every** transaction potentially entering the pend
75
75
  - Startup hydration — revalidating persisted non-mined txs on node restart
76
76
 
77
77
  Runs:
78
- - DoubleSpend, BlockHeader, GasLimits, Timestamp
78
+ - DoubleSpend, BlockHeader, GasLimits, Timestamp, AllowedSetupCalls
79
79
 
80
80
  Operates on `TxMetaData` (pre-built by the pool) rather than full `Tx` objects.
81
81
 
82
+ The `AllowedSetupCallsMetaValidator` checks a precomputed boolean flag (`TxMetaData.allowedSetupCalls`) rather than re-running the full `PhasesTxValidator`. This flag is computed by `createCheckAllowedSetupCalls` when the tx first enters the pool (via `addProtectedTxs` or startup hydration), so the pool migration validator can reject txs with disallowed setup calls without needing the full `Tx` object or its dependencies.
83
+
82
84
  ## Individual Validators
83
85
 
84
86
  | Validator | What it checks | Benchmarked verification duration |
@@ -92,6 +94,7 @@ Operates on `TxMetaData` (pre-built by the pool) rather than full `Tx` objects.
92
94
  | `GasTxValidator` | Gas limits are within bounds (delegates to `GasLimitsValidator`), max fee per gas meets current block fees, and fee payer has sufficient FeeJuice balance | 1.02 ms |
93
95
  | `GasLimitsValidator` | Gas limits are >= fixed minimums and <= AVM max processable L2 gas. Used standalone in pool migration; also called internally by `GasTxValidator` | 3–10 us |
94
96
  | `PhasesTxValidator` | Public function calls in setup phase are on the allow list | 10.12–13.12 us |
97
+ | `AllowedSetupCallsMetaValidator` | Checks the precomputed `allowedSetupCalls` flag on `TxMetaData`. Used in pool migration instead of the full `PhasesTxValidator` | — |
95
98
  | `BlockHeaderTxValidator` | Transaction's anchor block hash exists in the archive tree | 98.88 us |
96
99
  | `TxProofValidator` | Client proof verifies correctly | ~250ms |
97
100
 
@@ -108,6 +111,7 @@ Operates on `TxMetaData` (pre-built by the pool) rather than full `Tx` objects.
108
111
  | Gas (balance + limits) | Stage 1 | Optional* | — | Yes | — |
109
112
  | GasLimits (standalone) | — | — | — | — | Yes |
110
113
  | Phases | Stage 1 | Yes | — | Yes | — |
114
+ | AllowedSetupCalls | — | — | — | — | Yes |
111
115
  | BlockHeader | Stage 1 | Yes | — | Yes | Yes |
112
116
  | Proof | Stage 2 | Optional** | Yes | — | — |
113
117
 
@@ -0,0 +1,56 @@
1
+ import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
2
+ import { ContractInstancePublishedEvent } from '@aztec/protocol-contracts/instance-registry';
3
+ import { computeContractAddressFromInstance } from '@aztec/stdlib/contract';
4
+ import {
5
+ TX_ERROR_INCORRECT_CONTRACT_ADDRESS,
6
+ TX_ERROR_MALFORMED_CONTRACT_INSTANCE_LOG,
7
+ type Tx,
8
+ type TxValidationResult,
9
+ type TxValidator,
10
+ } from '@aztec/stdlib/tx';
11
+
12
+ /** Validates that contract instance deployment logs contain correct addresses. */
13
+ export class ContractInstanceTxValidator implements TxValidator<Tx> {
14
+ #log: Logger;
15
+
16
+ constructor(bindings?: LoggerBindings) {
17
+ this.#log = createLogger('p2p:tx_validator:contract_instance', bindings);
18
+ }
19
+
20
+ async validateTx(tx: Tx): Promise<TxValidationResult> {
21
+ const reason = await this.#hasCorrectContractInstanceAddresses(tx);
22
+ return reason ? { result: 'invalid', reason: [reason] } : { result: 'valid' };
23
+ }
24
+
25
+ async #hasCorrectContractInstanceAddresses(tx: Tx): Promise<string | undefined> {
26
+ const privateLogs = tx.data.getNonEmptyPrivateLogs();
27
+ for (const log of privateLogs) {
28
+ if (!ContractInstancePublishedEvent.isContractInstancePublishedEvent(log)) {
29
+ continue;
30
+ }
31
+
32
+ let event;
33
+ try {
34
+ event = ContractInstancePublishedEvent.fromLog(log);
35
+ } catch (e) {
36
+ this.#log.warn(`Rejecting tx ${tx.getTxHash()}: failed to parse contract instance event: ${e}`);
37
+ return TX_ERROR_MALFORMED_CONTRACT_INSTANCE_LOG;
38
+ }
39
+
40
+ try {
41
+ const instance = event.toContractInstance();
42
+ const computedAddress = await computeContractAddressFromInstance(instance);
43
+ if (!computedAddress.equals(instance.address)) {
44
+ this.#log.warn(
45
+ `Rejecting tx ${tx.getTxHash()}: contract instance address mismatch. Claimed ${instance.address}, computed ${computedAddress}`,
46
+ );
47
+ return TX_ERROR_INCORRECT_CONTRACT_ADDRESS;
48
+ }
49
+ } catch (e) {
50
+ this.#log.warn(`Rejecting tx ${tx.getTxHash()}: failed to compute contract instance address: ${e}`);
51
+ return TX_ERROR_MALFORMED_CONTRACT_INSTANCE_LOG;
52
+ }
53
+ }
54
+ return undefined;
55
+ }
56
+ }