@aztec/p2p 0.0.1-commit.ec5f612 → 0.0.1-commit.ef17749e1

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 +2 -1
  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 +0 -24
  7. package/dest/config.d.ts +20 -11
  8. package/dest/config.d.ts.map +1 -1
  9. package/dest/config.js +66 -32
  10. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +1 -1
  11. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  12. package/dest/mem_pools/attestation_pool/attestation_pool.js +5 -1
  13. package/dest/mem_pools/instrumentation.d.ts +4 -2
  14. package/dest/mem_pools/instrumentation.d.ts.map +1 -1
  15. package/dest/mem_pools/instrumentation.js +16 -14
  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.d.ts +1 -1
  34. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
  35. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +3 -0
  36. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +2 -1
  37. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  38. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +5 -1
  39. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts +6 -4
  40. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts.map +1 -1
  41. package/dest/msg_validators/proposal_validator/block_proposal_validator.js +10 -2
  42. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts +6 -4
  43. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts.map +1 -1
  44. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.js +16 -2
  45. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +13 -8
  46. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
  47. package/dest/msg_validators/proposal_validator/proposal_validator.js +48 -36
  48. package/dest/msg_validators/tx_validator/allowed_public_setup.d.ts +2 -1
  49. package/dest/msg_validators/tx_validator/allowed_public_setup.d.ts.map +1 -1
  50. package/dest/msg_validators/tx_validator/allowed_public_setup.js +24 -20
  51. package/dest/msg_validators/tx_validator/allowed_setup_helpers.d.ts +17 -0
  52. package/dest/msg_validators/tx_validator/allowed_setup_helpers.d.ts.map +1 -0
  53. package/dest/msg_validators/tx_validator/allowed_setup_helpers.js +24 -0
  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/index.d.ts +2 -1
  58. package/dest/msg_validators/tx_validator/index.d.ts.map +1 -1
  59. package/dest/msg_validators/tx_validator/index.js +1 -0
  60. package/dest/msg_validators/tx_validator/metadata_validator.d.ts +1 -1
  61. package/dest/msg_validators/tx_validator/metadata_validator.d.ts.map +1 -1
  62. package/dest/msg_validators/tx_validator/metadata_validator.js +4 -4
  63. package/dest/msg_validators/tx_validator/phases_validator.d.ts +2 -2
  64. package/dest/msg_validators/tx_validator/phases_validator.d.ts.map +1 -1
  65. package/dest/msg_validators/tx_validator/phases_validator.js +44 -23
  66. package/dest/services/libp2p/libp2p_service.d.ts +1 -1
  67. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  68. package/dest/services/libp2p/libp2p_service.js +16 -10
  69. package/dest/services/peer-manager/metrics.d.ts +3 -1
  70. package/dest/services/peer-manager/metrics.d.ts.map +1 -1
  71. package/dest/services/peer-manager/metrics.js +6 -0
  72. package/dest/services/peer-manager/peer_manager.d.ts +1 -1
  73. package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
  74. package/dest/services/peer-manager/peer_manager.js +2 -1
  75. package/dest/test-helpers/make-test-p2p-clients.d.ts +1 -1
  76. package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
  77. package/dest/test-helpers/reqresp-nodes.d.ts +1 -1
  78. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -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 +2 -1
  86. package/src/client/p2p_client.ts +0 -22
  87. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +1 -1
  88. package/src/config.ts +91 -34
  89. package/src/mem_pools/attestation_pool/attestation_pool.ts +5 -4
  90. package/src/mem_pools/instrumentation.ts +17 -13
  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.ts +3 -0
  99. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +8 -1
  100. package/src/msg_validators/proposal_validator/block_proposal_validator.ts +14 -4
  101. package/src/msg_validators/proposal_validator/checkpoint_proposal_validator.ts +20 -7
  102. package/src/msg_validators/proposal_validator/proposal_validator.ts +63 -40
  103. package/src/msg_validators/tx_validator/allowed_public_setup.ts +22 -27
  104. package/src/msg_validators/tx_validator/allowed_setup_helpers.ts +31 -0
  105. package/src/msg_validators/tx_validator/fee_payer_balance.ts +6 -2
  106. package/src/msg_validators/tx_validator/index.ts +1 -0
  107. package/src/msg_validators/tx_validator/metadata_validator.ts +12 -4
  108. package/src/msg_validators/tx_validator/phases_validator.ts +51 -26
  109. package/src/services/libp2p/libp2p_service.ts +15 -6
  110. package/src/services/peer-manager/metrics.ts +7 -0
  111. package/src/services/peer-manager/peer_manager.ts +2 -1
  112. package/src/test-helpers/make-test-p2p-clients.ts +1 -1
  113. package/src/test-helpers/reqresp-nodes.ts +1 -1
  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 -23
  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 -212
  120. package/src/msg_validators/proposal_validator/proposal_validator_test_suite.ts +0 -230
