@aztec/p2p 0.0.1-commit.381b1a9 → 0.0.1-commit.3a4ae741b

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 (161) 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 +20 -12
  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 -10
  8. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +3 -3
  9. package/dest/config.d.ts +6 -6
  10. package/dest/config.d.ts.map +1 -1
  11. package/dest/config.js +6 -6
  12. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +4 -4
  13. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  14. package/dest/mem_pools/attestation_pool/attestation_pool.js +8 -4
  15. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +6 -6
  16. package/dest/mem_pools/instrumentation.d.ts +4 -2
  17. package/dest/mem_pools/instrumentation.d.ts.map +1 -1
  18. package/dest/mem_pools/instrumentation.js +16 -14
  19. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
  20. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
  21. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.js +2 -1
  22. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
  23. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
  24. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +2 -1
  25. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +9 -5
  26. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  27. package/dest/mem_pools/tx_pool_v2/interfaces.js +1 -0
  28. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +13 -7
  29. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
  30. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +7 -1
  31. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +1 -1
  32. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -1
  33. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +26 -43
  34. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +4 -2
  35. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
  36. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +6 -0
  37. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +2 -1
  38. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  39. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +29 -5
  40. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +1 -1
  41. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -1
  42. package/dest/msg_validators/attestation_validator/attestation_validator.js +5 -4
  43. package/dest/msg_validators/clock_tolerance.d.ts +1 -1
  44. package/dest/msg_validators/clock_tolerance.d.ts.map +1 -1
  45. package/dest/msg_validators/clock_tolerance.js +4 -3
  46. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +1 -1
  47. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
  48. package/dest/msg_validators/proposal_validator/proposal_validator.js +5 -5
  49. package/dest/msg_validators/tx_validator/data_validator.d.ts +1 -1
  50. package/dest/msg_validators/tx_validator/data_validator.d.ts.map +1 -1
  51. package/dest/msg_validators/tx_validator/data_validator.js +35 -2
  52. package/dest/msg_validators/tx_validator/factory.d.ts +9 -1
  53. package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
  54. package/dest/msg_validators/tx_validator/factory.js +15 -2
  55. package/dest/msg_validators/tx_validator/metadata_validator.d.ts +1 -1
  56. package/dest/msg_validators/tx_validator/metadata_validator.d.ts.map +1 -1
  57. package/dest/msg_validators/tx_validator/metadata_validator.js +4 -4
  58. package/dest/msg_validators/tx_validator/phases_validator.d.ts +21 -1
  59. package/dest/msg_validators/tx_validator/phases_validator.d.ts.map +1 -1
  60. package/dest/msg_validators/tx_validator/phases_validator.js +28 -1
  61. package/dest/services/encoding.d.ts +5 -1
  62. package/dest/services/encoding.d.ts.map +1 -1
  63. package/dest/services/encoding.js +7 -1
  64. package/dest/services/libp2p/libp2p_service.d.ts +4 -9
  65. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  66. package/dest/services/libp2p/libp2p_service.js +105 -54
  67. package/dest/services/peer-manager/metrics.d.ts +3 -1
  68. package/dest/services/peer-manager/metrics.d.ts.map +1 -1
  69. package/dest/services/peer-manager/metrics.js +6 -0
  70. package/dest/services/peer-manager/peer_manager.d.ts +1 -1
  71. package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
  72. package/dest/services/peer-manager/peer_manager.js +6 -3
  73. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +11 -8
  74. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
  75. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +82 -101
  76. package/dest/services/reqresp/batch-tx-requester/interface.d.ts +3 -2
  77. package/dest/services/reqresp/batch-tx-requester/interface.d.ts.map +1 -1
  78. package/dest/services/reqresp/batch-tx-requester/missing_txs.d.ts +5 -4
  79. package/dest/services/reqresp/batch-tx-requester/missing_txs.d.ts.map +1 -1
  80. package/dest/services/reqresp/batch-tx-requester/missing_txs.js +13 -7
  81. package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts +19 -11
  82. package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts.map +1 -1
  83. package/dest/services/reqresp/batch-tx-requester/peer_collection.js +52 -15
  84. package/dest/services/reqresp/reqresp.d.ts +1 -1
  85. package/dest/services/reqresp/reqresp.d.ts.map +1 -1
  86. package/dest/services/reqresp/reqresp.js +19 -10
  87. package/dest/services/tx_collection/fast_tx_collection.d.ts +1 -4
  88. package/dest/services/tx_collection/fast_tx_collection.d.ts.map +1 -1
  89. package/dest/services/tx_collection/fast_tx_collection.js +57 -73
  90. package/dest/services/tx_collection/proposal_tx_collector.d.ts +6 -7
  91. package/dest/services/tx_collection/proposal_tx_collector.d.ts.map +1 -1
  92. package/dest/services/tx_collection/proposal_tx_collector.js +4 -4
  93. package/dest/services/tx_collection/request_tracker.d.ts +53 -0
  94. package/dest/services/tx_collection/request_tracker.d.ts.map +1 -0
  95. package/dest/services/tx_collection/request_tracker.js +84 -0
  96. package/dest/services/tx_collection/slow_tx_collection.js +1 -1
  97. package/dest/services/tx_collection/tx_collection.d.ts +3 -6
  98. package/dest/services/tx_collection/tx_collection.d.ts.map +1 -1
  99. package/dest/test-helpers/make-test-p2p-clients.d.ts +1 -1
  100. package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
  101. package/dest/test-helpers/reqresp-nodes.d.ts +1 -1
  102. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
  103. package/dest/test-helpers/testbench-utils.d.ts +1 -1
  104. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  105. package/dest/test-helpers/testbench-utils.js +20 -2
  106. package/dest/testbench/p2p_client_testbench_worker.js +3 -3
  107. package/dest/testbench/worker_client_manager.d.ts +1 -1
  108. package/dest/testbench/worker_client_manager.d.ts.map +1 -1
  109. package/dest/testbench/worker_client_manager.js +2 -1
  110. package/dest/util.d.ts +1 -1
  111. package/package.json +14 -14
  112. package/src/client/factory.ts +36 -18
  113. package/src/client/p2p_client.ts +19 -10
  114. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +4 -6
  115. package/src/config.ts +10 -10
  116. package/src/mem_pools/attestation_pool/attestation_pool.ts +8 -7
  117. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +6 -6
  118. package/src/mem_pools/instrumentation.ts +17 -13
  119. package/src/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.ts +2 -1
  120. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +2 -1
  121. package/src/mem_pools/tx_pool_v2/interfaces.ts +9 -4
  122. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +15 -5
  123. package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +29 -43
  124. package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +16 -1
  125. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +31 -5
  126. package/src/msg_validators/attestation_validator/README.md +49 -0
  127. package/src/msg_validators/attestation_validator/attestation_validator.ts +5 -4
  128. package/src/msg_validators/clock_tolerance.ts +4 -3
  129. package/src/msg_validators/proposal_validator/README.md +123 -0
  130. package/src/msg_validators/proposal_validator/proposal_validator.ts +6 -5
  131. package/src/msg_validators/tx_validator/README.md +5 -1
  132. package/src/msg_validators/tx_validator/data_validator.ts +42 -1
  133. package/src/msg_validators/tx_validator/factory.ts +21 -1
  134. package/src/msg_validators/tx_validator/metadata_validator.ts +12 -4
  135. package/src/msg_validators/tx_validator/phases_validator.ts +31 -1
  136. package/src/services/encoding.ts +9 -1
  137. package/src/services/libp2p/libp2p_service.ts +97 -65
  138. package/src/services/peer-manager/metrics.ts +7 -0
  139. package/src/services/peer-manager/peer_manager.ts +7 -3
  140. package/src/services/reqresp/README.md +229 -0
  141. package/src/services/reqresp/batch-tx-requester/README.md +46 -7
  142. package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +78 -111
  143. package/src/services/reqresp/batch-tx-requester/interface.ts +2 -1
  144. package/src/services/reqresp/batch-tx-requester/missing_txs.ts +13 -6
  145. package/src/services/reqresp/batch-tx-requester/peer_collection.ts +68 -24
  146. package/src/services/reqresp/reqresp.ts +22 -12
  147. package/src/services/tx_collection/fast_tx_collection.ts +57 -83
  148. package/src/services/tx_collection/proposal_tx_collector.ts +8 -13
  149. package/src/services/tx_collection/request_tracker.ts +127 -0
  150. package/src/services/tx_collection/slow_tx_collection.ts +1 -1
  151. package/src/services/tx_collection/tx_collection.ts +3 -5
  152. package/src/test-helpers/make-test-p2p-clients.ts +1 -1
  153. package/src/test-helpers/reqresp-nodes.ts +1 -1
  154. package/src/test-helpers/testbench-utils.ts +28 -3
  155. package/src/testbench/p2p_client_testbench_worker.ts +4 -6
  156. package/src/testbench/worker_client_manager.ts +2 -1
  157. package/src/util.ts +1 -1
  158. package/dest/services/tx_collection/missing_txs_tracker.d.ts +0 -32
  159. package/dest/services/tx_collection/missing_txs_tracker.d.ts.map +0 -1
  160. package/dest/services/tx_collection/missing_txs_tracker.js +0 -27
  161. package/src/services/tx_collection/missing_txs_tracker.ts +0 -52
