@aztec/sequencer-client 0.67.0 → 0.67.1-devnet

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.
@@ -38,6 +38,7 @@ import { type GlobalVariableBuilder } from '../global_variable_builder/global_bu
38
38
  import { type L1Publisher } from '../publisher/l1-publisher.js';
39
39
  import { prettyLogViemErrorMsg } from '../publisher/utils.js';
40
40
  import { type TxValidatorFactory } from '../tx_validator/tx_validator_factory.js';
41
+ import { getDefaultAllowedSetupFunctions } from './allowed.js';
41
42
  import { type SequencerConfig } from './config.js';
42
43
  import { SequencerMetrics } from './metrics.js';
43
44
  import { SequencerState, getSecondsIntoSlot, orderAttestations } from './utils.js';
@@ -76,13 +77,11 @@ export class Sequencer {
76
77
  private pollingIntervalMs: number = 1000;
77
78
  private maxTxsPerBlock = 32;
78
79
  private minTxsPerBLock = 1;
79
- private minSecondsBetweenBlocks = 0;
80
- private maxSecondsBetweenBlocks = 0;
81
80
  // TODO: zero values should not be allowed for the following 2 values in PROD
82
81
  private _coinbase = EthAddress.ZERO;
83
82
  private _feeRecipient = AztecAddress.ZERO;
84
83
  private state = SequencerState.STOPPED;
85
- private allowedInSetup: AllowedElement[] = [];
84
+ private allowedInSetup: AllowedElement[] = getDefaultAllowedSetupFunctions();
86
85
  private maxBlockSizeInBytes: number = 1024 * 1024;
87
86
  private metrics: SequencerMetrics;
88
87
  private isFlushing: boolean = false;
@@ -127,10 +126,7 @@ export class Sequencer {
127
126
  * @param config - New parameters.
128
127
  */
129
128
  public updateConfig(config: SequencerConfig) {
130
- this.log.info(
131
- `Sequencer config set`,
132
- omit(pickFromSchema(this.config, SequencerConfigSchema), 'allowedInSetup', 'allowedInTeardown'),
133
- );
129
+ this.log.info(`Sequencer config set`, omit(pickFromSchema(config, SequencerConfigSchema), 'allowedInSetup'));
134
130
 
135
131
  if (config.transactionPollingIntervalMS !== undefined) {
136
132
  this.pollingIntervalMs = config.transactionPollingIntervalMS;
@@ -141,12 +137,6 @@ export class Sequencer {
141
137
  if (config.minTxsPerBlock !== undefined) {
142
138
  this.minTxsPerBLock = config.minTxsPerBlock;
143
139
  }
144
- if (config.minSecondsBetweenBlocks !== undefined) {
145
- this.minSecondsBetweenBlocks = config.minSecondsBetweenBlocks;
146
- }
147
- if (config.maxSecondsBetweenBlocks !== undefined) {
148
- this.maxSecondsBetweenBlocks = config.maxSecondsBetweenBlocks;
149
- }
150
140
  if (config.coinbase) {
151
141
  this._coinbase = config.coinbase;
152
142
  }
@@ -192,7 +182,7 @@ export class Sequencer {
192
182
  * Starts the sequencer and moves to IDLE state.
193
183
  */
194
184
  public start() {
195
- this.runningPromise = new RunningPromise(this.work.bind(this), this.pollingIntervalMs);
185
+ this.runningPromise = new RunningPromise(this.work.bind(this), this.log, this.pollingIntervalMs);
196
186
  this.setState(SequencerState.IDLE, 0n, true /** force */);
197
187
  this.runningPromise.start();
198
188
  this.log.info(`Sequencer started with address ${this.publisher.getSenderAddress().toString()}`);
@@ -339,6 +329,7 @@ export class Sequencer {
339
329
  this.setState(SequencerState.IDLE, 0n);
340
330
  }
341
331
 
332
+ @trackSpan('Sequencer.work')
342
333
  protected async work() {
343
334
  try {
344
335
  await this.doRealWork();
@@ -354,15 +345,6 @@ export class Sequencer {
354
345
  }
355
346
  }
356
347
 
357
- /** Whether to skip the check of min txs per block if more than maxSecondsBetweenBlocks has passed since the previous block. */
358
- private skipMinTxsPerBlockCheck(historicalHeader: BlockHeader | undefined): boolean {
359
- const lastBlockTime = historicalHeader?.globalVariables.timestamp.toNumber() || 0;
360
- const currentTime = Math.floor(Date.now() / 1000);
361
- const elapsed = currentTime - lastBlockTime;
362
-
363
- return this.maxSecondsBetweenBlocks > 0 && elapsed >= this.maxSecondsBetweenBlocks;
364
- }
365
-
366
348
  async mayProposeBlock(tipArchive: Buffer, proposalBlockNumber: bigint): Promise<bigint> {
367
349
  // This checks that we can propose, and gives us the slot that we are to propose for
368
350
  try {
@@ -445,53 +427,29 @@ export class Sequencer {
445
427
  `Last block mined at ${lastBlockTime} current time is ${currentTime} (elapsed ${elapsedSinceLastBlock})`,
446
428
  );
447
429
 
448
- // If we haven't hit the maxSecondsBetweenBlocks, we need to have at least minTxsPerBLock txs.
449
- // Do not go forward with new block if not enough time has passed since last block
450
- if (this.minSecondsBetweenBlocks > 0 && elapsedSinceLastBlock < this.minSecondsBetweenBlocks) {
430
+ // We need to have at least minTxsPerBLock txs.
431
+ if (args.pendingTxsCount != undefined && args.pendingTxsCount < this.minTxsPerBLock) {
451
432
  this.log.verbose(
452
- `Not creating block because not enough time ${this.minSecondsBetweenBlocks} has passed since last block`,
433
+ `Not creating block because not enough txs in the pool (got ${args.pendingTxsCount} min ${this.minTxsPerBLock})`,
453
434
  );
454
435
  return false;
455
436
  }
456
437
 
457
- const skipCheck = this.skipMinTxsPerBlockCheck(historicalHeader);
458
-
459
- // If we haven't hit the maxSecondsBetweenBlocks, we need to have at least minTxsPerBLock txs.
460
- if (args.pendingTxsCount != undefined) {
461
- if (args.pendingTxsCount < this.minTxsPerBLock) {
462
- if (skipCheck) {
463
- this.log.debug(
464
- `Creating block with only ${args.pendingTxsCount} txs as more than ${this.maxSecondsBetweenBlocks}s have passed since last block`,
465
- );
466
- } else {
467
- this.log.verbose(
468
- `Not creating block because not enough txs in the pool (got ${args.pendingTxsCount} min ${this.minTxsPerBLock})`,
469
- );
470
- return false;
471
- }
472
- }
473
- }
474
-
475
438
  // Bail if we don't have enough valid txs
476
- if (args.validTxsCount != undefined) {
477
- // Bail if we don't have enough valid txs
478
- if (!skipCheck && args.validTxsCount < this.minTxsPerBLock) {
479
- this.log.verbose(
480
- `Not creating block because not enough valid txs loaded from the pool (got ${args.validTxsCount} min ${this.minTxsPerBLock})`,
481
- );
482
- return false;
483
- }
439
+ if (args.validTxsCount != undefined && args.validTxsCount < this.minTxsPerBLock) {
440
+ this.log.verbose(
441
+ `Not creating block because not enough valid txs loaded from the pool (got ${args.validTxsCount} min ${this.minTxsPerBLock})`,
442
+ );
443
+ return false;
484
444
  }
485
445
 
486
446
  // TODO: This check should be processedTxs.length < this.minTxsPerBLock, so we don't publish a block with
487
447
  // less txs than the minimum. But that'd cause the entire block to be aborted and retried. Instead, we should
488
448
  // go back to the p2p pool and load more txs until we hit our minTxsPerBLock target. Only if there are no txs
489
449
  // we should bail.
490
- if (args.processedTxsCount != undefined) {
491
- if (args.processedTxsCount === 0 && !skipCheck && this.minTxsPerBLock > 0) {
492
- this.log.verbose('No txs processed correctly to build block.');
493
- return false;
494
- }
450
+ if (args.processedTxsCount === 0 && this.minTxsPerBLock > 0) {
451
+ this.log.verbose('No txs processed correctly to build block.');
452
+ return false;
495
453
  }
496
454
 
497
455
  return true;
@@ -544,21 +502,17 @@ export class Sequencer {
544
502
  const processor = this.publicProcessorFactory.create(publicProcessorFork, historicalHeader, newGlobalVariables);
545
503
  const blockBuildingTimer = new Timer();
546
504
  const blockBuilder = this.blockBuilderFactory.create(orchestratorFork);
547
- await blockBuilder.startNewBlock(blockSize, newGlobalVariables, l1ToL2Messages);
505
+ await blockBuilder.startNewBlock(newGlobalVariables, l1ToL2Messages);
548
506
 
549
507
  const [publicProcessorDuration, [processedTxs, failedTxs]] = await elapsed(() =>
550
- processor.process(
551
- validTxs,
552
- blockSize,
553
- blockBuilder,
554
- this.txValidatorFactory.validatorForProcessedTxs(publicProcessorFork),
555
- ),
508
+ processor.process(validTxs, blockSize, this.txValidatorFactory.validatorForProcessedTxs(publicProcessorFork)),
556
509
  );
557
510
  if (failedTxs.length > 0) {
558
511
  const failedTxData = failedTxs.map(fail => fail.tx);
559
512
  this.log.verbose(`Dropping failed txs ${Tx.getHashes(failedTxData).join(', ')}`);
560
513
  await this.p2pClient.deleteTxs(Tx.getHashes(failedTxData));
561
514
  }
515
+ await blockBuilder.addTxs(processedTxs);
562
516
 
563
517
  await interrupt?.(processedTxs);
564
518
 
@@ -649,7 +603,6 @@ export class Sequencer {
649
603
  const blockHash = block.hash();
650
604
  const txHashes = validTxs.map(tx => tx.getTxHash());
651
605
  this.log.info(`Built block ${block.number} with hash ${blockHash}`, {
652
- txEffectsHash: block.header.contentCommitment.txsEffectsHash.toString('hex'),
653
606
  blockHash,
654
607
  globalVariables: block.header.globalVariables.toInspect(),
655
608
  txHashes,
@@ -1,5 +1,5 @@
1
1
  import { type Tx, TxExecutionPhase, type TxValidator } from '@aztec/circuit-types';
2
- import { type AztecAddress, type Fr, FunctionSelector } from '@aztec/circuits.js';
2
+ import { type AztecAddress, type Fr, FunctionSelector, type GasFees } from '@aztec/circuits.js';
3
3
  import { createLogger } from '@aztec/foundation/log';
4
4
  import { computeFeePayerBalanceStorageSlot, getExecutionRequestsByPhase } from '@aztec/simulator';
5
5
 
@@ -12,36 +12,62 @@ export class GasTxValidator implements TxValidator<Tx> {
12
12
  #log = createLogger('sequencer:tx_validator:tx_gas');
13
13
  #publicDataSource: PublicStateSource;
14
14
  #feeJuiceAddress: AztecAddress;
15
+ #enforceFees: boolean;
16
+ #gasFees: GasFees;
15
17
 
16
- constructor(publicDataSource: PublicStateSource, feeJuiceAddress: AztecAddress, public enforceFees: boolean) {
18
+ constructor(
19
+ publicDataSource: PublicStateSource,
20
+ feeJuiceAddress: AztecAddress,
21
+ enforceFees: boolean,
22
+ gasFees: GasFees,
23
+ ) {
17
24
  this.#publicDataSource = publicDataSource;
18
25
  this.#feeJuiceAddress = feeJuiceAddress;
26
+ this.#enforceFees = enforceFees;
27
+ this.#gasFees = gasFees;
19
28
  }
20
29
 
21
- async validateTxs(txs: Tx[]): Promise<[validTxs: Tx[], invalidTxs: Tx[]]> {
30
+ async validateTxs(txs: Tx[]): Promise<[validTxs: Tx[], invalidTxs: Tx[], skippedTxs: Tx[]]> {
22
31
  const validTxs: Tx[] = [];
23
32
  const invalidTxs: Tx[] = [];
33
+ const skippedTxs: Tx[] = [];
24
34
 
25
35
  for (const tx of txs) {
26
- if (await this.#validateTxFee(tx)) {
36
+ if (this.#shouldSkip(tx)) {
37
+ skippedTxs.push(tx);
38
+ } else if (await this.#validateTxFee(tx)) {
27
39
  validTxs.push(tx);
28
40
  } else {
29
41
  invalidTxs.push(tx);
30
42
  }
31
43
  }
32
44
 
33
- return [validTxs, invalidTxs];
45
+ return [validTxs, invalidTxs, skippedTxs];
34
46
  }
35
47
 
36
48
  validateTx(tx: Tx): Promise<boolean> {
37
49
  return this.#validateTxFee(tx);
38
50
  }
39
51
 
52
+ #shouldSkip(tx: Tx): boolean {
53
+ const gasSettings = tx.data.constants.txContext.gasSettings;
54
+
55
+ // Skip the tx if its max fees are not enough for the current block's gas fees.
56
+ const maxFeesPerGas = gasSettings.maxFeesPerGas;
57
+ const notEnoughMaxFees =
58
+ maxFeesPerGas.feePerDaGas.lt(this.#gasFees.feePerDaGas) ||
59
+ maxFeesPerGas.feePerL2Gas.lt(this.#gasFees.feePerL2Gas);
60
+ if (notEnoughMaxFees) {
61
+ this.#log.warn(`Skipping transaction ${tx.getTxHash()} due to insufficient fee per gas`);
62
+ }
63
+ return notEnoughMaxFees;
64
+ }
65
+
40
66
  async #validateTxFee(tx: Tx): Promise<boolean> {
41
67
  const feePayer = tx.data.feePayer;
42
68
  // TODO(@spalladino) Eventually remove the is_zero condition as we should always charge fees to every tx
43
69
  if (feePayer.isZero()) {
44
- if (this.enforceFees) {
70
+ if (this.#enforceFees) {
45
71
  this.#log.warn(`Rejecting transaction ${tx.getTxHash()} due to missing fee payer`);
46
72
  } else {
47
73
  return true;
@@ -29,7 +29,10 @@ export class TxValidatorFactory {
29
29
  private enforceFees: boolean,
30
30
  ) {
31
31
  this.nullifierSource = {
32
- getNullifierIndex: nullifier => this.committedDb.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer()),
32
+ getNullifierIndices: nullifiers =>
33
+ this.committedDb
34
+ .findLeafIndices(MerkleTreeId.NULLIFIER_TREE, nullifiers)
35
+ .then(x => x.filter(index => index !== undefined) as bigint[]),
33
36
  };
34
37
 
35
38
  this.publicStateSource = {
@@ -45,13 +48,18 @@ export class TxValidatorFactory {
45
48
  new MetadataTxValidator(globalVariables.chainId, globalVariables.blockNumber),
46
49
  new DoubleSpendTxValidator(this.nullifierSource),
47
50
  new PhasesTxValidator(this.contractDataSource, setupAllowList),
48
- new GasTxValidator(this.publicStateSource, ProtocolContractAddress.FeeJuice, this.enforceFees),
51
+ new GasTxValidator(
52
+ this.publicStateSource,
53
+ ProtocolContractAddress.FeeJuice,
54
+ this.enforceFees,
55
+ globalVariables.gasFees,
56
+ ),
49
57
  );
50
58
  }
51
59
 
52
60
  validatorForProcessedTxs(fork: MerkleTreeReadOperations): TxValidator<ProcessedTx> {
53
61
  return new DoubleSpendTxValidator({
54
- getNullifierIndex: nullifier => fork.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer()),
62
+ getNullifierIndices: nullifiers => fork.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, nullifiers),
55
63
  });
56
64
  }
57
65
  }