@@ -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
  }
@@ -58,6 +58,9 @@ export class AztecKVTxPoolV2 extends (EventEmitter as new () => TypedEventEmitte
58
58
  const hashes = txHashes.map(h => (typeof h === 'string' ? TxHash.fromString(h) : TxHash.fromBigInt(h)));
59
59
  this.emit('txs-removed', { txHashes: hashes });
60
60
  },
61
+ onTxsMined: (txHashes: string[]) => {
62
+ this.#metrics?.transactionsRemoved(txHashes);
63
+ },
61
64
  };
62
65
 
63
66
  // Create the implementation
@@ -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
  /**
@@ -213,7 +214,9 @@ export class TxPoolV2Impl {
213
214
  // in-memory reads, and buffered DB writes. Nothing here can throw an unhandled exception.
214
215
  const poolAccess = this.#createPreAddPoolAccess();
215
216
  const preAddContext: PreAddContext | undefined =
216
- opts.feeComparisonOnly !== undefined ? { feeComparisonOnly: opts.feeComparisonOnly } : undefined;
217
+ opts.feeComparisonOnly !== undefined
218
+ ? { feeComparisonOnly: opts.feeComparisonOnly, priceBumpPercentage: this.#config.priceBumpPercentage }
219
+ : undefined;
217
220
 
218
221
  await this.#store.transactionAsync(async () => {
219
222
  for (const tx of txs) {
@@ -498,6 +501,10 @@ export class TxPoolV2Impl {
498
501
  await this.#evictionManager.evictAfterNewBlock(block.header, nullifiers, feePayers);
499
502
  });
500
503
 
504
+ if (found.length > 0) {
505
+ this.#callbacks.onTxsMined(found.map(m => m.txHash));
506
+ }
507
+
501
508
  this.#log.info(`Marked ${found.length} txs as mined in block ${blockId.number}`);
502
509
  }
503
510
 
@@ -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> {
7
- constructor(epochCache: EpochCacheInterface, opts: { txsPermitted: boolean }) {
8
- super(epochCache, opts, 'p2p:block_proposal_validator');
6
+ export class BlockProposalValidator implements P2PValidator<BlockProposal> {
7
+ private proposalValidator: ProposalValidator;
8
+
9
+ constructor(epochCache: EpochCacheInterface, opts: { txsPermitted: boolean; maxTxsPerBlock?: number }) {
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
- {
10
- constructor(epochCache: EpochCacheInterface, opts: { txsPermitted: boolean }) {
11
- super(epochCache, opts, 'p2p:checkpoint_proposal_validator');
6
+ export class CheckpointProposalValidator implements P2PValidator<CheckpointProposal> {
7
+ private proposalValidator: ProposalValidator;
8
+
9
+ constructor(epochCache: EpochCacheInterface, opts: { txsPermitted: boolean; maxTxsPerBlock?: number }) {
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,22 +1,35 @@
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;
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;
12
19
 
13
- constructor(epochCache: EpochCacheInterface, opts: { txsPermitted: boolean }, loggerName: string) {
20
+ constructor(
21
+ epochCache: EpochCacheInterface,
22
+ opts: { txsPermitted: boolean; maxTxsPerBlock?: number },
23
+ loggerName: string,
24
+ ) {
14
25
  this.epochCache = epochCache;
15
26
  this.txsPermitted = opts.txsPermitted;
27
+ this.maxTxsPerBlock = opts.maxTxsPerBlock;
16
28
  this.logger = createLogger(loggerName);
17
29
  }
18
30
 
19
- 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> {
20
33
  try {
21
34
  // Slot check
22
35
  const { currentSlot, nextSlot } = this.epochCache.getCurrentAndNextSlot();
@@ -38,30 +51,6 @@ export abstract class ProposalValidator<TProposal extends BlockProposal | Checkp
38
51
  return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
39
52
  }
40
53
 
41
- // Transactions permitted check
42
- const embeddedTxCount = proposal.txs?.length ?? 0;
43
- if (!this.txsPermitted && (proposal.txHashes.length > 0 || embeddedTxCount > 0)) {
44
- this.logger.warn(
45
- `Penalizing peer for proposal with ${proposal.txHashes.length} transaction(s) when transactions are not permitted`,
46
- );
47
- return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
48
- }
49
-
50
- // Embedded txs must be listed in txHashes
51
- const hashSet = new Set(proposal.txHashes.map(h => h.toString()));
52
- const missingTxHashes =
53
- embeddedTxCount > 0
54
- ? proposal.txs!.filter(tx => !hashSet.has(tx.getTxHash().toString())).map(tx => tx.getTxHash().toString())
55
- : [];
56
- if (embeddedTxCount > 0 && missingTxHashes.length > 0) {
57
- this.logger.warn('Penalizing peer for embedded transaction(s) not included in txHashes', {
58
- embeddedTxCount,
59
- txHashesLength: proposal.txHashes.length,
60
- missingTxHashes,
61
- });
62
- return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
63
- }
64
-
65
54
  // Proposer check
66
55
  const expectedProposer = await this.epochCache.getProposerAttesterAddressInSlot(slotNumber);
67
56
  if (expectedProposer !== undefined && !proposer.equals(expectedProposer)) {
@@ -72,15 +61,6 @@ export abstract class ProposalValidator<TProposal extends BlockProposal | Checkp
72
61
  return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
73
62
  }
74
63
 
75
- // Validate tx hashes for all txs embedded in the proposal
76
- if (!(await Promise.all(proposal.txs?.map(tx => tx.validateTxHash()) ?? [])).every(v => v)) {
77
- this.logger.warn(`Penalizing peer for invalid tx hashes in proposal`, {
78
- proposer,
79
- slotNumber,
80
- });
81
- return { result: 'reject', severity: PeerErrorSeverity.LowToleranceError };
82
- }
83
-
84
64
  return { result: 'accept' };
85
65
  } catch (e) {
86
66
  if (e instanceof NoCommitteeError) {
@@ -89,4 +69,47 @@ export abstract class ProposalValidator<TProposal extends BlockProposal | Checkp
89
69
  throw e;
90
70
  }
91
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
+ }
92
115
  }
@@ -1,35 +1,30 @@
1
- import { FPCContract } from '@aztec/noir-contracts.js/FPC';
2
- import { TokenContractArtifact } from '@aztec/noir-contracts.js/Token';
3
1
  import { ProtocolContractAddress } from '@aztec/protocol-contracts';
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
 
7
- let defaultAllowedSetupFunctions: AllowedElement[] | undefined = undefined;
6
+ import { buildAllowedElement } from './allowed_setup_helpers.js';
7
+
8
+ let defaultAllowedSetupFunctions: AllowedElement[] | undefined;
9
+
10
+ /** Returns the default list of functions allowed to run in the setup phase of a transaction. */
8
11
  export async function getDefaultAllowedSetupFunctions(): Promise<AllowedElement[]> {
9
12
  if (defaultAllowedSetupFunctions === undefined) {
10
- defaultAllowedSetupFunctions = [
11
- // needed for authwit support
12
- {
13
- address: ProtocolContractAddress.AuthRegistry,
14
- },
15
- // needed for claiming on the same tx as a spend
16
- {
17
- address: ProtocolContractAddress.FeeJuice,
18
- // We can't restrict the selector because public functions get routed via dispatch.
19
- // selector: FunctionSelector.fromSignature('_increase_public_balance((Field),u128)'),
20
- },
21
- // needed for private transfers via FPC
22
- {
23
- classId: (await getContractClassFromArtifact(TokenContractArtifact)).id,
24
- // We can't restrict the selector because public functions get routed via dispatch.
25
- // selector: FunctionSelector.fromSignature('_increase_public_balance((Field),u128)'),
26
- },
27
- {
28
- classId: (await getContractClassFromArtifact(FPCContract.artifact)).id,
29
- // We can't restrict the selector because public functions get routed via dispatch.
30
- // selector: FunctionSelector.fromSignature('prepare_fee((Field),Field,(Field),Field)'),
31
- },
32
- ];
13
+ defaultAllowedSetupFunctions = await Promise.all([
14
+ // AuthRegistry: needed for authwit support via private path (set_authorized_private enqueues _set_authorized)
15
+ buildAllowedElement(AuthRegistryArtifact, { address: ProtocolContractAddress.AuthRegistry }, '_set_authorized', {
16
+ onlySelf: true,
17
+ rejectNullMsgSender: true,
18
+ }),
19
+ // AuthRegistry: needed for authwit support via public path (PublicFeePaymentMethod calls set_authorized directly)
20
+ buildAllowedElement(AuthRegistryArtifact, { address: ProtocolContractAddress.AuthRegistry }, 'set_authorized', {
21
+ rejectNullMsgSender: true,
22
+ }),
23
+ // FeeJuice: needed for claiming on the same tx as a spend (claim_and_end_setup enqueues this)
24
+ buildAllowedElement(FeeJuiceArtifact, { address: ProtocolContractAddress.FeeJuice }, '_increase_public_balance', {
25
+ onlySelf: true,
26
+ }),
27
+ ]);
33
28
  }
34
29
  return defaultAllowedSetupFunctions;
35
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
+ }
@@ -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();
@@ -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';
@@ -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
  }