@@ -1,7 +1,8 @@
1
+ import { insertIntoSortedArray, removeFromSortedArray } from '@aztec/foundation/array';
1
2
  import { SlotNumber } from '@aztec/foundation/branded-types';
2
3
  import type { L2BlockId } from '@aztec/stdlib/block';
3
4
 
4
- import { type TxMetaData, type TxState, compareFee, compareTxHash, txHashFromBigInt } from './tx_metadata.js';
5
+ import { type PriorityComparable, type TxMetaData, type TxState, comparePriority } from './tx_metadata.js';
5
6
 
6
7
  /**
7
8
  * Manages in-memory indices for the transaction pool.
@@ -22,8 +23,8 @@ export class TxPoolIndices {
22
23
  #nullifierToTxHash: Map<string, string> = new Map();
23
24
  /** Fee payer to txHashes index (pending txs only) */
24
25
  #feePayerToTxHashes: Map<string, Set<string>> = new Map();
25
- /** Pending txHash bigints grouped by priority fee */
26
- #pendingByPriority: Map<bigint, Set<bigint>> = new Map();
26
+ /** Pending transactions sorted ascending by priority fee, ties broken by txHash */
27
+ #pendingByPriority: PriorityComparable[] = [];
27
28
  /** Protected transactions: txHash -> slotNumber */
