@aztec/sequencer-client 0.33.0 → 0.35.0

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 (102) hide show
  1. package/dest/client/sequencer-client.d.ts.map +1 -1
  2. package/dest/client/sequencer-client.js +3 -3
  3. package/dest/config.d.ts.map +1 -1
  4. package/dest/config.js +84 -7
  5. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  6. package/dest/global_variable_builder/global_builder.js +5 -4
  7. package/dest/index.d.ts +0 -1
  8. package/dest/index.d.ts.map +1 -1
  9. package/dest/index.js +1 -2
  10. package/dest/publisher/l1-publisher.js +10 -10
  11. package/dest/publisher/viem-tx-sender.js +2 -2
  12. package/dest/sequencer/sequencer.d.ts +7 -8
  13. package/dest/sequencer/sequencer.d.ts.map +1 -1
  14. package/dest/sequencer/sequencer.js +22 -24
  15. package/dest/tx_validator/aggregate_tx_validator.d.ts +7 -0
  16. package/dest/tx_validator/aggregate_tx_validator.d.ts.map +1 -0
  17. package/dest/tx_validator/aggregate_tx_validator.js +23 -0
  18. package/dest/tx_validator/double_spend_validator.d.ts +11 -0
  19. package/dest/tx_validator/double_spend_validator.d.ts.map +1 -0
  20. package/dest/tx_validator/double_spend_validator.js +50 -0
  21. package/dest/tx_validator/gas_validator.d.ts +12 -0
  22. package/dest/tx_validator/gas_validator.d.ts.map +1 -0
  23. package/dest/tx_validator/gas_validator.js +62 -0
  24. package/dest/tx_validator/metadata_validator.d.ts +8 -0
  25. package/dest/tx_validator/metadata_validator.d.ts.map +1 -0
  26. package/dest/tx_validator/metadata_validator.js +50 -0
  27. package/dest/tx_validator/phases_validator.d.ts +13 -0
  28. package/dest/tx_validator/phases_validator.d.ts.map +1 -0
  29. package/dest/tx_validator/phases_validator.js +73 -0
  30. package/dest/tx_validator/tx_validator_factory.d.ts +13 -0
  31. package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -0
  32. package/dest/tx_validator/tx_validator_factory.js +21 -0
  33. package/package.json +14 -13
  34. package/src/client/sequencer-client.ts +2 -3
  35. package/src/config.ts +93 -7
  36. package/src/global_variable_builder/global_builder.ts +4 -3
  37. package/src/index.ts +0 -1
  38. package/src/publisher/l1-publisher.ts +9 -9
  39. package/src/publisher/viem-tx-sender.ts +1 -1
  40. package/src/sequencer/sequencer.ts +39 -33
  41. package/src/tx_validator/aggregate_tx_validator.ts +24 -0
  42. package/src/tx_validator/double_spend_validator.ts +65 -0
  43. package/src/tx_validator/gas_validator.ts +81 -0
  44. package/src/tx_validator/metadata_validator.ts +60 -0
  45. package/src/tx_validator/phases_validator.ts +101 -0
  46. package/src/tx_validator/tx_validator_factory.ts +37 -0
  47. package/dest/sequencer/abstract_phase_manager.d.ts +0 -77
  48. package/dest/sequencer/abstract_phase_manager.d.ts.map +0 -1
  49. package/dest/sequencer/abstract_phase_manager.js +0 -307
  50. package/dest/sequencer/app_logic_phase_manager.d.ts +0 -28
  51. package/dest/sequencer/app_logic_phase_manager.d.ts.map +0 -1
  52. package/dest/sequencer/app_logic_phase_manager.js +0 -41
  53. package/dest/sequencer/hints_builder.d.ts +0 -23
  54. package/dest/sequencer/hints_builder.d.ts.map +0 -1
  55. package/dest/sequencer/hints_builder.js +0 -62
  56. package/dest/sequencer/phase_manager_factory.d.ts +0 -18
  57. package/dest/sequencer/phase_manager_factory.d.ts.map +0 -1
  58. package/dest/sequencer/phase_manager_factory.js +0 -56
  59. package/dest/sequencer/public_processor.d.ts +0 -54
  60. package/dest/sequencer/public_processor.d.ts.map +0 -1
  61. package/dest/sequencer/public_processor.js +0 -142
  62. package/dest/sequencer/setup_phase_manager.d.ts +0 -28
  63. package/dest/sequencer/setup_phase_manager.d.ts.map +0 -1
  64. package/dest/sequencer/setup_phase_manager.js +0 -30
  65. package/dest/sequencer/tail_phase_manager.d.ts +0 -29
  66. package/dest/sequencer/tail_phase_manager.d.ts.map +0 -1
  67. package/dest/sequencer/tail_phase_manager.js +0 -52
  68. package/dest/sequencer/teardown_phase_manager.d.ts +0 -28
  69. package/dest/sequencer/teardown_phase_manager.d.ts.map +0 -1
  70. package/dest/sequencer/teardown_phase_manager.js +0 -30
  71. package/dest/sequencer/tx_validator.d.ts +0 -28
  72. package/dest/sequencer/tx_validator.d.ts.map +0 -1
  73. package/dest/sequencer/tx_validator.js +0 -174
  74. package/dest/sequencer/tx_validator_factory.d.ts +0 -12
  75. package/dest/sequencer/tx_validator_factory.d.ts.map +0 -1
  76. package/dest/sequencer/tx_validator_factory.js +0 -17
  77. package/dest/sequencer/utils.d.ts +0 -8
  78. package/dest/sequencer/utils.d.ts.map +0 -1
  79. package/dest/sequencer/utils.js +0 -29
  80. package/dest/simulator/index.d.ts +0 -31
  81. package/dest/simulator/index.d.ts.map +0 -1
  82. package/dest/simulator/index.js +0 -2
  83. package/dest/simulator/public_executor.d.ts +0 -79
  84. package/dest/simulator/public_executor.d.ts.map +0 -1
  85. package/dest/simulator/public_executor.js +0 -198
  86. package/dest/simulator/public_kernel.d.ts +0 -37
  87. package/dest/simulator/public_kernel.d.ts.map +0 -1
  88. package/dest/simulator/public_kernel.js +0 -97
  89. package/src/sequencer/abstract_phase_manager.ts +0 -549
  90. package/src/sequencer/app_logic_phase_manager.ts +0 -62
  91. package/src/sequencer/hints_builder.ts +0 -119
  92. package/src/sequencer/phase_manager_factory.ts +0 -126
  93. package/src/sequencer/public_processor.ts +0 -209
  94. package/src/sequencer/setup_phase_manager.ts +0 -50
  95. package/src/sequencer/tail_phase_manager.ts +0 -111
  96. package/src/sequencer/teardown_phase_manager.ts +0 -50
  97. package/src/sequencer/tx_validator.ts +0 -265
  98. package/src/sequencer/tx_validator_factory.ts +0 -32
  99. package/src/sequencer/utils.ts +0 -31
  100. package/src/simulator/index.ts +0 -36
  101. package/src/simulator/public_executor.ts +0 -267
  102. package/src/simulator/public_kernel.ts +0 -139
