@aztec/sequencer-client 0.67.1 → 0.68.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 (47) hide show
  1. package/dest/config.d.ts +3 -3
  2. package/dest/config.d.ts.map +1 -1
  3. package/dest/config.js +3 -56
  4. package/dest/index.d.ts +0 -1
  5. package/dest/index.d.ts.map +1 -1
  6. package/dest/index.js +1 -2
  7. package/dest/publisher/index.d.ts +0 -1
  8. package/dest/publisher/index.d.ts.map +1 -1
  9. package/dest/publisher/index.js +1 -2
  10. package/dest/publisher/l1-publisher-metrics.d.ts +5 -2
  11. package/dest/publisher/l1-publisher-metrics.d.ts.map +1 -1
  12. package/dest/publisher/l1-publisher-metrics.js +16 -1
  13. package/dest/publisher/l1-publisher.d.ts +4 -1
  14. package/dest/publisher/l1-publisher.d.ts.map +1 -1
  15. package/dest/publisher/l1-publisher.js +200 -44
  16. package/dest/publisher/utils.d.ts +1 -3
  17. package/dest/publisher/utils.d.ts.map +1 -1
  18. package/dest/publisher/utils.js +2 -8
  19. package/dest/sequencer/allowed.d.ts +3 -0
  20. package/dest/sequencer/allowed.d.ts.map +1 -0
  21. package/dest/sequencer/allowed.js +34 -0
  22. package/dest/sequencer/config.d.ts +1 -1
  23. package/dest/sequencer/config.d.ts.map +1 -1
  24. package/dest/sequencer/metrics.d.ts +2 -0
  25. package/dest/sequencer/metrics.d.ts.map +1 -1
  26. package/dest/sequencer/metrics.js +13 -2
  27. package/dest/sequencer/sequencer.d.ts +1 -4
  28. package/dest/sequencer/sequencer.d.ts.map +1 -1
  29. package/dest/sequencer/sequencer.js +51 -54
  30. package/dest/tx_validator/gas_validator.d.ts +3 -4
  31. package/dest/tx_validator/gas_validator.d.ts.map +1 -1
  32. package/dest/tx_validator/gas_validator.js +25 -8
  33. package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
  34. package/dest/tx_validator/tx_validator_factory.js +6 -4
  35. package/package.json +26 -23
  36. package/src/config.ts +5 -60
  37. package/src/index.ts +0 -1
  38. package/src/publisher/index.ts +0 -1
  39. package/src/publisher/l1-publisher-metrics.ts +24 -3
  40. package/src/publisher/l1-publisher.ts +241 -68
  41. package/src/publisher/utils.ts +1 -10
  42. package/src/sequencer/allowed.ts +36 -0
  43. package/src/sequencer/config.ts +1 -1
  44. package/src/sequencer/metrics.ts +15 -1
  45. package/src/sequencer/sequencer.ts +61 -70
  46. package/src/tx_validator/gas_validator.ts +32 -6
  47. package/src/tx_validator/tx_validator_factory.ts +11 -3