28
29
  #protectedTransactions: Map<string, SlotNumber> = new Map();
29
30
 
@@ -73,20 +74,14 @@ export class TxPoolIndices {
73
74
  * @param order - 'desc' for highest priority first, 'asc' for lowest priority first
74
75
  */
75
76
  *iteratePendingByPriority(order: 'asc' | 'desc', filter?: (hash: string) => boolean): Generator<string> {
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
- }
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;
90
85
  }
91
86
  }
92
87
  }
@@ -227,11 +222,7 @@ export class TxPoolIndices {
227
222
 
228
223
  /** Gets the count of pending transactions */
229
224
  getPendingTxCount(): number {
230
- let count = 0;
231
- for (const hashes of this.#pendingByPriority.values()) {
232
- count += hashes.size;
233
- }
234
- return count;
225
+ return this.#pendingByPriority.length;
235
226
  }
236
227
 
237
228
  /** Gets the lowest priority pending transaction hashes (up to limit) */
@@ -264,12 +255,10 @@ export class TxPoolIndices {
264
255
  /** Gets all pending transactions */
265
256
  getPendingTxs(): TxMetaData[] {
266
257
  const result: TxMetaData[] = [];
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
- }
258
+ for (const entry of this.#pendingByPriority) {
259
+ const meta = this.#metadata.get(entry.txHash);
260
+ if (meta) {
261
+ result.push(meta);
273
262
  }
274
263
  }
275
264
  return result;
@@ -408,13 +397,12 @@ export class TxPoolIndices {
408
397
  }
409
398
  feePayerSet.add(meta.txHash);
410
399
 
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);
400
+ insertIntoSortedArray(
401
+ this.#pendingByPriority,
402
+ { txHash: meta.txHash, priorityFee: meta.priorityFee, txHashBigInt: meta.txHashBigInt },
403
+ comparePriority,
404
+ false,
405
+ );
418
406
  }
