@aztec/p2p 0.0.1-commit.181e2d196 → 0.0.1-commit.1a421b1a1

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 (120) hide show
  1. package/dest/client/factory.d.ts +2 -2
  2. package/dest/client/factory.d.ts.map +1 -1
  3. package/dest/client/factory.js +8 -2
  4. package/dest/client/p2p_client.d.ts +1 -1
  5. package/dest/client/p2p_client.d.ts.map +1 -1
  6. package/dest/client/p2p_client.js +8 -26
  7. package/dest/config.d.ts +18 -2
  8. package/dest/config.d.ts.map +1 -1
  9. package/dest/config.js +56 -7
  10. package/dest/mem_pools/tx_pool/priority.d.ts +2 -2
  11. package/dest/mem_pools/tx_pool/priority.d.ts.map +1 -1
  12. package/dest/mem_pools/tx_pool/priority.js +4 -3
  13. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts +1 -1
  14. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts.map +1 -1
  15. package/dest/mem_pools/tx_pool/tx_pool_test_suite.js +3 -1
  16. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts +7 -1
  17. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts.map +1 -1
  18. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts +1 -1
  19. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts.map +1 -1
  20. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.js +8 -6
  21. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts +2 -2
  22. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts.map +1 -1
  23. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.js +2 -2
  24. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +3 -1
  25. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  26. package/dest/mem_pools/tx_pool_v2/interfaces.js +2 -1
  27. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +17 -9
  28. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
  29. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +26 -9
  30. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +1 -1
  31. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -1
  32. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +26 -43
  33. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +1 -1
  34. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  35. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +10 -2
  36. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts +5 -4
  37. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts.map +1 -1
  38. package/dest/msg_validators/proposal_validator/block_proposal_validator.js +10 -2
  39. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts +5 -4
  40. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts.map +1 -1
  41. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.js +16 -2
  42. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +12 -9
  43. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
  44. package/dest/msg_validators/proposal_validator/proposal_validator.js +46 -44
  45. package/dest/msg_validators/tx_validator/allowed_public_setup.d.ts +1 -1
  46. package/dest/msg_validators/tx_validator/allowed_public_setup.d.ts.map +1 -1
  47. package/dest/msg_validators/tx_validator/allowed_public_setup.js +21 -32
  48. package/dest/msg_validators/tx_validator/allowed_setup_helpers.d.ts +17 -0
  49. package/dest/msg_validators/tx_validator/allowed_setup_helpers.d.ts.map +1 -0
  50. package/dest/msg_validators/tx_validator/allowed_setup_helpers.js +24 -0
  51. package/dest/msg_validators/tx_validator/factory.d.ts +15 -4
  52. package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
  53. package/dest/msg_validators/tx_validator/factory.js +13 -6
  54. package/dest/msg_validators/tx_validator/fee_payer_balance.d.ts +1 -1
  55. package/dest/msg_validators/tx_validator/fee_payer_balance.d.ts.map +1 -1
  56. package/dest/msg_validators/tx_validator/fee_payer_balance.js +6 -2
  57. package/dest/msg_validators/tx_validator/gas_validator.d.ts +13 -4
  58. package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
  59. package/dest/msg_validators/tx_validator/gas_validator.js +39 -9
  60. package/dest/msg_validators/tx_validator/index.d.ts +2 -1
  61. package/dest/msg_validators/tx_validator/index.d.ts.map +1 -1
  62. package/dest/msg_validators/tx_validator/index.js +1 -0
  63. package/dest/msg_validators/tx_validator/metadata_validator.d.ts +1 -1
  64. package/dest/msg_validators/tx_validator/metadata_validator.d.ts.map +1 -1
  65. package/dest/msg_validators/tx_validator/metadata_validator.js +4 -4
  66. package/dest/msg_validators/tx_validator/phases_validator.d.ts +1 -1
  67. package/dest/msg_validators/tx_validator/phases_validator.d.ts.map +1 -1
  68. package/dest/msg_validators/tx_validator/phases_validator.js +21 -1
  69. package/dest/services/libp2p/libp2p_service.d.ts +1 -1
  70. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  71. package/dest/services/libp2p/libp2p_service.js +41 -16
  72. package/dest/test-helpers/make-test-p2p-clients.d.ts +1 -1
  73. package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
  74. package/dest/test-helpers/reqresp-nodes.d.ts +1 -1
  75. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
  76. package/dest/test-helpers/testbench-utils.d.ts +1 -1
  77. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  78. package/dest/test-helpers/testbench-utils.js +2 -1
  79. package/dest/testbench/p2p_client_testbench_worker.js +2 -1
  80. package/dest/testbench/worker_client_manager.d.ts +3 -1
  81. package/dest/testbench/worker_client_manager.d.ts.map +1 -1
  82. package/dest/testbench/worker_client_manager.js +6 -2
  83. package/dest/util.d.ts +1 -1
  84. package/package.json +14 -14
  85. package/src/client/factory.ts +8 -1
  86. package/src/client/p2p_client.ts +5 -26
  87. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +1 -1
  88. package/src/config.ts +75 -4
  89. package/src/mem_pools/tx_pool/priority.ts +6 -3
  90. package/src/mem_pools/tx_pool/tx_pool_test_suite.ts +3 -1
  91. package/src/mem_pools/tx_pool_v2/README.md +9 -1
  92. package/src/mem_pools/tx_pool_v2/eviction/interfaces.ts +11 -1
  93. package/src/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.ts +15 -6
  94. package/src/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.ts +2 -1
  95. package/src/mem_pools/tx_pool_v2/interfaces.ts +3 -0
  96. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +41 -11
  97. package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +29 -43
  98. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +11 -2
  99. package/src/msg_validators/proposal_validator/block_proposal_validator.ts +13 -3
  100. package/src/msg_validators/proposal_validator/checkpoint_proposal_validator.ts +19 -6
  101. package/src/msg_validators/proposal_validator/proposal_validator.ts +57 -48
  102. package/src/msg_validators/tx_validator/allowed_public_setup.ts +16 -35
  103. package/src/msg_validators/tx_validator/allowed_setup_helpers.ts +31 -0
  104. package/src/msg_validators/tx_validator/factory.ts +15 -2
  105. package/src/msg_validators/tx_validator/fee_payer_balance.ts +6 -2
  106. package/src/msg_validators/tx_validator/gas_validator.ts +41 -8
  107. package/src/msg_validators/tx_validator/index.ts +1 -0
  108. package/src/msg_validators/tx_validator/metadata_validator.ts +12 -4
  109. package/src/msg_validators/tx_validator/phases_validator.ts +29 -0
  110. package/src/services/libp2p/libp2p_service.ts +39 -15
  111. package/src/test-helpers/make-test-p2p-clients.ts +1 -1
  112. package/src/test-helpers/reqresp-nodes.ts +1 -1
  113. package/src/test-helpers/testbench-utils.ts +1 -0
  114. package/src/testbench/p2p_client_testbench_worker.ts +2 -1
  115. package/src/testbench/worker_client_manager.ts +13 -5
  116. package/src/util.ts +1 -1
  117. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts +0 -24
  118. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts.map +0 -1
  119. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.js +0 -378
  120. package/src/msg_validators/proposal_validator/proposal_validator_test_suite.ts +0 -373