@@ -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()}`);
@@ -293,6 +283,7 @@ export class Sequencer {
293
283
  const pendingTxs = await this.p2pClient.getPendingTxs();
294
284
 
295
285
  if (!this.shouldProposeBlock(historicalHeader, { pendingTxsCount: pendingTxs.length })) {
286
+ await this.claimEpochProofRightIfAvailable(slot);
296
287
  return;
297
288
  }
298
289
 
@@ -325,6 +316,7 @@ export class Sequencer {
325
316
 
326
317
  // Bail if we don't have enough valid txs
327
318
  if (!this.shouldProposeBlock(historicalHeader, { validTxsCount: validTxs.length })) {
319
+ await this.claimEpochProofRightIfAvailable(slot);
328
320
  return;
329
321
  }
330
322
 
@@ -339,6 +331,7 @@ export class Sequencer {
339
331
  this.setState(SequencerState.IDLE, 0n);
340
332
  }
341
333
 
334
+ @trackSpan('Sequencer.work')
342
335
  protected async work() {
343
336
  try {
344
337
  await this.doRealWork();
@@ -354,15 +347,6 @@ export class Sequencer {
354
347
  }
355
348
  }
356
349
 
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
350
  async mayProposeBlock(tipArchive: Buffer, proposalBlockNumber: bigint): Promise<bigint> {
367
351
  // This checks that we can propose, and gives us the slot that we are to propose for
368
352
  try {
@@ -445,53 +429,29 @@ export class Sequencer {
445
429
  `Last block mined at ${lastBlockTime} current time is ${currentTime} (elapsed ${elapsedSinceLastBlock})`,
446
430
  );
447
431
 
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) {
432
+ // We need to have at least minTxsPerBLock txs.
433
+ if (args.pendingTxsCount !== undefined && args.pendingTxsCount < this.minTxsPerBLock) {
451
434
  this.log.verbose(
452
- `Not creating block because not enough time ${this.minSecondsBetweenBlocks} has passed since last block`,
435
+ `Not creating block because not enough txs in the pool (got ${args.pendingTxsCount} min ${this.minTxsPerBLock})`,
453
436
  );
454
437
  return false;
455
438
  }
456
439
 
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
440
  // 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
- }
441
+ if (args.validTxsCount !== undefined && args.validTxsCount < this.minTxsPerBLock) {
442
+ this.log.verbose(
443
+ `Not creating block because not enough valid txs loaded from the pool (got ${args.validTxsCount} min ${this.minTxsPerBLock})`,
444
+ );
445
+ return false;
484
446
  }
485
447
 
486
448
  // TODO: This check should be processedTxs.length < this.minTxsPerBLock, so we don't publish a block with
487
449
  // less txs than the minimum. But that'd cause the entire block to be aborted and retried. Instead, we should
488
450
  // go back to the p2p pool and load more txs until we hit our minTxsPerBLock target. Only if there are no txs
489
451
  // 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
- }
452
+ if (args.processedTxsCount === 0 && this.minTxsPerBLock > 0) {
453
+ this.log.verbose('No txs processed correctly to build block.');
454
+ return false;
495
455
  }
496
456
 
497
457
  return true;
@@ -541,18 +501,18 @@ export class Sequencer {
541
501
  const orchestratorFork = await this.worldState.fork();
542
502
 
543
503
  try {
544
- const processor = this.publicProcessorFactory.create(publicProcessorFork, historicalHeader, newGlobalVariables);
504
+ const processor = this.publicProcessorFactory.create(
505
+ publicProcessorFork,
506
+ historicalHeader,
507
+ newGlobalVariables,
508
+ true,
509
+ );
545
510
  const blockBuildingTimer = new Timer();
546
511
  const blockBuilder = this.blockBuilderFactory.create(orchestratorFork);
547
- await blockBuilder.startNewBlock(blockSize, newGlobalVariables, l1ToL2Messages);
512
+ await blockBuilder.startNewBlock(newGlobalVariables, l1ToL2Messages);
548
513
 
549
514
  const [publicProcessorDuration, [processedTxs, failedTxs]] = await elapsed(() =>
550
- processor.process(
551
- validTxs,
552
- blockSize,
553
- blockBuilder,
554
- this.txValidatorFactory.validatorForProcessedTxs(publicProcessorFork),
555
- ),
515
+ processor.process(validTxs, blockSize, this.txValidatorFactory.validatorForProcessedTxs(publicProcessorFork)),
556
516
  );
557
517
  if (failedTxs.length > 0) {
558
518
  const failedTxData = failedTxs.map(fail => fail.tx);
@@ -560,6 +520,12 @@ export class Sequencer {
560
520
  await this.p2pClient.deleteTxs(Tx.getHashes(failedTxData));
561
521
  }
562
522
 
523
+ const start = process.hrtime.bigint();
524
+ await blockBuilder.addTxs(processedTxs);
525
+ const end = process.hrtime.bigint();
526
+ const duration = Number(end - start) / 1_000;
527
+ this.metrics.recordBlockBuilderTreeInsertions(duration);
528
+
563
529
  await interrupt?.(processedTxs);
564
530
 
565
531
  // All real transactions have been added, set the block as full and complete the proving.
@@ -649,7 +615,6 @@ export class Sequencer {
649
615
  const blockHash = block.hash();
650
616
  const txHashes = validTxs.map(tx => tx.getTxHash());
651
617
  this.log.info(`Built block ${block.number} with hash ${blockHash}`, {
652
- txEffectsHash: block.header.contentCommitment.txsEffectsHash.toString('hex'),
653
618
  blockHash,
654
619
  globalVariables: block.header.globalVariables.toInspect(),
655
620
  txHashes,
@@ -748,7 +713,7 @@ export class Sequencer {
748
713
  // Find out which epoch we are currently in
749
714
  const epochToProve = await this.publisher.getClaimableEpoch();
750
715
  if (epochToProve === undefined) {
751
- this.log.debug(`No epoch to prove`);
716
+ this.log.trace(`No epoch to prove at slot ${slotNumber}`);
752
717
  return undefined;
753
718
  }
754
719
 
@@ -761,7 +726,10 @@ export class Sequencer {
761
726
  });
762
727
  // ensure these quotes are still valid for the slot and have the contract validate them
763
728
  const validQuotesPromise = Promise.all(
764
- quotes.filter(x => x.payload.validUntilSlot >= slotNumber).map(x => this.publisher.validateProofQuote(x)),
729
+ quotes
730
+ .filter(x => x.payload.validUntilSlot >= slotNumber)
731
+ .filter(x => x.payload.epochToProve === epochToProve)
732
+ .map(x => this.publisher.validateProofQuote(x)),
765
733
  );
766
734
 
767
735
  const validQuotes = (await validQuotesPromise).filter((q): q is EpochProofQuote => !!q);
@@ -774,7 +742,7 @@ export class Sequencer {
774
742
  (a: EpochProofQuote, b: EpochProofQuote) => a.payload.basisPointFee - b.payload.basisPointFee,
775
743
  );
776
744
  const quote = sortedQuotes[0];
777
- this.log.info(`Selected proof quote for proof claim`, quote.payload);
745
+ this.log.info(`Selected proof quote for proof claim`, { quote: quote.toInspect() });
778
746
  return quote;
779
747
  } catch (err) {
780
748
  this.log.error(`Failed to create proof claim for previous epoch`, err, { slotNumber });
@@ -834,6 +802,29 @@ export class Sequencer {
834
802
  return toReturn;
835
803
  }
836
804
 
805
+ @trackSpan(
806
+ 'Sequencer.claimEpochProofRightIfAvailable',
807
+ slotNumber => ({ [Attributes.SLOT_NUMBER]: Number(slotNumber) }),
808
+ epoch => ({ [Attributes.EPOCH_NUMBER]: Number(epoch) }),
809
+ )
810
+ /** Collects an epoch proof quote if there is an epoch to prove, and submits it to the L1 contract. */
811
+ protected async claimEpochProofRightIfAvailable(slotNumber: bigint) {
812
+ const proofQuote = await this.createProofClaimForPreviousEpoch(slotNumber);
813
+ if (proofQuote === undefined) {
814
+ return;
815
+ }
816
+
817
+ const epoch = proofQuote.payload.epochToProve;
818
+ const ctx = { slotNumber, epoch, quote: proofQuote.toInspect() };
819
+ this.log.verbose(`Claiming proof right for epoch ${epoch}`, ctx);
820
+ const success = await this.publisher.claimEpochProofRight(proofQuote);
821
+ if (!success) {
822
+ throw new Error(`Failed to claim proof right for epoch ${epoch}`);
823
+ }
824
+ this.log.info(`Claimed proof right for epoch ${epoch}`, ctx);
825
+ return epoch;
826
+ }
827
+
837
828
  /**
838
829
  * Returns whether all dependencies have caught up.
839
830
  * We don't check against the previous block submitted since it may have been reorg'd out.
@@ -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
  }