419
407
 
420
408
  #removeFromPendingIndices(meta: TxMetaData): void {
@@ -432,13 +420,11 @@ export class TxPoolIndices {
432
420
  }
433
421
  }
434
422
 
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
- }
423
+ // Remove from priority array
424
+ removeFromSortedArray(
425
+ this.#pendingByPriority,
426
+ { txHash: meta.txHash, priorityFee: meta.priorityFee, txHashBigInt: meta.txHashBigInt },
427
+ comparePriority,
428
+ );
443
429
  }
444
430
  }
@@ -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,6 +65,9 @@ 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
  },
68
+ onTxsMined: (txHashes: string[]) => {
69
+ this.#metrics?.transactionsRemoved(txHashes);
70
+ },
61
71
  };
62
72
 
63
73
  // Create the implementation
@@ -162,6 +172,11 @@ export class AztecKVTxPoolV2 extends (EventEmitter as new () => TypedEventEmitte
162
172
  return this.#queue.put(() => Promise.resolve(this.#impl.getLowestPriorityPending(limit)));
163
173
  }
164
174
 
175
+ /** Returns read-only access to the pool. Used for testing. */
176
+ getPoolReadAccess(): PoolReadAccess {
177
+ return this.#impl.getPoolReadAccess();
178
+ }
179
+
165
180
  // === Configuration ===
166
181
 
167
182
  updateConfig(config: Partial<TxPoolV2Config>): Promise<void> {
@@ -45,6 +45,7 @@ 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;
48
49
  }
49
50
 