@@ -122,7 +122,7 @@ export class L1Publisher implements L2BlockReceiver {
122
122
  // TODO(#4148) Remove this block number check, it's here because we don't currently have proper genesis state on the contract
123
123
  const lastArchive = block.header.lastArchive.root.toBuffer();
124
124
  if (block.number != 1 && !(await this.checkLastArchiveHash(lastArchive))) {
125
- this.log(`Detected different last archive prior to publishing a block, aborting publish...`);
125
+ this.log.info(`Detected different last archive prior to publishing a block, aborting publish...`);
126
126
  return false;
127
127
  }
128
128
 
@@ -131,7 +131,7 @@ export class L1Publisher implements L2BlockReceiver {
131
131
  // Publish block transaction effects
132
132
  while (!this.interrupted) {
133
133
  if (await this.txSender.checkIfTxsAreAvailable(block)) {
134
- this.log(`Transaction effects of a block ${block.number} already published.`);
134
+ this.log.verbose(`Transaction effects of a block ${block.number} already published.`);
135
135
  break;
136
136
  }
137
137
 
@@ -151,14 +151,14 @@ export class L1Publisher implements L2BlockReceiver {
151
151
  // txsEffectsHash from IAvailabilityOracle.TxsPublished event
152
152
  txsEffectsHash = receipt.logs[0].data;
153
153
  } else {
154
- this.log(`Expected 1 log, got ${receipt.logs.length}`);
154
+ this.log.warn(`Expected 1 log, got ${receipt.logs.length}`);
155
155
  }
156
156
 
157
157
  this.log.info(`Block txs effects published, txsEffectsHash: ${txsEffectsHash}`);
158
158
  break;
159
159
  }
160
160
 
161
- this.log(`AvailabilityOracle.publish tx status failed: ${receipt.transactionHash}`);
161
+ this.log.error(`AvailabilityOracle.publish tx status failed: ${receipt.transactionHash}`);
162
162
  await this.sleepOrInterrupted();
163
163
  }
164
164
 
@@ -196,15 +196,15 @@ export class L1Publisher implements L2BlockReceiver {
196
196
 
197
197
  // Check if someone else incremented the block number
198
198
  if (!(await this.checkLastArchiveHash(lastArchive))) {
199
- this.log('Publish failed. Detected different last archive hash.');
199
+ this.log.warn('Publish failed. Detected different last archive hash.');
200
200
  break;
201
201
  }
202
202
 
203
- this.log(`Rollup.process tx status failed: ${receipt.transactionHash}`);
203
+ this.log.error(`Rollup.process tx status failed: ${receipt.transactionHash}`);
204
204
  await this.sleepOrInterrupted();
205
205
  }
206
206
 
207
- this.log('L2 block data syncing interrupted while processing blocks.');
207
+ this.log.verbose('L2 block data syncing interrupted while processing blocks.');
208
208
  return false;
209
209
  }