@@ -1,11 +1,17 @@
1
+ import { NULL_MSG_SENDER_CONTRACT_ADDRESS } from '@aztec/constants';
1
2
  import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
2
3
  import { PublicContractsDB, getCallRequestsWithCalldataByPhase } from '@aztec/simulator/server';
4
+ import { AztecAddress } from '@aztec/stdlib/aztec-address';
3
5
  import type { ContractDataSource } from '@aztec/stdlib/contract';
4
6
  import type { AllowedElement } from '@aztec/stdlib/interfaces/server';
5
7
  import {
6
8
  type PublicCallRequestWithCalldata,
7
9
  TX_ERROR_DURING_VALIDATION,
8
10
  TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED,
11
+ TX_ERROR_SETUP_FUNCTION_UNKNOWN_CONTRACT,
12
+ TX_ERROR_SETUP_NULL_MSG_SENDER,
13
+ TX_ERROR_SETUP_ONLY_SELF_WRONG_SENDER,
14
+ TX_ERROR_SETUP_WRONG_CALLDATA_LENGTH,
9
15
  Tx,
10
16
  TxExecutionPhase,
11
17
  type TxValidationResult,
@@ -45,7 +51,8 @@ export class PhasesTxValidator implements TxValidator<Tx> {
45
51
 
46
52
  const setupFns = getCallRequestsWithCalldataByPhase(tx, TxExecutionPhase.SETUP);
47
53
  for (const setupFn of setupFns) {
48
- if (!(await this.isOnAllowList(setupFn, this.setupAllowList))) {
54
+ const rejectionReason = await this.checkAllowList(setupFn, this.setupAllowList);
55
+ if (rejectionReason) {
49
56
  this.#log.verbose(
50
57
  `Rejecting tx ${tx.getTxHash().toString()} because it calls setup function not on allow list: ${
51
58
  setupFn.request.contractAddress
@@ -53,7 +60,7 @@ export class PhasesTxValidator implements TxValidator<Tx> {
53
60
  { allowList: this.setupAllowList },
54
61
  );
55
62
 
56
- return { result: 'invalid', reason: [TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED] };
63
+ return { result: 'invalid', reason: [rejectionReason] };
57
64
  }
58
65
  }
59
66
 
@@ -66,53 +73,71 @@ export class PhasesTxValidator implements TxValidator<Tx> {
66
73
  }
67
74
  }
68
75
 
69
- private async isOnAllowList(
76
+ /** Returns a rejection reason if the call is not on the allow list, or undefined if it is allowed. */
77
+ private async checkAllowList(
70
78
  publicCall: PublicCallRequestWithCalldata,
71
79
  allowList: AllowedElement[],
72
- ): Promise<boolean> {
80
+ ): Promise<string | undefined> {
73
81
  if (publicCall.isEmpty()) {
74
- return true;
82
+ return undefined;
75
83
  }
76
84
 
77
85
  const contractAddress = publicCall.request.contractAddress;
78
86
  const functionSelector = publicCall.functionSelector;
79
87
 
80
- // do these checks first since they don't require the contract class
88
+ // Check address-based entries first since they don't require the contract class.
81
89
  for (const entry of allowList) {
82
- if ('address' in entry && !('selector' in entry)) {
83
- if (contractAddress.equals(entry.address)) {
84
- return true;
85
- }
86
- }
87
-
88
- if ('address' in entry && 'selector' in entry) {
90
+ if ('address' in entry) {
89
91
  if (contractAddress.equals(entry.address) && entry.selector.equals(functionSelector)) {
90
- return true;
92
+ if (entry.calldataLength !== undefined && publicCall.calldata.length !== entry.calldataLength) {
93
+ return TX_ERROR_SETUP_WRONG_CALLDATA_LENGTH;
94
+ }
95
+ if (entry.onlySelf && !publicCall.request.msgSender.equals(contractAddress)) {
96
+ return TX_ERROR_SETUP_ONLY_SELF_WRONG_SENDER;
97
+ }
98
+ if (
99
+ entry.rejectNullMsgSender &&
100
+ publicCall.request.msgSender.equals(AztecAddress.fromBigInt(NULL_MSG_SENDER_CONTRACT_ADDRESS))
101
+ ) {
102
+ return TX_ERROR_SETUP_NULL_MSG_SENDER;
103
+ }
104
+ return undefined;
91
105
  }
92
106
  }
107
+ }
93
108
 
94
- const contractClass = await this.contractsDB.getContractInstance(contractAddress, this.timestamp);
95
-
96
- if (!contractClass) {
97
- throw new Error(`Contract not found: ${contractAddress}`);
109
+ // Check class-based entries. Fetch the contract instance lazily (only once).
110
+ let contractClassId: undefined | { value: string | undefined };
111
+ for (const entry of allowList) {
112
+ if (!('classId' in entry)) {
113
+ continue;
98
114
  }
99
115
 
100
- if ('classId' in entry && !('selector' in entry)) {
101
- if (contractClass.currentContractClassId.equals(entry.classId)) {
102
- return true;
116
+ if (contractClassId === undefined) {
117
+ const instance = await this.contractsDB.getContractInstance(contractAddress, this.timestamp);
118
+ contractClassId = { value: instance?.currentContractClassId.toString() };
119
+ if (!contractClassId.value) {
120
+ return TX_ERROR_SETUP_FUNCTION_UNKNOWN_CONTRACT;
103
121
  }
104
122
  }
105
123
 
106
- if ('classId' in entry && 'selector' in entry) {
124
+ if (contractClassId.value === entry.classId.toString() && entry.selector.equals(functionSelector)) {
125
+ if (entry.calldataLength !== undefined && publicCall.calldata.length !== entry.calldataLength) {
126
+ return TX_ERROR_SETUP_WRONG_CALLDATA_LENGTH;
127
+ }
128
+ if (entry.onlySelf && !publicCall.request.msgSender.equals(contractAddress)) {
129
+ return TX_ERROR_SETUP_ONLY_SELF_WRONG_SENDER;
130
+ }
107
131
  if (
108
- contractClass.currentContractClassId.equals(entry.classId) &&
109
- (entry.selector === undefined || entry.selector.equals(functionSelector))
132
+ entry.rejectNullMsgSender &&
133
+ publicCall.request.msgSender.equals(AztecAddress.fromBigInt(NULL_MSG_SENDER_CONTRACT_ADDRESS))
110
134
  ) {
111
- return true;
135
+ return TX_ERROR_SETUP_NULL_MSG_SENDER;
112
136
  }
137
+ return undefined;
113
138
  }
114
139
  }
115
140
 
116
- return false;
141
+ return TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED;
117
142
  }
118
143
  }