50
51
  /**
@@ -61,6 +62,7 @@ export class TxPoolV2Impl {
61
62
  #l2BlockSource: L2BlockSource;
62
63
  #worldStateSynchronizer: WorldStateSynchronizer;
63
64
  #createTxValidator: TxPoolV2Dependencies['createTxValidator'];
65
+ #checkAllowedSetupCalls: TxPoolV2Dependencies['checkAllowedSetupCalls'];
64
66
 
65
67
  // === In-Memory Indices ===
66
68
  #indices: TxPoolIndices = new TxPoolIndices();
@@ -92,6 +94,7 @@ export class TxPoolV2Impl {
92
94
  this.#l2BlockSource = deps.l2BlockSource;
93
95
  this.#worldStateSynchronizer = deps.worldStateSynchronizer;
94
96
  this.#createTxValidator = deps.createTxValidator;
97
+ this.#checkAllowedSetupCalls = deps.checkAllowedSetupCalls;
95
98
 
96
99
  this.#config = { ...DEFAULT_TX_POOL_V2_CONFIG, ...config };
97
100
  this.#archive = new TxArchive(archiveStore, this.#config.archivedTxLimit, log);
@@ -337,6 +340,12 @@ export class TxPoolV2Impl {
337
340
  }
338
341
  }
339
342
 
343
+ // Randomly drop the transaction for testing purposes (report as accepted so it propagates)
344
+ if (this.#config.dropTransactionsProbability > 0 && Math.random() < this.#config.dropTransactionsProbability) {
345
+ this.#log.debug(`Dropping tx ${txHashStr} (simulated drop for testing)`);
346
+ return { status: 'accepted' };
347
+ }
348
+
340
349
  // Add the transaction
341
350
  await this.#addTx(tx, 'pending', opts, precomputedMeta);
342
351
  return { status: 'accepted' };
@@ -347,6 +356,7 @@ export class TxPoolV2Impl {
347
356
 
348
357
  // Check if already in pool
349
358
  if (this.#indices.has(txHashStr)) {
359
+ this.#log.verbose(`canAddPendingTx: tx ${txHashStr} already in pool`);
350
360
  return 'ignored';
351
361
  }
352
362
 
@@ -355,26 +365,37 @@ export class TxPoolV2Impl {
355
365
  const poolAccess = this.#createPreAddPoolAccess();
356
366
  const preAddResult = await this.#evictionManager.runPreAddRules(meta, poolAccess);
357
367
 
358
- return preAddResult.shouldIgnore ? 'ignored' : 'accepted';
368
+ if (preAddResult.shouldIgnore) {
369
+ this.#log.verbose(`canAddPendingTx: tx ${txHashStr} ignored by pre-add rule`, {
370
+ reason: preAddResult.reason?.message ?? 'no reason provided',
371
+ });
372
+ return 'ignored';
373
+ }
374
+ return 'accepted';
359
375
  }
360
376
 
361
377
  async addProtectedTxs(txs: Tx[], block: BlockHeader, opts: { source?: string }): Promise<void> {
362
378
  const slotNumber = block.globalVariables.slotNumber;
363
379
 
380
+ // Precompute setup-call allow-list flags outside the store transaction
381
+ const allowedFlags = await Promise.all(txs.map(tx => this.#checkAllowedSetupCalls(tx)));
382
+
364
383
  await this.#store.transactionAsync(async () => {
365
- for (const tx of txs) {
384
+ for (let i = 0; i < txs.length; i++) {
385
+ const tx = txs[i];
366
386
  const txHash = tx.getTxHash();
367
387
  const txHashStr = txHash.toString();
368
388
  const isNew = !this.#indices.has(txHashStr);
369
389
  const minedBlockId = await this.#getMinedBlockId(txHash);
370
390
 
371
391
  if (isNew) {
392
+ const meta = await buildTxMetaData(tx, allowedFlags[i]);
372
393
  // New tx - add as mined or protected (callback emitted by #addTx)
373
394
  if (minedBlockId) {
374
- await this.#addTx(tx, { mined: minedBlockId }, opts);
395
+ await this.#addTx(tx, { mined: minedBlockId }, opts, meta);
375
396
  this.#indices.setProtection(txHashStr, slotNumber);
376
397
  } else {
377
- await this.#addTx(tx, { protected: slotNumber }, opts);
398
+ await this.#addTx(tx, { protected: slotNumber }, opts, meta);
378
399
  }
379
400
  } else {
380
401
  // Existing tx - update protection and mined status
@@ -494,6 +515,10 @@ export class TxPoolV2Impl {
494
515
  await this.#evictionManager.evictAfterNewBlock(block.header, nullifiers, feePayers);
495
516
  });
496
517
 
518
+ if (found.length > 0) {
519
+ this.#callbacks.onTxsMined(found.map(m => m.txHash));
520
+ }
521
+
497
522
  this.#log.info(`Marked ${found.length} txs as mined in block ${blockId.number}`);
498
523
  }
499
524
 
@@ -965,7 +990,8 @@ export class TxPoolV2Impl {
965
990
 
966
991
  try {
967
992
  const tx = Tx.fromBuffer(buffer);
968
- const meta = await buildTxMetaData(tx);
993
+ const allowedSetupCalls = await this.#checkAllowedSetupCalls(tx);
994
+ const meta = await buildTxMetaData(tx, allowedSetupCalls);
969
995
  loaded.push({ tx, meta });
970
996
  } catch (err) {
971
997
  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` = 2 | 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
+
@@ -23,13 +23,14 @@ export class CheckpointAttestationValidator implements P2PValidator<CheckpointAt
23
23
  const slotNumber = message.payload.header.slotNumber;
24
24
 
25
25
  try {
26
- const { currentSlot, nextSlot } = this.epochCache.getCurrentAndNextSlot();
26
+ // Use target slots since proposals target pipeline slots (slot + 1 when pipelining)
27
+ const { targetSlot, nextSlot } = this.epochCache.getTargetAndNextSlot();
27
28
 
28
- if (slotNumber !== currentSlot && slotNumber !== nextSlot) {
29
+ if (slotNumber !== targetSlot && slotNumber !== nextSlot) {
29
30
  // Check if message is for previous slot and within clock tolerance
30
- if (!isWithinClockTolerance(slotNumber, currentSlot, this.epochCache)) {
31
+ if (!isWithinClockTolerance(slotNumber, targetSlot, this.epochCache)) {
31
32
  this.logger.warn(
32
- `Checkpoint attestation slot ${slotNumber} is not current (${currentSlot}) or next (${nextSlot}) slot`,
33
+ `Checkpoint attestation slot ${slotNumber} is not current (${targetSlot}) or next (${nextSlot}) slot`,
33
34
  );
34
35
  return { result: 'reject', severity: PeerErrorSeverity.HighToleranceError };
35
36
  }
@@ -36,10 +36,11 @@ export function isWithinClockTolerance(
36
36
  }
37
37
 
38
38
  // Check how far we are into the current slot (in milliseconds)
39
- const { ts: slotStartTs, nowMs, slot } = epochCache.getEpochAndSlotNow();
39
+ const { ts: slotStartTs, nowMs } = epochCache.getEpochAndSlotNow();
40
+ const targetSlot = epochCache.getTargetSlot();
40
41
 
41
- // Sanity check: ensure the epoch cache's current slot matches the expected current slot
42
- if (slot !== currentSlot) {
42
+ // Sanity check: ensure the epoch cache's target slot matches the expected current slot
43
+ if (targetSlot !== currentSlot) {
43
44
  return false;
44
45
  }
45
46
 
@@ -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 2 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 (2) | 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` = 2 | 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
+
@@ -31,13 +31,14 @@ export class ProposalValidator {
31
31
  /** Validates header-level fields: slot, signature, and proposer. */