@@ -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
  }
@@ -214,7 +214,9 @@ export class TxPoolV2Impl {
214
214
  // in-memory reads, and buffered DB writes. Nothing here can throw an unhandled exception.
215
215
  const poolAccess = this.#createPreAddPoolAccess();
216
216
  const preAddContext: PreAddContext | undefined =
217
- opts.feeComparisonOnly !== undefined ? { feeComparisonOnly: opts.feeComparisonOnly } : undefined;
217
+ opts.feeComparisonOnly !== undefined
218
+ ? { feeComparisonOnly: opts.feeComparisonOnly, priceBumpPercentage: this.#config.priceBumpPercentage }
219
+ : undefined;
218
220
 
219
221
  await this.#store.transactionAsync(async () => {
220
222
  for (const tx of txs) {
@@ -352,6 +354,7 @@ export class TxPoolV2Impl {
352
354
 
353
355
  // Check if already in pool
354
356
  if (this.#indices.has(txHashStr)) {
357
+ this.#log.verbose(`canAddPendingTx: tx ${txHashStr} already in pool`);
355
358
  return 'ignored';
356
359
  }
357
360
 
@@ -360,7 +363,13 @@ export class TxPoolV2Impl {
360
363
  const poolAccess = this.#createPreAddPoolAccess();
361
364
  const preAddResult = await this.#evictionManager.runPreAddRules(meta, poolAccess);
362
365
 
363
- return preAddResult.shouldIgnore ? 'ignored' : 'accepted';
366
+ if (preAddResult.shouldIgnore) {
367
+ this.#log.verbose(`canAddPendingTx: tx ${txHashStr} ignored by pre-add rule`, {
368
+ reason: preAddResult.reason?.message ?? 'no reason provided',
369
+ });
370
+ return 'ignored';
371
+ }
372
+ return 'accepted';
364
373
  }
365
374
 
366
375
  async addProtectedTxs(txs: Tx[], block: BlockHeader, opts: { source?: string }): Promise<void> {
@@ -1,10 +1,20 @@
1
1
  import type { EpochCacheInterface } from '@aztec/epoch-cache';
2
- import type { BlockProposal, P2PValidator } from '@aztec/stdlib/p2p';
2
+ import type { BlockProposal, P2PValidator, ValidationResult } from '@aztec/stdlib/p2p';
3
3
 
4
4
  import { ProposalValidator } from '../proposal_validator/proposal_validator.js';
5
5
 
6
- export class BlockProposalValidator extends ProposalValidator<BlockProposal> implements P2PValidator<BlockProposal> {
6
+ export class BlockProposalValidator implements P2PValidator<BlockProposal> {
7
+ private proposalValidator: ProposalValidator;
8
+
7
9
  constructor(epochCache: EpochCacheInterface, opts: { txsPermitted: boolean; maxTxsPerBlock?: number }) {
8
- super(epochCache, opts, 'p2p:block_proposal_validator');
10
+ this.proposalValidator = new ProposalValidator(epochCache, opts, 'p2p:block_proposal_validator');
11
+ }
12
+
13
+ async validate(proposal: BlockProposal): Promise<ValidationResult> {
14
+ const headerResult = await this.proposalValidator.validate(proposal);
15
+ if (headerResult.result !== 'accept') {
16
+ return headerResult;
17
+ }
18
+ return this.proposalValidator.validateTxs(proposal);
9
19
  }
10
20
  }
@@ -1,13 +1,26 @@
1
1
  import type { EpochCacheInterface } from '@aztec/epoch-cache';
2
- import type { CheckpointProposal, P2PValidator } from '@aztec/stdlib/p2p';
2
+ import type { CheckpointProposal, P2PValidator, ValidationResult } from '@aztec/stdlib/p2p';
3
3
 
4
4
  import { ProposalValidator } from '../proposal_validator/proposal_validator.js';
5
5
 
6
- export class CheckpointProposalValidator
7
- extends ProposalValidator<CheckpointProposal>
8
- implements P2PValidator<CheckpointProposal>
9
- {
6
+ export class CheckpointProposalValidator implements P2PValidator<CheckpointProposal> {
7
+ private proposalValidator: ProposalValidator;
8
+
10
9
  constructor(epochCache: EpochCacheInterface, opts: { txsPermitted: boolean; maxTxsPerBlock?: number }) {
11
- super(epochCache, opts, 'p2p:checkpoint_proposal_validator');
10
+ this.proposalValidator = new ProposalValidator(epochCache, opts, 'p2p:checkpoint_proposal_validator');
11
+ }
12
+
13
+ async validate(proposal: CheckpointProposal): Promise<ValidationResult> {
14
+ const headerResult = await this.proposalValidator.validate(proposal);
15
+ if (headerResult.result !== 'accept') {
16
+ return headerResult;
17
+ }
18
+
19
+ const blockProposal = proposal.getBlockProposal();
20
+ if (blockProposal) {
21
+ return this.proposalValidator.validateTxs(blockProposal);
22
+ }
23
+
24
+ return { result: 'accept' };
12
25
  }
13
26
  }
@@ -1,15 +1,21 @@
1
1
  import type { EpochCacheInterface } from '@aztec/epoch-cache';
2
2
  import { NoCommitteeError } from '@aztec/ethereum/contracts';
3
3
  import { type Logger, createLogger } from '@aztec/foundation/log';
4
- import { BlockProposal, CheckpointProposal, PeerErrorSeverity, type ValidationResult } from '@aztec/stdlib/p2p';
4
+ import {
5
+ type BlockProposal,
6
+ type CheckpointProposalCore,
7
+ PeerErrorSeverity,
8
+ type ValidationResult,
9
+ } from '@aztec/stdlib/p2p';
5
10
 
6
11
  import { isWithinClockTolerance } from '../clock_tolerance.js';
7
12
 
8
- export abstract class ProposalValidator<TProposal extends BlockProposal | CheckpointProposal> {
9
- protected epochCache: EpochCacheInterface;
10
- protected logger: Logger;
11
- protected txsPermitted: boolean;
12
- protected maxTxsPerBlock?: number;
13
+ /** Validates header-level and tx-level fields of block and checkpoint proposals. */
14
+ export class ProposalValidator {
15
+ private epochCache: EpochCacheInterface;
16
+ private logger: Logger;
17
+ private txsPermitted: boolean;
18
+ private maxTxsPerBlock?: number;
13
19
 
14
20
  constructor(
15
21
  epochCache: EpochCacheInterface,
@@ -22,7 +28,8 @@ export abstract class ProposalValidator<TProposal extends BlockProposal | Checkp
22
28
  this.logger = createLogger(loggerName);
23
29
  }
24
30
 
25
- public async validate(proposal: TProposal): Promise<ValidationResult> {
31
+ /** Validates header-level fields: slot, signature, and proposer. */
32
+ public async validate(proposal: BlockProposal | CheckpointProposalCore): Promise<ValidationResult> {
26
33
  try {
27
34
  // Slot check
28
35
  const { currentSlot, nextSlot } = this.epochCache.getCurrentAndNextSlot();
@@ -44,38 +51,6 @@ export abstract class ProposalValidator<TProposal extends BlockProposal | Checkp
44
51
  return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
45
52
  }
46
53
 
47
- // Transactions permitted check
48
- const embeddedTxCount = proposal.txs?.length ?? 0;
49
- if (!this.txsPermitted && (proposal.txHashes.length > 0 || embeddedTxCount > 0)) {
50
- this.logger.warn(
51
- `Penalizing peer for proposal with ${proposal.txHashes.length} transaction(s) when transactions are not permitted`,
52
- );
53
- return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
54
- }
55
-
56
- // Max txs per block check
57
- if (this.maxTxsPerBlock !== undefined && proposal.txHashes.length > this.maxTxsPerBlock) {
58
- this.logger.warn(
59
- `Penalizing peer for proposal with ${proposal.txHashes.length} transaction(s) when max is ${this.maxTxsPerBlock}`,
60
- );
61
- return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
62
- }
63
-
64
- // Embedded txs must be listed in txHashes
65
- const hashSet = new Set(proposal.txHashes.map(h => h.toString()));
66
- const missingTxHashes =
67
- embeddedTxCount > 0
68
- ? proposal.txs!.filter(tx => !hashSet.has(tx.getTxHash().toString())).map(tx => tx.getTxHash().toString())
69
- : [];
70
- if (embeddedTxCount > 0 && missingTxHashes.length > 0) {
71
- this.logger.warn('Penalizing peer for embedded transaction(s) not included in txHashes', {
72
- embeddedTxCount,
73
- txHashesLength: proposal.txHashes.length,
74
- missingTxHashes,
75
- });
76
- return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
77
- }
78
-
79
54
  // Proposer check
80
55
  const expectedProposer = await this.epochCache.getProposerAttesterAddressInSlot(slotNumber);
81
56
  if (expectedProposer !== undefined && !proposer.equals(expectedProposer)) {
@@ -86,15 +61,6 @@ export abstract class ProposalValidator<TProposal extends BlockProposal | Checkp
86
61
  return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
87
62
  }
88
63
 
89
- // Validate tx hashes for all txs embedded in the proposal
90
- if (!(await Promise.all(proposal.txs?.map(tx => tx.validateTxHash()) ?? [])).every(v => v)) {
91
- this.logger.warn(`Penalizing peer for invalid tx hashes in proposal`, {
92
- proposer,
93
- slotNumber,
94
- });
95
- return { result: 'reject', severity: PeerErrorSeverity.LowToleranceError };
96
- }
97
-
98
64
  return { result: 'accept' };
99
65
  } catch (e) {
100
66
  if (e instanceof NoCommitteeError) {
@@ -103,4 +69,47 @@ export abstract class ProposalValidator<TProposal extends BlockProposal | Checkp
103
69
  throw e;
104
70
  }
105
71
  }
72
+
73
+ /** Validates transaction-related fields of a block proposal. */
74
+ public async validateTxs(proposal: BlockProposal): Promise<ValidationResult> {
75
+ // Transactions permitted check
76
+ const embeddedTxCount = proposal.txs?.length ?? 0;
77
+ if (!this.txsPermitted && (proposal.txHashes.length > 0 || embeddedTxCount > 0)) {
78
+ this.logger.warn(
79
+ `Penalizing peer for proposal with ${proposal.txHashes.length} transaction(s) when transactions are not permitted`,
80
+ );
81
+ return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
82
+ }
83
+
84
+ // Max txs per block check
85
+ if (this.maxTxsPerBlock !== undefined && proposal.txHashes.length > this.maxTxsPerBlock) {
86
+ this.logger.warn(
87
+ `Penalizing peer for proposal with ${proposal.txHashes.length} transaction(s) when max is ${this.maxTxsPerBlock}`,
88
+ );
89
+ return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
90
+ }
91
+
92
+ // Embedded txs must be listed in txHashes
93
+ const hashSet = new Set(proposal.txHashes.map(h => h.toString()));
94
+ const missingTxHashes =
95
+ embeddedTxCount > 0
96
+ ? proposal.txs!.filter(tx => !hashSet.has(tx.getTxHash().toString())).map(tx => tx.getTxHash().toString())
97
+ : [];
98
+ if (embeddedTxCount > 0 && missingTxHashes.length > 0) {
99
+ this.logger.warn('Penalizing peer for embedded transaction(s) not included in txHashes', {
100
+ embeddedTxCount,
101
+ txHashesLength: proposal.txHashes.length,
102
+ missingTxHashes,
103
+ });
104
+ return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
105
+ }
106
+
107
+ // Validate tx hashes for all txs embedded in the proposal
108
+ if (!(await Promise.all(proposal.txs?.map(tx => tx.validateTxHash()) ?? [])).every(v => v)) {
109
+ this.logger.warn(`Penalizing peer for invalid tx hashes in proposal`);
110
+ return { result: 'reject', severity: PeerErrorSeverity.LowToleranceError };
111
+ }
112
+
113
+ return { result: 'accept' };
114
+ }
106
115
  }
@@ -1,49 +1,30 @@
1
- import { TokenContractArtifact } from '@aztec/noir-contracts.js/Token';
2
1
  import { ProtocolContractAddress } from '@aztec/protocol-contracts';
3
- import { FunctionSelector } from '@aztec/stdlib/abi';
4
- import { getContractClassFromArtifact } from '@aztec/stdlib/contract';
2
+ import { AuthRegistryArtifact } from '@aztec/protocol-contracts/auth-registry';
3
+ import { FeeJuiceArtifact } from '@aztec/protocol-contracts/fee-juice';
5
4
  import type { AllowedElement } from '@aztec/stdlib/interfaces/server';
6
5
 
6
+ import { buildAllowedElement } from './allowed_setup_helpers.js';
7
+
7
8
  let defaultAllowedSetupFunctions: AllowedElement[] | undefined;
8
9
 
9
10
  /** Returns the default list of functions allowed to run in the setup phase of a transaction. */
10
11
  export async function getDefaultAllowedSetupFunctions(): Promise<AllowedElement[]> {
11
12
  if (defaultAllowedSetupFunctions === undefined) {
12
- const tokenClassId = (await getContractClassFromArtifact(TokenContractArtifact)).id;
13
- const setAuthorizedInternalSelector = await FunctionSelector.fromSignature('_set_authorized((Field),Field,bool)');
14
- const setAuthorizedSelector = await FunctionSelector.fromSignature('set_authorized(Field,bool)');
15
- const increaseBalanceSelector = await FunctionSelector.fromSignature('_increase_public_balance((Field),u128)');
16
- const transferInPublicSelector = await FunctionSelector.fromSignature(
17
- 'transfer_in_public((Field),(Field),u128,Field)',
18
- );
19
-
20
- defaultAllowedSetupFunctions = [
13
+ defaultAllowedSetupFunctions = await Promise.all([
21
14
  // AuthRegistry: needed for authwit support via private path (set_authorized_private enqueues _set_authorized)
22
- {
23
- address: ProtocolContractAddress.AuthRegistry,
24
- selector: setAuthorizedInternalSelector,
25
- },
15
+ buildAllowedElement(AuthRegistryArtifact, { address: ProtocolContractAddress.AuthRegistry }, '_set_authorized', {
16
+ onlySelf: true,
17
+ rejectNullMsgSender: true,
18
+ }),
26
19
  // AuthRegistry: needed for authwit support via public path (PublicFeePaymentMethod calls set_authorized directly)
27
- {
28
- address: ProtocolContractAddress.AuthRegistry,
29
- selector: setAuthorizedSelector,
30
- },
20
+ buildAllowedElement(AuthRegistryArtifact, { address: ProtocolContractAddress.AuthRegistry }, 'set_authorized', {
21
+ rejectNullMsgSender: true,
22
+ }),
31
23
  // FeeJuice: needed for claiming on the same tx as a spend (claim_and_end_setup enqueues this)
32
- {
33
- address: ProtocolContractAddress.FeeJuice,
34
- selector: increaseBalanceSelector,
35
- },
36
- // Token: needed for private transfers via FPC (transfer_to_public enqueues this)
37
- {
38
- classId: tokenClassId,
39
- selector: increaseBalanceSelector,
40
- },
41
- // Token: needed for public transfers via FPC (fee_entrypoint_public enqueues this)
42
- {
43
- classId: tokenClassId,
44
- selector: transferInPublicSelector,
45
- },
46
- ];
24
+ buildAllowedElement(FeeJuiceArtifact, { address: ProtocolContractAddress.FeeJuice }, '_increase_public_balance', {
25
+ onlySelf: true,
26
+ }),
27
+ ]);
47
28
  }
48
29
  return defaultAllowedSetupFunctions;
49
30
  }
@@ -0,0 +1,31 @@
1
+ import type { Fr } from '@aztec/foundation/curves/bn254';
2
+ import { FunctionSelector, countArgumentsSize, getAllFunctionAbis } from '@aztec/stdlib/abi';
3
+ import type { ContractArtifact } from '@aztec/stdlib/abi';
4
+ import type { AztecAddress } from '@aztec/stdlib/aztec-address';
5
+ import type { AllowedElement } from '@aztec/stdlib/interfaces/server';
6
+
7
+ /**
8
+ * Builds an AllowedElement from a contract artifact, deriving both the function selector
9
+ * and calldata length from the artifact instead of hardcoding signature strings.
10
+ */
11
+ export async function buildAllowedElement(
12
+ artifact: ContractArtifact,
13
+ target: { address: AztecAddress } | { classId: Fr },
14
+ functionName: string,
15
+ opts?: { onlySelf?: boolean; rejectNullMsgSender?: boolean },
16
+ ): Promise<AllowedElement> {
17
+ const allFunctions = getAllFunctionAbis(artifact);
18
+ const fn = allFunctions.find(f => f.name === functionName);
19
+ if (!fn) {
20
+ throw new Error(`Unknown function ${functionName} in artifact ${artifact.name}`);
21
+ }
22
+ const selector = await FunctionSelector.fromNameAndParameters(fn.name, fn.parameters);
23
+ const calldataLength = 1 + countArgumentsSize(fn);
24
+ return {
25
+ ...target,
26
+ selector,
27
+ calldataLength,
28
+ ...(opts?.onlySelf ? { onlySelf: true } : {}),
29
+ ...(opts?.rejectNullMsgSender ? { rejectNullMsgSender: true } : {}),
30
+ } as AllowedElement;
31
+ }
@@ -97,6 +97,7 @@ export function createFirstStageTxValidationsForGossipedTransactions(
97
97
  txsPermitted: boolean,
98
98
  allowedInSetup: AllowedElement[] = [],
99
99
  bindings?: LoggerBindings,
100
+ gasLimitOpts?: { rollupManaLimit?: number; maxBlockL2Gas?: number; maxBlockDAGas?: number },
100
101
  ): Record<string, TransactionValidator> {
101
102
  const merkleTree = worldStateSynchronizer.getCommitted();
102
103
 
@@ -158,6 +159,7 @@ export function createFirstStageTxValidationsForGossipedTransactions(
158
159
  ProtocolContractAddress.FeeJuice,
159
160
  gasFees,
160
161
  bindings,
162
+ gasLimitOpts,
161
163
  ),
162
164
  severity: PeerErrorSeverity.MidToleranceError,
163
165
  },
@@ -278,6 +280,9 @@ export function createTxValidatorForAcceptingTxsOverRPC(
278
280
  timestamp,
279
281
  blockNumber,
280
282
  txsPermitted,
283
+ rollupManaLimit,
284
+ maxBlockL2Gas,
285
+ maxBlockDAGas,
281
286
  }: {
282
287
  l1ChainId: number;
283
288
  rollupVersion: number;
@@ -287,6 +292,9 @@ export function createTxValidatorForAcceptingTxsOverRPC(
287
292
  timestamp: UInt64;
288
293
  blockNumber: BlockNumber;
289
294
  txsPermitted: boolean;
295
+ rollupManaLimit: number;
296
+ maxBlockL2Gas?: number;
297
+ maxBlockDAGas?: number;
290
298
  },
291
299
  bindings?: LoggerBindings,
292
300
  ): TxValidator<Tx> {
@@ -317,7 +325,11 @@ export function createTxValidatorForAcceptingTxsOverRPC(
317
325
 
318
326
  if (!skipFeeEnforcement) {
319
327
  validators.push(
320
- new GasTxValidator(new DatabasePublicStateSource(db), ProtocolContractAddress.FeeJuice, gasFees, bindings),
328
+ new GasTxValidator(new DatabasePublicStateSource(db), ProtocolContractAddress.FeeJuice, gasFees, bindings, {
329
+ rollupManaLimit,
330
+ maxBlockL2Gas,
331
+ maxBlockDAGas,
332
+ }),
321
333
  );
322
334
  }
323
335
 
@@ -403,6 +415,7 @@ export async function createTxValidatorForTransactionsEnteringPendingTxPool(
403
415
  worldStateSynchronizer: WorldStateSynchronizer,
404
416
  timestamp: bigint,
405
417
  blockNumber: BlockNumber,
418
+ gasLimitOpts: { rollupManaLimit?: number; maxBlockL2Gas?: number; maxBlockDAGas?: number },
406
419
  bindings?: LoggerBindings,
407
420
  ): Promise<TxValidator<TxMetaData>> {
408
421
  await worldStateSynchronizer.syncImmediate();
@@ -419,7 +432,7 @@ export async function createTxValidatorForTransactionsEnteringPendingTxPool(
419
432
  },
420
433
  };
421
434
  return new AggregateTxValidator<TxMetaData>(
422
- new GasLimitsValidator<TxMetaData>(bindings),
435
+ new GasLimitsValidator<TxMetaData>({ ...gasLimitOpts, bindings }),
423
436
  new TimestampTxValidator<TxMetaData>({ timestamp, blockNumber }, bindings),
424
437
  new DoubleSpendTxValidator<TxMetaData>(nullifierSource, bindings),
425
438
  new BlockHeaderTxValidator<TxMetaData>(archiveSource, bindings),
@@ -1,5 +1,6 @@
1
+ import { FeeJuiceArtifact } from '@aztec/protocol-contracts/fee-juice';
1
2
  import { getCallRequestsWithCalldataByPhase } from '@aztec/simulator/server';
2
- import { FunctionSelector } from '@aztec/stdlib/abi';
3
+ import { FunctionSelector, getAllFunctionAbis } from '@aztec/stdlib/abi';
3
4
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
4
5
  import { type Tx, TxExecutionPhase } from '@aztec/stdlib/tx';
5
6
 
@@ -8,7 +9,10 @@ export type FeePayerBalanceDelta = {
8
9
  claimAmount: bigint;
9
10
  };
10
11
 
11
- const increasePublicBalanceSelectorPromise = FunctionSelector.fromSignature('_increase_public_balance((Field),u128)');
12
+ const increasePublicBalanceSelectorPromise = (() => {
13
+ const fn = getAllFunctionAbis(FeeJuiceArtifact).find(f => f.name === '_increase_public_balance')!;
14
+ return FunctionSelector.fromNameAndParameters(fn.name, fn.parameters);
15
+ })();
12
16
 
13
17
  export function getTxFeeLimit(tx: Tx): bigint {
14
18
  return tx.data.constants.txContext.gasSettings.getFeeLimit().toBigInt();
@@ -1,4 +1,5 @@
1
1
  import {
2
+ MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT,
2
3
  MAX_PROCESSABLE_L2_GAS,
3
4
  PRIVATE_TX_L2_GAS_OVERHEAD,
4
5
  PUBLIC_TX_L2_GAS_OVERHEAD,
@@ -49,16 +50,31 @@ export interface HasGasLimitData {
49
50
  */
50
51
  export class GasLimitsValidator<T extends HasGasLimitData> implements TxValidator<T> {
51
52
  #log: Logger;
52
-
53
- constructor(bindings?: LoggerBindings) {
54
- this.#log = createLogger('sequencer:tx_validator:tx_gas', bindings);
53
+ #effectiveMaxL2Gas: number;
54
+ #effectiveMaxDAGas: number;
55
+ #rollupManaLimit: number;
56
+ #maxBlockL2Gas: number;
57
+ #maxBlockDAGas: number;
58
+
59
+ constructor(opts?: {
60
+ rollupManaLimit?: number;
61
+ maxBlockL2Gas?: number;
62
+ maxBlockDAGas?: number;
63
+ bindings?: LoggerBindings;
64
+ }) {
65
+ this.#log = createLogger('sequencer:tx_validator:tx_gas', opts?.bindings);
66
+ this.#rollupManaLimit = opts?.rollupManaLimit ?? Infinity;
67
+ this.#maxBlockL2Gas = opts?.maxBlockL2Gas ?? Infinity;
68
+ this.#maxBlockDAGas = opts?.maxBlockDAGas ?? Infinity;
69
+ this.#effectiveMaxL2Gas = Math.min(MAX_PROCESSABLE_L2_GAS, this.#rollupManaLimit, this.#maxBlockL2Gas);
70
+ this.#effectiveMaxDAGas = Math.min(MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT, this.#maxBlockDAGas);
55
71
  }
56
72
 
57
73
  validateTx(tx: T): Promise<TxValidationResult> {
58
74
  return Promise.resolve(this.validateGasLimit(tx));
59
75
  }
60
76
 
61
- /** Checks gas limits are >= fixed minimums and <= AVM max processable L2 gas. */
77
+ /** Checks gas limits are >= fixed minimums and <= effective max gas (L2 and DA). */
62
78
  validateGasLimit(tx: T): TxValidationResult {
63
79
  const gasLimits = tx.data.constants.txContext.gasSettings.gasLimits;
64
80
  const minGasLimits = new Gas(
@@ -74,10 +90,21 @@ export class GasLimitsValidator<T extends HasGasLimitData> implements TxValidato
74
90
  return { result: 'invalid', reason: [TX_ERROR_INSUFFICIENT_GAS_LIMIT] };
75
91
  }
76
92
 
77
- if (gasLimits.l2Gas > MAX_PROCESSABLE_L2_GAS) {
78
- this.#log.verbose(`Rejecting transaction due to the gas limit(s) being higher than the maximum processable gas`, {
93
+ if (gasLimits.l2Gas > this.#effectiveMaxL2Gas) {
94
+ this.#log.verbose(`Rejecting transaction due to the L2 gas limit being higher than the effective maximum`, {
79
95
  gasLimits,
80
- minGasLimits,
96
+ effectiveMaxL2Gas: this.#effectiveMaxL2Gas,
97
+ rollupManaLimit: this.#rollupManaLimit,
98
+ maxBlockL2Gas: this.#maxBlockL2Gas,
99
+ });
100
+ return { result: 'invalid', reason: [TX_ERROR_GAS_LIMIT_TOO_HIGH] };
101
+ }
102
+
103
+ if (gasLimits.daGas > this.#effectiveMaxDAGas) {
104
+ this.#log.verbose(`Rejecting transaction due to the DA gas limit being higher than the effective maximum`, {
105
+ gasLimits,
106
+ effectiveMaxDAGas: this.#effectiveMaxDAGas,
107
+ maxBlockDAGas: this.#maxBlockDAGas,
81
108
  });
82
109
  return { result: 'invalid', reason: [TX_ERROR_GAS_LIMIT_TOO_HIGH] };
83
110
  }
@@ -106,21 +133,27 @@ export class GasTxValidator implements TxValidator<Tx> {
106
133
  #publicDataSource: PublicStateSource;
107
134
  #feeJuiceAddress: AztecAddress;
108
135
  #gasFees: GasFees;
136
+ #gasLimitOpts?: { rollupManaLimit?: number; maxBlockL2Gas?: number; maxBlockDAGas?: number };
109
137
 
110
138
  constructor(
111
139
  publicDataSource: PublicStateSource,
112
140
  feeJuiceAddress: AztecAddress,
113
141
  gasFees: GasFees,
114
142
  private bindings?: LoggerBindings,
143
+ opts?: { rollupManaLimit?: number; maxBlockL2Gas?: number; maxBlockDAGas?: number },
115
144
  ) {
116
145
  this.#log = createLogger('sequencer:tx_validator:tx_gas', bindings);
117
146
  this.#publicDataSource = publicDataSource;
118
147
  this.#feeJuiceAddress = feeJuiceAddress;
119
148
  this.#gasFees = gasFees;
149
+ this.#gasLimitOpts = opts;
120
150
  }
121
151
 
122
152
  async validateTx(tx: Tx): Promise<TxValidationResult> {
123
- const gasLimitValidation = new GasLimitsValidator(this.bindings).validateGasLimit(tx);
153
+ const gasLimitValidation = new GasLimitsValidator({
154
+ ...this.#gasLimitOpts,
155
+ bindings: this.bindings,
156
+ }).validateGasLimit(tx);
124
157
  if (gasLimitValidation.result === 'invalid') {
125
158
  return Promise.resolve(gasLimitValidation);
126
159
  }
@@ -8,6 +8,7 @@ export * from './gas_validator.js';
8
8
  export * from './phases_validator.js';
9
9
  export * from './test_utils.js';
10
10
  export * from './allowed_public_setup.js';
11
+ export * from './allowed_setup_helpers.js';
11
12
  export * from './archive_cache.js';
12
13
  export * from './tx_permitted_validator.js';
13
14
  export * from './timestamp_validator.js';