210
210
 
@@ -233,8 +233,8 @@ export class L1Publisher implements L2BlockReceiver {
233
233
  const fromChain = await this.txSender.getCurrentArchive();
234
234
  const areSame = lastArchive.equals(fromChain);
235
235
  if (!areSame) {
236
- this.log(`CONTRACT ARCHIVE: ${fromChain.toString('hex')}`);
237
- this.log(`NEW BLOCK LAST ARCHIVE: ${lastArchive.toString('hex')}`);
236
+ this.log.debug(`Contract archive: ${fromChain.toString('hex')}`);
237
+ this.log.debug(`New block last archive: ${lastArchive.toString('hex')}`);
238
238
  }
239
239
  return areSame;
240
240
  }
@@ -114,7 +114,7 @@ export class ViemTxSender implements L1PublisherTxSender {
114
114
  };
115
115
  }
116
116
 
117
- this.log(`Receipt not found for tx hash ${txHash}`);
117
+ this.log.debug(`Receipt not found for tx hash ${txHash}`);
118
118
  return undefined;
119
119
  }
120
120
 
@@ -1,5 +1,12 @@
1
- import { type L1ToL2MessageSource, type L2Block, type L2BlockSource, type ProcessedTx, Tx } from '@aztec/circuit-types';
2
- import { type BlockProver, PROVING_STATUS } from '@aztec/circuit-types/interfaces';
1
+ import {
2
+ type L1ToL2MessageSource,
3
+ type L2Block,
4
+ type L2BlockSource,
5
+ type ProcessedTx,
6
+ Tx,
7
+ type TxValidator,
8
+ } from '@aztec/circuit-types';
9
+ import { type AllowedFunction, type BlockProver, PROVING_STATUS } from '@aztec/circuit-types/interfaces';
3
10
  import { type L2BlockBuiltStats } from '@aztec/circuit-types/stats';
4
11
  import { AztecAddress, EthAddress } from '@aztec/circuits.js';
5
12
  import { Fr } from '@aztec/foundation/fields';
@@ -7,14 +14,13 @@ import { createDebugLogger } from '@aztec/foundation/log';
7
14
  import { RunningPromise } from '@aztec/foundation/running-promise';
8
15
  import { Timer, elapsed } from '@aztec/foundation/timer';
9
16
  import { type P2P } from '@aztec/p2p';
17
+ import { type PublicProcessorFactory } from '@aztec/simulator';
10
18
  import { type WorldStateStatus, type WorldStateSynchronizer } from '@aztec/world-state';
11
19
 