32
32
  public async validate(proposal: BlockProposal | CheckpointProposalCore): Promise<ValidationResult> {
33
33
  try {
34
- // Slot check
35
- const { currentSlot, nextSlot } = this.epochCache.getCurrentAndNextSlot();
34
+ // Slot check: use target slots since proposals target pipeline slots (slot + 1 when pipelining)
35
+ const { targetSlot, nextSlot } = this.epochCache.getTargetAndNextSlot();
36
+
36
37
  const slotNumber = proposal.slotNumber;
37
- if (slotNumber !== currentSlot && slotNumber !== nextSlot) {
38
+ if (slotNumber !== targetSlot && slotNumber !== nextSlot) {
38
39
  // Check if message is for previous slot and within clock tolerance
39
- if (!isWithinClockTolerance(slotNumber, currentSlot, this.epochCache)) {
40
- this.logger.warn(`Penalizing peer for invalid slot number ${slotNumber}`, { currentSlot, nextSlot });
40
+ if (!isWithinClockTolerance(slotNumber, targetSlot, this.epochCache)) {
41
+ this.logger.warn(`Penalizing peer for invalid slot number ${slotNumber}`, { targetSlot, nextSlot });
41
42
  return { result: 'reject', severity: PeerErrorSeverity.HighToleranceError };
42
43
  }
43
44
  this.logger.verbose(`Ignoring proposal for previous slot ${slotNumber} within clock tolerance`);
@@ -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
 
@@ -1,5 +1,7 @@
1
1
  import { MAX_FR_CALLDATA_TO_ALL_ENQUEUED_CALLS } from '@aztec/constants';
2
2
  import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
3
+ import { ContractClassPublishedEvent } from '@aztec/protocol-contracts/class-registry';
4
+ import { computeContractClassId } from '@aztec/stdlib/contract';
3
5
  import { computeCalldataHash } from '@aztec/stdlib/hash';
4
6
  import {
5
7
  TX_ERROR_CALLDATA_COUNT_MISMATCH,
@@ -9,7 +11,9 @@ import {
9
11
  TX_ERROR_CONTRACT_CLASS_LOG_LENGTH,
10
12
  TX_ERROR_CONTRACT_CLASS_LOG_SORTING,
11
13
  TX_ERROR_INCORRECT_CALLDATA,
14
+ TX_ERROR_INCORRECT_CONTRACT_CLASS_ID,
12
15
  TX_ERROR_INCORRECT_HASH,
16
+ TX_ERROR_MALFORMED_CONTRACT_CLASS_LOG,
13
17
  Tx,
14
18
  type TxValidationResult,
15
19
  type TxValidator,
@@ -26,7 +30,8 @@ export class DataTxValidator implements TxValidator<Tx> {
26
30
  const reason =
27
31
  (await this.#hasCorrectHash(tx)) ??
28
32
  (await this.#hasCorrectCalldata(tx)) ??
29
- (await this.#hasCorrectContractClassLogs(tx));
33
+ (await this.#hasCorrectContractClassLogs(tx)) ??
34
+ (await this.#hasCorrectContractClassIds(tx));
30
35
  return reason ? { result: 'invalid', reason: [reason] } : { result: 'valid' };
31
36
  }
32
37
 
@@ -127,4 +132,40 @@ export class DataTxValidator implements TxValidator<Tx> {
127
132
 
128
133
  return undefined;
129
134
  }
135
+
136
+ async #hasCorrectContractClassIds(tx: Tx): Promise<string | undefined> {
137
+ const contractClassLogs = tx.getContractClassLogs();
138
+ for (const log of contractClassLogs) {
139
+ if (!ContractClassPublishedEvent.isContractClassPublishedEvent(log)) {
140
+ continue;
141
+ }
142
+
143
+ let event;
144
+ try {
145
+ event = ContractClassPublishedEvent.fromLog(log);
146
+ } catch (e) {
147
+ this.#log.warn(`Rejecting tx ${tx.getTxHash()}: failed to parse contract class event: ${e}`);
148
+ return TX_ERROR_MALFORMED_CONTRACT_CLASS_LOG;
149
+ }
150
+
151
+ try {
152
+ const { publicBytecodeCommitment } = await event.toContractClassPublicWithBytecodeCommitment();
153
+ const computedClassId = await computeContractClassId({
154
+ artifactHash: event.artifactHash,
155
+ privateFunctionsRoot: event.privateFunctionsRoot,
156
+ publicBytecodeCommitment,
157
+ });
158
+ if (!computedClassId.equals(event.contractClassId)) {
159
+ this.#log.warn(
160
+ `Rejecting tx ${tx.getTxHash()}: contract class id mismatch. Claimed ${event.contractClassId}, computed ${computedClassId}`,
161
+ );
162
+ return TX_ERROR_INCORRECT_CONTRACT_CLASS_ID;
163
+ }
164
+ } catch (e) {
165
+ this.#log.warn(`Rejecting tx ${tx.getTxHash()}: failed to compute contract class id: ${e}`);
166
+ return TX_ERROR_MALFORMED_CONTRACT_CLASS_LOG;
167
+ }
168
+ }
169
+ return undefined;
170
+ }
130
171
  }
@@ -59,7 +59,7 @@ import { DoubleSpendTxValidator, type NullifierSource } from './double_spend_val
59
59
  import { GasLimitsValidator, GasTxValidator } from './gas_validator.js';
60
60
  import { MetadataTxValidator } from './metadata_validator.js';
61
61
  import { NullifierCache } from './nullifier_cache.js';
62
- import { PhasesTxValidator } from './phases_validator.js';
62
+ import { AllowedSetupCallsMetaValidator, PhasesTxValidator } from './phases_validator.js';
63
63
  import { SizeTxValidator } from './size_validator.js';
64
64
  import { TimestampTxValidator } from './timestamp_validator.js';
65
65
  import { TxPermittedValidator } from './tx_permitted_validator.js';
@@ -443,5 +443,25 @@ export async function createTxValidatorForTransactionsEnteringPendingTxPool(
443
443
  new TimestampTxValidator<TxMetaData>({ timestamp, blockNumber }, bindings),
444
444
  new DoubleSpendTxValidator<TxMetaData>(nullifierSource, bindings),
445
445
  new BlockHeaderTxValidator<TxMetaData>(archiveSource, bindings),
446
+ new AllowedSetupCallsMetaValidator<TxMetaData>(bindings),
446
447
  );
447
448
  }
449
+
450
+ /**
451
+ * Creates a function that checks whether a tx's setup-phase calls are on the allow list.
452
+ *
453
+ * Uses the `PhasesTxValidator` on the full Tx. The result is stored as a boolean
454
+ * flag in `TxMetaData.allowedSetupCalls` at receipt time, so the pending pool
455
+ * migration validator can check it without needing the full Tx or its dependencies.
456
+ */
457
+ export function createCheckAllowedSetupCalls(
458
+ contractDataSource: ContractDataSource,
459
+ setupAllowList: AllowedElement[],
460
+ getTimestamp: () => UInt64,
461
+ ): (tx: Tx) => Promise<boolean> {
462
+ return async (tx: Tx) => {
463
+ const validator = new PhasesTxValidator(contractDataSource, setupAllowList, getTimestamp());
464
+ const result = await validator.validateTx(tx);
465
+ return result.result === 'valid';
466
+ };
467
+ }
@@ -28,16 +28,24 @@ export class MetadataTxValidator<T extends AnyTx> implements TxValidator<T> {
28
28
  validateTx(tx: T): Promise<TxValidationResult> {
29
29
  const errors = [];
30
30
  if (!this.#hasCorrectL1ChainId(tx)) {
31
- errors.push(TX_ERROR_INCORRECT_L1_CHAIN_ID);
31
+ errors.push(
32
+ `${TX_ERROR_INCORRECT_L1_CHAIN_ID} (tx: ${tx.data.constants.txContext.chainId.toNumber()}, expected: ${this.values.l1ChainId.toNumber()})`,
33
+ );
32
34
  }
33
35
  if (!this.#hasCorrectRollupVersion(tx)) {
34
- errors.push(TX_ERROR_INCORRECT_ROLLUP_VERSION);
36
+ errors.push(
37
+ `${TX_ERROR_INCORRECT_ROLLUP_VERSION} (tx: ${tx.data.constants.txContext.version.toNumber()}, expected: ${this.values.rollupVersion.toNumber()})`,
38
+ );
35
39
  }
36
40
  if (!this.#hasCorrectVkTreeRoot(tx)) {
37
- errors.push(TX_ERROR_INCORRECT_VK_TREE_ROOT);
41
+ errors.push(
42
+ `${TX_ERROR_INCORRECT_VK_TREE_ROOT} (tx: ${tx.data.constants.vkTreeRoot.toString()}, expected: ${this.values.vkTreeRoot.toString()})`,
43
+ );
38
44
  }
39
45
  if (!this.#hasCorrectprotocolContractsHash(tx)) {
40
- errors.push(TX_ERROR_INCORRECT_PROTOCOL_CONTRACTS_HASH);
46
+ errors.push(
47
+ `${TX_ERROR_INCORRECT_PROTOCOL_CONTRACTS_HASH} (tx: ${tx.data.constants.protocolContractsHash.toString()}, expected: ${this.values.protocolContractsHash.toString()})`,
48
+ );
41
49
  }
42
50
  return Promise.resolve(errors.length > 0 ? { result: 'invalid', reason: errors } : { result: 'valid' });
43
51
  }