12
20
  import { type GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
13
21
  import { type L1Publisher } from '../publisher/l1-publisher.js';
22
+ import { type TxValidatorFactory } from '../tx_validator/tx_validator_factory.js';
14
23
  import { type SequencerConfig } from './config.js';
15
- import { type PublicProcessorFactory } from './public_processor.js';
16
- import { type TxValidator } from './tx_validator.js';
17
- import { type TxValidatorFactory } from './tx_validator_factory.js';
18
24
 
19
25
  /**
20
26
  * Sequencer client
@@ -35,8 +41,8 @@ export class Sequencer {
35
41
  private _feeRecipient = AztecAddress.ZERO;
36
42
  private lastPublishedBlock = 0;
37
43
  private state = SequencerState.STOPPED;
38
- private allowedFeePaymentContractClasses: Fr[] = [];
39
- private allowedFeePaymentContractInstances: AztecAddress[] = [];
44
+ private allowedFunctionsInSetup: AllowedFunction[] = [];
45
+ private allowedFunctionsInTeardown: AllowedFunction[] = [];
40
46
 
41
47
  constructor(
42
48
  private publisher: L1Publisher,
@@ -52,7 +58,7 @@ export class Sequencer {
52
58
  private log = createDebugLogger('aztec:sequencer'),
53
59
  ) {
54
60
  this.updateConfig(config);
55
- this.log(`Initialized sequencer with ${this.minTxsPerBLock}-${this.maxTxsPerBlock} txs per block.`);
61
+ this.log.verbose(`Initialized sequencer with ${this.minTxsPerBLock}-${this.maxTxsPerBlock} txs per block.`);
56
62
  }
57
63
 
58
64
  /**
@@ -75,11 +81,11 @@ export class Sequencer {
75
81
  if (config.feeRecipient) {
76
82
  this._feeRecipient = config.feeRecipient;
77
83
  }
78
- if (config.allowedFeePaymentContractClasses) {
79
- this.allowedFeePaymentContractClasses = config.allowedFeePaymentContractClasses;
84
+ if (config.allowedFunctionsInSetup) {
85
+ this.allowedFunctionsInSetup = config.allowedFunctionsInSetup;
80
86
  }
81
- if (config.allowedFeePaymentContractInstances) {
82
- this.allowedFeePaymentContractInstances = config.allowedFeePaymentContractInstances;
87
+ if (config.allowedFunctionsInTeardown) {
88
+ this.allowedFunctionsInTeardown = config.allowedFunctionsInTeardown;
83
89
  }
84
90
  }
85
91
 
@@ -92,25 +98,25 @@ export class Sequencer {
92
98
  this.runningPromise = new RunningPromise(this.work.bind(this), this.pollingIntervalMs);
93
99
  this.runningPromise.start();
94
100
  this.state = SequencerState.IDLE;
95
- this.log('Sequencer started');
101
+ this.log.info('Sequencer started');
96
102
  }
97
103
 
98
104
  /**
99
105
  * Stops the sequencer from processing txs and moves to STOPPED state.
100
106
  */
101
107
  public async stop(): Promise<void> {
102
- this.log(`Stopping sequencer`);
108
+ this.log.debug(`Stopping sequencer`);
103
109
  await this.runningPromise?.stop();
104
110
  this.publisher.interrupt();
105
111
  this.state = SequencerState.STOPPED;
106
- this.log('Stopped sequencer');
112
+ this.log.info('Stopped sequencer');
107
113
  }
108
114
 
109
115
  /**
110
116
  * Starts a previously stopped sequencer.
111
117
  */
112
118
  public restart() {
113
- this.log('Restarting sequencer');
119
+ this.log.info('Restarting sequencer');
114
120
  this.publisher.restart();
115
121
  this.runningPromise!.start();
116
122
  this.state = SequencerState.IDLE;
@@ -137,7 +143,7 @@ export class Sequencer {
137
143
  // Update state when the previous block has been synced
138
144
  const prevBlockSynced = await this.isBlockSynced();
139
145
  if (prevBlockSynced && this.state === SequencerState.PUBLISHING_BLOCK) {
140
- this.log(`Block has been synced`);
146
+ this.log.debug(`Block has been synced`);
141
147
  this.state = SequencerState.IDLE;
142
148
  }
143
149
 
@@ -178,14 +184,15 @@ export class Sequencer {
178
184
  this._feeRecipient,
179
185
  );
180
186
 
181
- const txValidator = this.txValidatorFactory.buildTxValidator(
182
- newGlobalVariables,
183
- this.allowedFeePaymentContractClasses,
184
- this.allowedFeePaymentContractInstances,
185
- );
186
-
187
187
  // TODO: It should be responsibility of the P2P layer to validate txs before passing them on here
188
- const validTxs = await this.takeValidTxs(pendingTxs, txValidator);
188
+ const validTxs = await this.takeValidTxs(
189
+ pendingTxs,
190
+ this.txValidatorFactory.validatorForNewTxs(
191
+ newGlobalVariables,
192
+ this.allowedFunctionsInSetup,
193
+ this.allowedFunctionsInTeardown,
194
+ ),
195
+ );
189
196
  if (validTxs.length < this.minTxsPerBLock) {
190
197
  return;
191
198
  }
@@ -194,9 +201,9 @@ export class Sequencer {
194
201
  this.state = SequencerState.CREATING_BLOCK;
195
202
 
196
203
  // Get l1 to l2 messages from the contract
197
- this.log('Requesting L1 to L2 messages from contract');
204
+ this.log.debug('Requesting L1 to L2 messages from contract');
198
205
  const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(BigInt(newBlockNumber));
199
- this.log(`Retrieved ${l1ToL2Messages.length} L1 to L2 messages for block ${newBlockNumber}`);
206
+ this.log.verbose(`Retrieved ${l1ToL2Messages.length} L1 to L2 messages for block ${newBlockNumber}`);
200
207
 
201
208
  // We create a fresh processor each time to reset any cached state (eg storage writes)
202
209
  const processor = await this.publicProcessorFactory.create(historicalHeader, newGlobalVariables);
@@ -213,16 +220,16 @@ export class Sequencer {
213
220
  const blockTicket = await this.prover.startNewBlock(blockSize, newGlobalVariables, l1ToL2Messages, emptyTx);
214
221
 
215
222
  const [publicProcessorDuration, [processedTxs, failedTxs]] = await elapsed(() =>
216
- processor.process(validTxs, blockSize, this.prover, txValidator),
223
+ processor.process(validTxs, blockSize, this.prover, this.txValidatorFactory.validatorForProcessedTxs()),
217
224
  );
218
225
  if (failedTxs.length > 0) {
219
226
  const failedTxData = failedTxs.map(fail => fail.tx);
220
- this.log(`Dropping failed txs ${Tx.getHashes(failedTxData).join(', ')}`);
227
+ this.log.debug(`Dropping failed txs ${Tx.getHashes(failedTxData).join(', ')}`);
221
228
  await this.p2pClient.deleteTxs(Tx.getHashes(failedTxData));
222
229
  }
223
230
 
224
231
  if (processedTxs.length === 0) {
225
- this.log('No txs processed correctly to build block. Exiting');
232
+ this.log.verbose('No txs processed correctly to build block. Exiting');
226
233
  this.prover.cancelBlock();
227
234
  return;
228
235
  }
@@ -248,7 +255,7 @@ export class Sequencer {
248
255
 
249
256
  await assertBlockHeight();
250
257
 
251
- this.log(`Assembled block ${block.number}`, {
258
+ this.log.verbose(`Assembled block ${block.number}`, {
252
259
  eventName: 'l2-block-built',
253
260
  duration: workTimer.ms(),
254
261
  publicProcessDuration: publicProcessorDuration,
@@ -275,17 +282,16 @@ export class Sequencer {
275
282
  this.state = SequencerState.PUBLISHING_BLOCK;
276
283
  const publishedL2Block = await this.publisher.processL2Block(block);
277
284
  if (publishedL2Block) {
278
- this.log(`Successfully published block ${block.number}`);
279
285
  this.lastPublishedBlock = block.number;
280
286
  } else {
281
287
  throw new Error(`Failed to publish block`);
282
288
  }
283
289
  }
284
290
 
285
- protected async takeValidTxs<T extends Tx | ProcessedTx>(txs: T[], validator: TxValidator): Promise<T[]> {
291
+ protected async takeValidTxs<T extends Tx | ProcessedTx>(txs: T[], validator: TxValidator<T>): Promise<T[]> {
286
292
  const [valid, invalid] = await validator.validateTxs(txs);
287
293
  if (invalid.length > 0) {
288
- this.log(`Dropping invalid txs from the p2p pool ${Tx.getHashes(invalid).join(', ')}`);
294
+ this.log.debug(`Dropping invalid txs from the p2p pool ${Tx.getHashes(invalid).join(', ')}`);
289
295
  await this.p2pClient.deleteTxs(Tx.getHashes(invalid));
290
296
  }
291
297
 
@@ -0,0 +1,24 @@
1
+ import { type ProcessedTx, type Tx, type TxValidator } from '@aztec/circuit-types';
2
+
3
+ export class AggregateTxValidator<T extends Tx | ProcessedTx> implements TxValidator<T> {
4
+ #validators: TxValidator<T>[];
5
+ constructor(...validators: TxValidator<T>[]) {
6
+ if (validators.length === 0) {
7
+ throw new Error('At least one validator must be provided');
8
+ }
9
+
10
+ this.#validators = validators;
11
+ }
12
+
13
+ async validateTxs(txs: T[]): Promise<[validTxs: T[], invalidTxs: T[]]> {
14
+ const invalidTxs: T[] = [];
15
+ let txPool = txs;
16
+ for (const validator of this.#validators) {
17
+ const [valid, invalid] = await validator.validateTxs(txPool);
18
+ invalidTxs.push(...invalid);
19
+ txPool = valid;
20
+ }
21
+
22
+ return [txPool, invalidTxs];
23
+ }
24
+ }
@@ -0,0 +1,65 @@
1
+ import { type AnyTx, Tx, type TxValidator } from '@aztec/circuit-types';
2
+ import { Fr } from '@aztec/circuits.js';
3
+ import { createDebugLogger } from '@aztec/foundation/log';
4
+
5
+ export interface NullifierSource {
6
+ getNullifierIndex: (nullifier: Fr) => Promise<bigint | undefined>;
7
+ }
8
+
9
+ export class DoubleSpendTxValidator<T extends AnyTx> implements TxValidator<T> {
10
+ #log = createDebugLogger('aztec:sequencer:tx_validator:tx_double_spend');
11
+ #nullifierSource: NullifierSource;
12
+
13
+ constructor(nullifierSource: NullifierSource) {
14
+ this.#nullifierSource = nullifierSource;
15
+ }
16
+
17
+ async validateTxs(txs: T[]): Promise<[validTxs: T[], invalidTxs: T[]]> {
18
+ const validTxs: T[] = [];
19
+ const invalidTxs: T[] = [];
20
+ const thisBlockNullifiers = new Set<bigint>();
21
+
22
+ for (const tx of txs) {
23
+ if (!(await this.#uniqueNullifiers(tx, thisBlockNullifiers))) {
24
+ invalidTxs.push(tx);
25
+ continue;
26
+ }
27
+
28
+ validTxs.push(tx);
29
+ }
30
+
31
+ return [validTxs, invalidTxs];
32
+ }
33
+
34
+ async #uniqueNullifiers(tx: AnyTx, thisBlockNullifiers: Set<bigint>): Promise<boolean> {
35
+ const newNullifiers = tx.data.getNonEmptyNullifiers().map(x => x.toBigInt());
36
+
37
+ // Ditch this tx if it has repeated nullifiers
38
+ const uniqueNullifiers = new Set(newNullifiers);
39
+ if (uniqueNullifiers.size !== newNullifiers.length) {
40
+ this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for emitting duplicate nullifiers`);
41
+ return false;
42
+ }
43
+
44
+ for (const nullifier of newNullifiers) {
45
+ if (thisBlockNullifiers.has(nullifier)) {
46
+ this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for repeating a nullifier in the same block`);
47
+ return false;
48
+ }
49
+
50
+ thisBlockNullifiers.add(nullifier);
51
+ }
52
+
53
+ const nullifierIndexes = await Promise.all(
54
+ newNullifiers.map(n => this.#nullifierSource.getNullifierIndex(new Fr(n))),
55
+ );
56
+
57
+ const hasDuplicates = nullifierIndexes.some(index => index !== undefined);
58
+ if (hasDuplicates) {
59
+ this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for repeating nullifiers present in state trees`);
60
+ return false;
61
+ }
62
+
63
+ return true;
64
+ }
65
+ }
@@ -0,0 +1,81 @@
1
+ import { Tx, type TxValidator } from '@aztec/circuit-types';
2
+ import { type AztecAddress, Fr } from '@aztec/circuits.js';
3
+ import { pedersenHash } from '@aztec/foundation/crypto';
4
+ import { createDebugLogger } from '@aztec/foundation/log';
5
+ import { GasTokenContract } from '@aztec/noir-contracts.js';
6
+ import { AbstractPhaseManager, PublicKernelPhase } from '@aztec/simulator';
7
+
8
+ /** Provides a view into public contract state */
9
+ export interface PublicStateSource {
10
+ storageRead: (contractAddress: AztecAddress, slot: Fr) => Promise<Fr>;
11
+ }
12
+
13
+ export class GasTxValidator implements TxValidator<Tx> {
14
+ #log = createDebugLogger('aztec:sequencer:tx_validator:tx_gas');
15
+ #publicDataSource: PublicStateSource;
16
+ #gasTokenAddress: AztecAddress;
17
+ #requireFees: boolean;
18
+
19
+ constructor(publicDataSource: PublicStateSource, gasTokenAddress: AztecAddress, requireFees = false) {
20
+ this.#publicDataSource = publicDataSource;
21
+ this.#gasTokenAddress = gasTokenAddress;
22
+ this.#requireFees = requireFees;
23
+ }
24
+
25
+ async validateTxs(txs: Tx[]): Promise<[validTxs: Tx[], invalidTxs: Tx[]]> {
26
+ const validTxs: Tx[] = [];
27
+ const invalidTxs: Tx[] = [];
28
+
29
+ for (const tx of txs) {
30
+ if (await this.#validateTxFee(tx)) {
31
+ validTxs.push(tx);
32
+ } else {
33
+ invalidTxs.push(tx);
34
+ }
35
+ }
36
+
37
+ return [validTxs, invalidTxs];
38
+ }
39
+
40
+ async #validateTxFee(tx: Tx): Promise<boolean> {
41
+ const { [PublicKernelPhase.TEARDOWN]: teardownFns } = AbstractPhaseManager.extractEnqueuedPublicCallsByPhase(
42
+ tx.data,
43
+ tx.enqueuedPublicFunctionCalls,
44
+ );
45
+
46
+ if (teardownFns.length === 0) {
47
+ if (this.#requireFees) {
48
+ this.#log.warn(
49
+ `Rejecting tx ${Tx.getHash(tx)} because it should pay for gas but has no enqueued teardown functions`,
50
+ );
51
+ return false;
52
+ } else {
53
+ this.#log.debug(`Tx ${Tx.getHash(tx)} does not pay fees. Skipping balance check.`);
54
+ return true;
55
+ }
56
+ }
57
+
58
+ if (teardownFns.length > 1) {
59
+ this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} because it has multiple teardown functions`);
60
+ return false;
61
+ }
62
+
63
+ // check that the caller of the teardown function has enough balance to pay for tx costs
64
+ const teardownFn = teardownFns[0];
65
+ const slot = pedersenHash([GasTokenContract.storage.balances.slot, teardownFn.callContext.msgSender]);
66
+ const gasBalance = await this.#publicDataSource.storageRead(this.#gasTokenAddress, slot);
67
+
68
+ // TODO(#5004) calculate fee needed based on tx limits and gas prices
69
+ const gasAmountNeeded = new Fr(1);
70
+ if (gasBalance.lt(gasAmountNeeded)) {
71
+ this.#log.warn(
72
+ `Rejecting tx ${Tx.getHash(
73
+ tx,
74
+ )} because it should pay for gas but has insufficient balance ${gasBalance.toShortString()} < ${gasAmountNeeded.toShortString()}`,
75
+ );
76
+ return false;
77
+ }
78
+
79
+ return true;
80
+ }
81
+ }
@@ -0,0 +1,60 @@
1
+ import { type AnyTx, Tx, type TxValidator } from '@aztec/circuit-types';
2
+ import { type GlobalVariables } from '@aztec/circuits.js';
3
+ import { createDebugLogger } from '@aztec/foundation/log';
4
+
5
+ export class MetadataTxValidator<T extends AnyTx> implements TxValidator<T> {
6
+ #log = createDebugLogger('aztec:sequencer:tx_validator:tx_metadata');
7
+ #globalVariables: GlobalVariables;
8
+
9
+ constructor(globalVariables: GlobalVariables) {
10
+ this.#globalVariables = globalVariables;
11
+ }
12
+
13
+ validateTxs(txs: T[]): Promise<[validTxs: T[], invalidTxs: T[]]> {
14
+ const validTxs: T[] = [];
15
+ const invalidTxs: T[] = [];
16
+ for (const tx of txs) {
17
+ if (!this.#hasCorrectChainId(tx)) {
18
+ invalidTxs.push(tx);
19
+ continue;
20
+ }
21
+
22
+ if (!this.#isValidForBlockNumber(tx)) {
23
+ invalidTxs.push(tx);
24
+ continue;
25
+ }
26
+
27
+ validTxs.push(tx);
28
+ }
29
+
30
+ return Promise.resolve([validTxs, invalidTxs]);
31
+ }
32
+
33
+ #hasCorrectChainId(tx: T): boolean {
34
+ if (!tx.data.constants.txContext.chainId.equals(this.#globalVariables.chainId)) {
35
+ this.#log.warn(
36
+ `Rejecting tx ${Tx.getHash(
37
+ tx,
38
+ )} because of incorrect chain ${tx.data.constants.txContext.chainId.toNumber()} != ${this.#globalVariables.chainId.toNumber()}`,
39
+ );
40
+ return false;
41
+ } else {
42
+ return true;
43
+ }
44
+ }
45
+
46
+ #isValidForBlockNumber(tx: T): boolean {
47
+ const target =
48
+ tx instanceof Tx
49
+ ? tx.data.forRollup?.rollupValidationRequests || tx.data.forPublic!.validationRequests.forRollup
50
+ : tx.data.rollupValidationRequests;
51
+ const maxBlockNumber = target.maxBlockNumber;
52
+
53
+ if (maxBlockNumber.isSome && maxBlockNumber.value < this.#globalVariables.blockNumber) {
54
+ this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for low max block number`);
55
+ return false;
56
+ } else {
57
+ return true;
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,101 @@
1
+ import { type AllowedFunction, Tx, type TxValidator } from '@aztec/circuit-types';
2
+ import { type PublicCallRequest } from '@aztec/circuits.js';
3
+ import { createDebugLogger } from '@aztec/foundation/log';
4
+ import { AbstractPhaseManager, PublicKernelPhase } from '@aztec/simulator';
5
+ import { type ContractDataSource } from '@aztec/types/contracts';
6
+
7
+ export class PhasesTxValidator implements TxValidator<Tx> {
8
+ #log = createDebugLogger('aztec:sequencer:tx_validator:tx_phases');
9
+
10
+ constructor(
11
+ private contractDataSource: ContractDataSource,
12
+ private setupAllowList: AllowedFunction[],
13
+ private teardownAllowList: AllowedFunction[],
14
+ ) {}
15
+
16
+ async validateTxs(txs: Tx[]): Promise<[validTxs: Tx[], invalidTxs: Tx[]]> {
17
+ const validTxs: Tx[] = [];
18
+ const invalidTxs: Tx[] = [];
19
+
20
+ for (const tx of txs) {
21
+ if (await this.#validateTx(tx)) {
22
+ validTxs.push(tx);
23
+ } else {
24
+ invalidTxs.push(tx);
25
+ }
26
+ }
27
+
28
+ return Promise.resolve([validTxs, invalidTxs]);
29
+ }
30
+
31
+ async #validateTx(tx: Tx): Promise<boolean> {
32
+ if (!tx.data.forPublic) {
33
+ this.#log.debug(`Tx ${Tx.getHash(tx)} does not contain enqueued public functions. Skipping phases validation.`);
34
+ return true;
35
+ }
36
+
37
+ const { [PublicKernelPhase.SETUP]: setupFns, [PublicKernelPhase.TEARDOWN]: teardownFns } =
38
+ AbstractPhaseManager.extractEnqueuedPublicCallsByPhase(tx.data, tx.enqueuedPublicFunctionCalls);
39
+
40
+ for (const setupFn of setupFns) {
41
+ if (!(await this.isOnAllowList(setupFn, this.setupAllowList))) {
42
+ this.#log.warn(
43
+ `Rejecting tx ${Tx.getHash(tx)} because it calls setup function not on allow list: ${
44
+ setupFn.contractAddress
45
+ }:${setupFn.functionData.selector}`,
46
+ );
47
+
48
+ return false;
49
+ }
50
+ }
51
+
52
+ for (const teardownFn of teardownFns) {
53
+ if (!(await this.isOnAllowList(teardownFn, this.teardownAllowList))) {
54
+ this.#log.warn(
55
+ `Rejecting tx ${Tx.getHash(tx)} because it calls teardown function not on allowlist: ${
56
+ teardownFn.contractAddress
57
+ }:${teardownFn.functionData.selector}`,
58
+ );
59
+
60
+ return false;
61
+ }
62
+ }
63
+
64
+ return true;
65
+ }
66
+
67
+ async isOnAllowList(publicCall: PublicCallRequest, allowList: AllowedFunction[]): Promise<boolean> {
68
+ const {
69
+ contractAddress,
70
+ functionData: { selector },
71
+ } = publicCall;
72
+
73
+ // do these checks first since they don't require the contract class
74
+ for (const entry of allowList) {
75
+ if (!('address' in entry)) {
76
+ continue;
77
+ }
78
+
79
+ if (contractAddress.equals(entry.address) && entry.selector.equals(selector)) {
80
+ return true;
81
+ }
82
+ }
83
+
84
+ const contractClass = await this.contractDataSource.getContract(contractAddress);
85
+ if (!contractClass) {
86
+ throw new Error(`Contract not found: ${publicCall.contractAddress.toString()}`);
87
+ }
88
+
89
+ for (const entry of allowList) {
90
+ if (!('classId' in entry)) {
91
+ continue;
92
+ }
93
+
94
+ if (contractClass.contractClassId.equals(entry.classId) && entry.selector.equals(selector)) {
95
+ return true;
96
+ }
97
+ }
98
+
99
+ return false;
100
+ }
101
+ }
@@ -0,0 +1,37 @@
1
+ import { type AllowedFunction, type ProcessedTx, type Tx, type TxValidator } from '@aztec/circuit-types';
2
+ import { type EthAddress, type GlobalVariables } from '@aztec/circuits.js';
3
+ import { getCanonicalGasTokenAddress } from '@aztec/protocol-contracts/gas-token';
4
+ import { WorldStateDB, WorldStatePublicDB } from '@aztec/simulator';
5
+ import { type ContractDataSource } from '@aztec/types/contracts';
6
+ import { type MerkleTreeOperations } from '@aztec/world-state';
7
+
8
+ import { AggregateTxValidator } from './aggregate_tx_validator.js';
9
+ import { DoubleSpendTxValidator } from './double_spend_validator.js';
10
+ import { GasTxValidator } from './gas_validator.js';
11
+ import { MetadataTxValidator } from './metadata_validator.js';
12
+ import { PhasesTxValidator } from './phases_validator.js';
13
+
14
+ export class TxValidatorFactory {
15
+ constructor(
16
+ private merkleTreeDb: MerkleTreeOperations,
17
+ private contractDataSource: ContractDataSource,
18
+ private gasPortalAddress: EthAddress,
19
+ ) {}
20
+
21
+ validatorForNewTxs(
22
+ globalVariables: GlobalVariables,
23
+ setupAllowList: AllowedFunction[],
24
+ teardownAllowList: AllowedFunction[],
25
+ ): TxValidator<Tx> {
26
+ return new AggregateTxValidator(
27
+ new MetadataTxValidator(globalVariables),
28
+ new DoubleSpendTxValidator(new WorldStateDB(this.merkleTreeDb)),
29
+ new PhasesTxValidator(this.contractDataSource, setupAllowList, teardownAllowList),
30
+ new GasTxValidator(new WorldStatePublicDB(this.merkleTreeDb), getCanonicalGasTokenAddress(this.gasPortalAddress)),
31
+ );
32
+ }
33
+
34
+ validatorForProcessedTxs(): TxValidator<ProcessedTx> {
35
+ return new DoubleSpendTxValidator(new WorldStateDB(this.merkleTreeDb));
36
+ }
37
+ }