@aztec/sequencer-client 0.67.1 → 0.68.1

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 (55) hide show
  1. package/dest/client/sequencer-client.d.ts +3 -1
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +17 -3
  4. package/dest/config.d.ts +3 -3
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +8 -56
  7. package/dest/index.d.ts +1 -1
  8. package/dest/index.d.ts.map +1 -1
  9. package/dest/index.js +2 -2
  10. package/dest/publisher/index.d.ts +0 -1
  11. package/dest/publisher/index.d.ts.map +1 -1
  12. package/dest/publisher/index.js +1 -2
  13. package/dest/publisher/l1-publisher-metrics.d.ts +5 -2
  14. package/dest/publisher/l1-publisher-metrics.d.ts.map +1 -1
  15. package/dest/publisher/l1-publisher-metrics.js +16 -1
  16. package/dest/publisher/l1-publisher.d.ts +4 -1
  17. package/dest/publisher/l1-publisher.d.ts.map +1 -1
  18. package/dest/publisher/l1-publisher.js +200 -44
  19. package/dest/publisher/utils.d.ts +1 -3
  20. package/dest/publisher/utils.d.ts.map +1 -1
  21. package/dest/publisher/utils.js +2 -8
  22. package/dest/sequencer/allowed.d.ts +3 -0
  23. package/dest/sequencer/allowed.d.ts.map +1 -0
  24. package/dest/sequencer/allowed.js +34 -0
  25. package/dest/sequencer/config.d.ts +1 -1
  26. package/dest/sequencer/config.d.ts.map +1 -1
  27. package/dest/sequencer/metrics.d.ts +2 -0
  28. package/dest/sequencer/metrics.d.ts.map +1 -1
  29. package/dest/sequencer/metrics.js +13 -2
  30. package/dest/sequencer/sequencer.d.ts +14 -11
  31. package/dest/sequencer/sequencer.d.ts.map +1 -1
  32. package/dest/sequencer/sequencer.js +160 -87
  33. package/dest/sequencer/utils.d.ts +2 -7
  34. package/dest/sequencer/utils.d.ts.map +1 -1
  35. package/dest/sequencer/utils.js +3 -11
  36. package/dest/tx_validator/gas_validator.d.ts +3 -4
  37. package/dest/tx_validator/gas_validator.d.ts.map +1 -1
  38. package/dest/tx_validator/gas_validator.js +25 -8
  39. package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
  40. package/dest/tx_validator/tx_validator_factory.js +6 -4
  41. package/package.json +26 -23
  42. package/src/client/sequencer-client.ts +25 -5
  43. package/src/config.ts +10 -60
  44. package/src/index.ts +1 -1
  45. package/src/publisher/index.ts +0 -1
  46. package/src/publisher/l1-publisher-metrics.ts +24 -3
  47. package/src/publisher/l1-publisher.ts +241 -68
  48. package/src/publisher/utils.ts +1 -10
  49. package/src/sequencer/allowed.ts +36 -0
  50. package/src/sequencer/config.ts +1 -1
  51. package/src/sequencer/metrics.ts +15 -1
  52. package/src/sequencer/sequencer.ts +195 -109
  53. package/src/sequencer/utils.ts +2 -11
  54. package/src/tx_validator/gas_validator.ts +32 -6
  55. package/src/tx_validator/tx_validator_factory.ts +11 -3
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  type EpochProofQuote,
3
+ type L1RollupConstants,
3
4
  type L1ToL2MessageSource,
4
5
  type L2Block,
5
6
  type L2BlockSource,
@@ -27,7 +28,7 @@ import { Fr } from '@aztec/foundation/fields';
27
28
  import { createLogger } from '@aztec/foundation/log';
28
29
  import { RunningPromise } from '@aztec/foundation/running-promise';
29
30
  import { pickFromSchema } from '@aztec/foundation/schemas';
30
- import { Timer, elapsed } from '@aztec/foundation/timer';
31
+ import { type DateProvider, Timer, elapsed } from '@aztec/foundation/timer';
31
32
  import { type P2P } from '@aztec/p2p';
32
33
  import { type BlockBuilderFactory } from '@aztec/prover-client/block-builder';
33
34
  import { type PublicProcessorFactory } from '@aztec/simulator';
@@ -38,9 +39,12 @@ import { type GlobalVariableBuilder } from '../global_variable_builder/global_bu
38
39
  import { type L1Publisher } from '../publisher/l1-publisher.js';
39
40
  import { prettyLogViemErrorMsg } from '../publisher/utils.js';
40
41
  import { type TxValidatorFactory } from '../tx_validator/tx_validator_factory.js';
42
+ import { getDefaultAllowedSetupFunctions } from './allowed.js';
41
43
  import { type SequencerConfig } from './config.js';
42
44
  import { SequencerMetrics } from './metrics.js';
43
- import { SequencerState, getSecondsIntoSlot, orderAttestations } from './utils.js';
45
+ import { SequencerState, orderAttestations } from './utils.js';
46
+
47
+ export { SequencerState };
44
48
 
45
49
  export type ShouldProposeArgs = {
46
50
  pendingTxsCount?: number;
@@ -62,6 +66,8 @@ export class SequencerTooSlowError extends Error {
62
66
  }
63
67
  }
64
68
 
69
+ type SequencerRollupConstants = Pick<L1RollupConstants, 'ethereumSlotDuration' | 'l1GenesisTime' | 'slotDuration'>;
70
+
65
71
  /**
66
72
  * Sequencer client
67
73
  * - Wins a period of time to become the sequencer (depending on finalized protocol).
@@ -76,14 +82,14 @@ export class Sequencer {
76
82
  private pollingIntervalMs: number = 1000;
77
83
  private maxTxsPerBlock = 32;
78
84
  private minTxsPerBLock = 1;
79
- private minSecondsBetweenBlocks = 0;
80
- private maxSecondsBetweenBlocks = 0;
85
+ private maxL1TxInclusionTimeIntoSlot = 0;
81
86
  // TODO: zero values should not be allowed for the following 2 values in PROD
82
87
  private _coinbase = EthAddress.ZERO;
83
88
  private _feeRecipient = AztecAddress.ZERO;
84
89
  private state = SequencerState.STOPPED;
85
- private allowedInSetup: AllowedElement[] = [];
90
+ private allowedInSetup: AllowedElement[] = getDefaultAllowedSetupFunctions();
86
91
  private maxBlockSizeInBytes: number = 1024 * 1024;
92
+ private processTxTime: number = 12;
87
93
  private metrics: SequencerMetrics;
88
94
  private isFlushing: boolean = false;
89
95
 
@@ -105,8 +111,8 @@ export class Sequencer {
105
111
  private l1ToL2MessageSource: L1ToL2MessageSource,
106
112
  private publicProcessorFactory: PublicProcessorFactory,
107
113
  private txValidatorFactory: TxValidatorFactory,
108
- protected l1GenesisTime: number,
109
- private aztecSlotDuration: number,
114
+ protected l1Constants: SequencerRollupConstants,
115
+ private dateProvider: DateProvider,
110
116
  telemetry: TelemetryClient,
111
117
  private config: SequencerConfig = {},
112
118
  private log = createLogger('sequencer'),
@@ -127,10 +133,7 @@ export class Sequencer {
127
133
  * @param config - New parameters.
128
134
  */
129
135
  public updateConfig(config: SequencerConfig) {
130
- this.log.info(
131
- `Sequencer config set`,
132
- omit(pickFromSchema(this.config, SequencerConfigSchema), 'allowedInSetup', 'allowedInTeardown'),
133
- );
136
+ this.log.info(`Sequencer config set`, omit(pickFromSchema(config, SequencerConfigSchema), 'allowedInSetup'));
134
137
 
135
138
  if (config.transactionPollingIntervalMS !== undefined) {
136
139
  this.pollingIntervalMs = config.transactionPollingIntervalMS;
@@ -141,12 +144,6 @@ export class Sequencer {
141
144
  if (config.minTxsPerBlock !== undefined) {
142
145
  this.minTxsPerBLock = config.minTxsPerBlock;
143
146
  }
144
- if (config.minSecondsBetweenBlocks !== undefined) {
145
- this.minSecondsBetweenBlocks = config.minSecondsBetweenBlocks;
146
- }
147
- if (config.maxSecondsBetweenBlocks !== undefined) {
148
- this.maxSecondsBetweenBlocks = config.maxSecondsBetweenBlocks;
149
- }
150
147
  if (config.coinbase) {
151
148
  this._coinbase = config.coinbase;
152
149
  }
@@ -162,6 +159,9 @@ export class Sequencer {
162
159
  if (config.governanceProposerPayload) {
163
160
  this.publisher.setPayload(config.governanceProposerPayload);
164
161
  }
162
+ if (config.maxL1TxInclusionTimeIntoSlot !== undefined) {
163
+ this.maxL1TxInclusionTimeIntoSlot = config.maxL1TxInclusionTimeIntoSlot;
164
+ }
165
165
  this.enforceTimeTable = config.enforceTimeTable === true;
166
166
 
167
167
  this.setTimeTable();
@@ -171,20 +171,59 @@ export class Sequencer {
171
171
  }
172
172
 
173
173
  private setTimeTable() {
174
+ // How late into the slot can we be to start working
175
+ const initialTime = 1;
176
+
177
+ // How long it takes to validate the txs collected and get ready to start building
178
+ const blockPrepareTime = 2;
179
+
180
+ // How long it takes to for attestations to travel across the p2p layer.
181
+ const attestationPropagationTime = 2;
182
+
183
+ // How long it takes to get a published block into L1. L1 builders typically accept txs up to 4 seconds into their slot,
184
+ // but we'll timeout sooner to give it more time to propagate (remember we also have blobs!). Still, when working in anvil,
185
+ // we can just post in the very last second of the L1 slot.
186
+ const l1PublishingTime = this.l1Constants.ethereumSlotDuration - this.maxL1TxInclusionTimeIntoSlot;
187
+
188
+ // How much time we spend validating and processing a block after building it
189
+ const blockValidationTime = 1;
190
+
191
+ // How much time we have left in the slot for actually processing txs and building the block.
192
+ const remainingTimeInSlot =
193
+ this.aztecSlotDuration -
194
+ initialTime -
195
+ blockPrepareTime -
196
+ l1PublishingTime -
197
+ 2 * attestationPropagationTime -
198
+ blockValidationTime;
199
+
200
+ // Check that numbers make sense
201
+ if (this.enforceTimeTable && remainingTimeInSlot < 0) {
202
+ throw new Error(`Not enough time for block building in ${this.aztecSlotDuration}s slot`);
203
+ }
204
+
205
+ // How much time we have for actually processing txs. Note that we need both the sequencer and the validators to execute txs.
206
+ const processTxsTime = remainingTimeInSlot / 2;
207
+ this.processTxTime = processTxsTime;
208
+
174
209
  const newTimeTable: Record<SequencerState, number> = {
210
+ // No checks needed for any of these transitions
175
211
  [SequencerState.STOPPED]: this.aztecSlotDuration,
176
212
  [SequencerState.IDLE]: this.aztecSlotDuration,
177
213
  [SequencerState.SYNCHRONIZING]: this.aztecSlotDuration,
178
- [SequencerState.PROPOSER_CHECK]: this.aztecSlotDuration, // We always want to allow the full slot to check if we are the proposer
179
- [SequencerState.WAITING_FOR_TXS]: 5,
180
- [SequencerState.CREATING_BLOCK]: 7,
181
- [SequencerState.PUBLISHING_BLOCK_TO_PEERS]: 7 + this.maxTxsPerBlock * 2, // if we take 5 seconds to create block, then 4 transactions at 2 seconds each
182
- [SequencerState.WAITING_FOR_ATTESTATIONS]: 7 + this.maxTxsPerBlock * 2 + 3, // it shouldn't take 3 seconds to publish to peers
183
- [SequencerState.PUBLISHING_BLOCK]: 7 + this.maxTxsPerBlock * 2 + 3 + 5, // wait 5 seconds for attestations
214
+ // We always want to allow the full slot to check if we are the proposer
215
+ [SequencerState.PROPOSER_CHECK]: this.aztecSlotDuration,
216
+ // First transition towards building a block
217
+ [SequencerState.WAITING_FOR_TXS]: initialTime,
218
+ // We then validate the txs and prepare to start building the block
219
+ [SequencerState.CREATING_BLOCK]: initialTime + blockPrepareTime,
220
+ // We start collecting attestations after building the block
221
+ [SequencerState.COLLECTING_ATTESTATIONS]: initialTime + blockPrepareTime + processTxsTime + blockValidationTime,
222
+ // We publish the block after collecting attestations
223
+ [SequencerState.PUBLISHING_BLOCK]: this.aztecSlotDuration - l1PublishingTime,
184
224
  };
185
- if (this.enforceTimeTable && newTimeTable[SequencerState.PUBLISHING_BLOCK] > this.aztecSlotDuration) {
186
- throw new Error('Sequencer cannot publish block in less than a slot');
187
- }
225
+
226
+ this.log.verbose(`Sequencer time table updated with ${processTxsTime}s for processing txs`, newTimeTable);
188
227
  this.timeTable = newTimeTable;
189
228
  }
190
229
 
@@ -192,7 +231,7 @@ export class Sequencer {
192
231
  * Starts the sequencer and moves to IDLE state.
193
232
  */
194
233
  public start() {
195
- this.runningPromise = new RunningPromise(this.work.bind(this), this.pollingIntervalMs);
234
+ this.runningPromise = new RunningPromise(this.work.bind(this), this.log, this.pollingIntervalMs);
196
235
  this.setState(SequencerState.IDLE, 0n, true /** force */);
197
236
  this.runningPromise.start();
198
237
  this.log.info(`Sequencer started with address ${this.publisher.getSenderAddress().toString()}`);
@@ -293,6 +332,7 @@ export class Sequencer {
293
332
  const pendingTxs = await this.p2pClient.getPendingTxs();
294
333
 
295
334
  if (!this.shouldProposeBlock(historicalHeader, { pendingTxsCount: pendingTxs.length })) {
335
+ await this.claimEpochProofRightIfAvailable(slot);
296
336
  return;
297
337
  }
298
338
 
@@ -306,7 +346,8 @@ export class Sequencer {
306
346
  Fr.ZERO,
307
347
  );
308
348
 
309
- // TODO: It should be responsibility of the P2P layer to validate txs before passing them on here
349
+ // TODO: It should be responsibility of the P2P layer to validate txs before passing them on here.
350
+ // TODO: We should validate only the number of txs we need to speed up this process.
310
351
  const allValidTxs = await this.takeValidTxs(
311
352
  pendingTxs,
312
353
  this.txValidatorFactory.validatorForNewTxs(newGlobalVariables, this.allowedInSetup),
@@ -325,6 +366,7 @@ export class Sequencer {
325
366
 
326
367
  // Bail if we don't have enough valid txs
327
368
  if (!this.shouldProposeBlock(historicalHeader, { validTxsCount: validTxs.length })) {
369
+ await this.claimEpochProofRightIfAvailable(slot);
328
370
  return;
329
371
  }
330
372
 
@@ -339,6 +381,7 @@ export class Sequencer {
339
381
  this.setState(SequencerState.IDLE, 0n);
340
382
  }
341
383
 
384
+ @trackSpan('Sequencer.work')
342
385
  protected async work() {
343
386
  try {
344
387
  await this.doRealWork();
@@ -354,15 +397,6 @@ export class Sequencer {
354
397
  }
355
398
  }
356
399
 
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
400
  async mayProposeBlock(tipArchive: Buffer, proposalBlockNumber: bigint): Promise<bigint> {
367
401
  // This checks that we can propose, and gives us the slot that we are to propose for
368
402
  try {
@@ -388,24 +422,21 @@ export class Sequencer {
388
422
  return true;
389
423
  }
390
424
 
391
- if (this.timeTable[proposedState] === this.aztecSlotDuration) {
425
+ const maxAllowedTime = this.timeTable[proposedState];
426
+ if (maxAllowedTime === this.aztecSlotDuration) {
392
427
  return true;
393
428
  }
394
429
 
395
- const bufferSeconds = this.timeTable[proposedState] - secondsIntoSlot;
430
+ const bufferSeconds = maxAllowedTime - secondsIntoSlot;
396
431
 
397
432
  if (bufferSeconds < 0) {
398
- this.log.warn(
399
- `Too far into slot to transition to ${proposedState}. max allowed: ${this.timeTable[proposedState]}s, time into slot: ${secondsIntoSlot}s`,
400
- );
433
+ this.log.warn(`Too far into slot to transition to ${proposedState}`, { maxAllowedTime, secondsIntoSlot });
401
434
  return false;
402
435
  }
403
436
 
404
437
  this.metrics.recordStateTransitionBufferMs(Math.floor(bufferSeconds * 1000), proposedState);
405
438
 
406
- this.log.debug(
407
- `Enough time to transition to ${proposedState}, max allowed: ${this.timeTable[proposedState]}s, time into slot: ${secondsIntoSlot}s`,
408
- );
439
+ this.log.trace(`Enough time to transition to ${proposedState}`, { maxAllowedTime, secondsIntoSlot });
409
440
  return true;
410
441
  }
411
442
 
@@ -423,7 +454,7 @@ export class Sequencer {
423
454
  this.log.warn(`Cannot set sequencer from ${this.state} to ${proposedState} as it is stopped.`);
424
455
  return;
425
456
  }
426
- const secondsIntoSlot = getSecondsIntoSlot(this.l1GenesisTime, this.aztecSlotDuration, Number(currentSlotNumber));
457
+ const secondsIntoSlot = this.getSecondsIntoSlot(currentSlotNumber);
427
458
  if (!this.doIHaveEnoughTimeLeft(proposedState, secondsIntoSlot)) {
428
459
  throw new SequencerTooSlowError(this.state, proposedState, this.timeTable[proposedState], secondsIntoSlot);
429
460
  }
@@ -445,53 +476,29 @@ export class Sequencer {
445
476
  `Last block mined at ${lastBlockTime} current time is ${currentTime} (elapsed ${elapsedSinceLastBlock})`,
446
477
  );
447
478
 
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) {
479
+ // We need to have at least minTxsPerBLock txs.
480
+ if (args.pendingTxsCount !== undefined && args.pendingTxsCount < this.minTxsPerBLock) {
451
481
  this.log.verbose(
452
- `Not creating block because not enough time ${this.minSecondsBetweenBlocks} has passed since last block`,
482
+ `Not creating block because not enough txs in the pool (got ${args.pendingTxsCount} min ${this.minTxsPerBLock})`,
453
483
  );
454
484
  return false;
455
485
  }
456
486
 
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
487
  // 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
- }
488
+ if (args.validTxsCount !== undefined && args.validTxsCount < this.minTxsPerBLock) {
489
+ this.log.verbose(
490
+ `Not creating block because not enough valid txs loaded from the pool (got ${args.validTxsCount} min ${this.minTxsPerBLock})`,
491
+ );
492
+ return false;
484
493
  }
485
494
 
486
495
  // TODO: This check should be processedTxs.length < this.minTxsPerBLock, so we don't publish a block with
487
496
  // less txs than the minimum. But that'd cause the entire block to be aborted and retried. Instead, we should
488
497
  // go back to the p2p pool and load more txs until we hit our minTxsPerBLock target. Only if there are no txs
489
498
  // 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
- }
499
+ if (args.processedTxsCount === 0 && this.minTxsPerBLock > 0) {
500
+ this.log.verbose('No txs processed correctly to build block.');
501
+ return false;
495
502
  }
496
503
 
497
504
  return true;
@@ -506,12 +513,14 @@ export class Sequencer {
506
513
  * @param newGlobalVariables - The global variables for the new block
507
514
  * @param historicalHeader - The historical header of the parent
508
515
  * @param interrupt - The interrupt callback, used to validate the block for submission and check if we should propose the block
516
+ * @param opts - Whether to just validate the block as a validator, as opposed to building it as a proposal
509
517
  */
510
518
  private async buildBlock(
511
519
  validTxs: Tx[],
512
520
  newGlobalVariables: GlobalVariables,
513
521
  historicalHeader?: BlockHeader,
514
522
  interrupt?: (processedTxs: ProcessedTx[]) => Promise<void>,
523
+ opts: { validateOnly?: boolean } = {},
515
524
  ) {
516
525
  const blockNumber = newGlobalVariables.blockNumber.toBigInt();
517
526
  const slot = newGlobalVariables.slotNumber.toBigInt();
@@ -541,28 +550,61 @@ export class Sequencer {
541
550
  const orchestratorFork = await this.worldState.fork();
542
551
 
543
552
  try {
544
- const processor = this.publicProcessorFactory.create(publicProcessorFork, historicalHeader, newGlobalVariables);
553
+ const processor = this.publicProcessorFactory.create(
554
+ publicProcessorFork,
555
+ historicalHeader,
556
+ newGlobalVariables,
557
+ true,
558
+ );
545
559
  const blockBuildingTimer = new Timer();
546
560
  const blockBuilder = this.blockBuilderFactory.create(orchestratorFork);
547
- await blockBuilder.startNewBlock(blockSize, newGlobalVariables, l1ToL2Messages);
548
-
561
+ await blockBuilder.startNewBlock(newGlobalVariables, l1ToL2Messages);
562
+
563
+ // We set the deadline for tx processing to the start of the CREATING_BLOCK phase, plus the expected time for tx processing.
564
+ // Deadline is only set if enforceTimeTable is enabled.
565
+ const processingEndTimeWithinSlot = this.timeTable[SequencerState.CREATING_BLOCK] + this.processTxTime;
566
+ const processingDeadline = this.enforceTimeTable
567
+ ? new Date((this.getSlotStartTimestamp(slot) + processingEndTimeWithinSlot) * 1000)
568
+ : undefined;
569
+ this.log.verbose(`Processing ${validTxs.length} txs`, {
570
+ slot,
571
+ slotStart: new Date(this.getSlotStartTimestamp(slot) * 1000),
572
+ now: new Date(this.dateProvider.now()),
573
+ deadline: processingDeadline,
574
+ });
575
+ const processingTxValidator = this.txValidatorFactory.validatorForProcessedTxs(publicProcessorFork);
549
576
  const [publicProcessorDuration, [processedTxs, failedTxs]] = await elapsed(() =>
550
- processor.process(
551
- validTxs,
552
- blockSize,
553
- blockBuilder,
554
- this.txValidatorFactory.validatorForProcessedTxs(publicProcessorFork),
555
- ),
577
+ processor.process(validTxs, blockSize, processingTxValidator, processingDeadline),
556
578
  );
579
+
557
580
  if (failedTxs.length > 0) {
558
581
  const failedTxData = failedTxs.map(fail => fail.tx);
559
582
  this.log.verbose(`Dropping failed txs ${Tx.getHashes(failedTxData).join(', ')}`);
560
583
  await this.p2pClient.deleteTxs(Tx.getHashes(failedTxData));
561
584
  }
562
585
 
586
+ if (
587
+ !opts.validateOnly && // We check for minTxCount only if we are proposing a block, not if we are validating it
588
+ !this.isFlushing && // And we skip the check when flushing, since we want all pending txs to go out, no matter if too few
589
+ this.minTxsPerBLock !== undefined &&
590
+ processedTxs.length < this.minTxsPerBLock
591
+ ) {
592
+ this.log.warn(
593
+ `Block ${blockNumber} has too few txs to be proposed (got ${processedTxs.length} but required ${this.minTxsPerBLock})`,
594
+ { slot, blockNumber, processedTxCount: processedTxs.length },
595
+ );
596
+ throw new Error(`Block has too few successful txs to be proposed`);
597
+ }
598
+
599
+ const start = process.hrtime.bigint();
600
+ await blockBuilder.addTxs(processedTxs);
601
+ const end = process.hrtime.bigint();
602
+ const duration = Number(end - start) / 1_000;
603
+ this.metrics.recordBlockBuilderTreeInsertions(duration);
604
+
563
605
  await interrupt?.(processedTxs);
564
606
 
565
- // All real transactions have been added, set the block as full and complete the proving.
607
+ // All real transactions have been added, set the block as full and pad if needed
566
608
  const block = await blockBuilder.setBlockCompleted();
567
609
 
568
610
  return {
@@ -574,8 +616,16 @@ export class Sequencer {
574
616
  };
575
617
  } finally {
576
618
  // We create a fresh processor each time to reset any cached state (eg storage writes)
577
- await publicProcessorFork.close();
578
- await orchestratorFork.close();
619
+ // We wait a bit to close the forks since the processor may still be working on a dangling tx
620
+ // which was interrupted due to the processingDeadline being hit.
621
+ setTimeout(async () => {
622
+ try {
623
+ await publicProcessorFork.close();
624
+ await orchestratorFork.close();
625
+ } catch (err) {
626
+ this.log.error(`Error closing forks`, err);
627
+ }
628
+ }, 5000);
579
629
  }
580
630
  }
581
631
 
@@ -627,6 +677,9 @@ export class Sequencer {
627
677
  }
628
678
  };
629
679
 
680
+ // Start collecting proof quotes for the previous epoch if needed in the background
681
+ const proofQuotePromise = this.createProofClaimForPreviousEpoch(slot);
682
+
630
683
  try {
631
684
  const buildBlockRes = await this.buildBlock(validTxs, newGlobalVariables, historicalHeader, interrupt);
632
685
  const { block, publicProcessorDuration, numProcessedTxs, numMsgs, blockBuildingTimer } = buildBlockRes;
@@ -649,7 +702,6 @@ export class Sequencer {
649
702
  const blockHash = block.hash();
650
703
  const txHashes = validTxs.map(tx => tx.getTxHash());
651
704
  this.log.info(`Built block ${block.number} with hash ${blockHash}`, {
652
- txEffectsHash: block.header.contentCommitment.txsEffectsHash.toString('hex'),
653
705
  blockHash,
654
706
  globalVariables: block.header.globalVariables.toInspect(),
655
707
  txHashes,
@@ -665,12 +717,12 @@ export class Sequencer {
665
717
  const stopCollectingAttestationsTimer = this.metrics.startCollectingAttestationsTimer();
666
718
  const attestations = await this.collectAttestations(block, txHashes);
667
719
  if (attestations !== undefined) {
668
- this.log.verbose(`Collected ${attestations.length} attestations`);
720
+ this.log.verbose(`Collected ${attestations.length} attestations`, { blockHash, blockNumber });
669
721
  }
670
722
  stopCollectingAttestationsTimer();
671
723
 
672
- this.log.debug('Collecting proof quotes');
673
- const proofQuote = await this.createProofClaimForPreviousEpoch(newGlobalVariables.slotNumber.toBigInt());
724
+ // Get the proof quote for the previous epoch, if any
725
+ const proofQuote = await proofQuotePromise;
674
726
 
675
727
  await this.publishL2Block(block, attestations, txHashes, proofQuote);
676
728
  this.metrics.recordPublishedBlock(workDuration);
@@ -722,21 +774,19 @@ export class Sequencer {
722
774
  }
723
775
 
724
776
  const numberOfRequiredAttestations = Math.floor((committee.length * 2) / 3) + 1;
777
+ const slotNumber = block.header.globalVariables.slotNumber.toBigInt();
778
+ this.setState(SequencerState.COLLECTING_ATTESTATIONS, slotNumber);
725
779
 
726
- this.log.debug('Creating block proposal');
780
+ this.log.debug('Creating block proposal for validators');
727
781
  const proposal = await this.validatorClient.createBlockProposal(block.header, block.archive.root, txHashes);
728
782
  if (!proposal) {
729
783
  this.log.warn(`Failed to create block proposal, skipping collecting attestations`);
730
784
  return undefined;
731
785
  }
732
786
 
733
- const slotNumber = block.header.globalVariables.slotNumber.toBigInt();
734
-
735
- this.setState(SequencerState.PUBLISHING_BLOCK_TO_PEERS, slotNumber);
736
787
  this.log.debug('Broadcasting block proposal to validators');
737
788
  this.validatorClient.broadcastBlockProposal(proposal);
738
789
 
739
- this.setState(SequencerState.WAITING_FOR_ATTESTATIONS, slotNumber);
740
790
  const attestations = await this.validatorClient.collectAttestations(proposal, numberOfRequiredAttestations);
741
791
 
742
792
  // note: the smart contract requires that the signatures are provided in the order of the committee
@@ -748,11 +798,12 @@ export class Sequencer {
748
798
  // Find out which epoch we are currently in
749
799
  const epochToProve = await this.publisher.getClaimableEpoch();
750
800
  if (epochToProve === undefined) {
751
- this.log.debug(`No epoch to prove`);
801
+ this.log.trace(`No epoch to prove at slot ${slotNumber}`);
752
802
  return undefined;
753
803
  }
754
804
 
755
805
  // Get quotes for the epoch to be proven
806
+ this.log.debug(`Collecting proof quotes for epoch ${epochToProve}`);
756
807
  const quotes = await this.p2pClient.getEpochProofQuotes(epochToProve);
757
808
  this.log.verbose(`Retrieved ${quotes.length} quotes for slot ${slotNumber} epoch ${epochToProve}`, {
758
809
  epochToProve,
@@ -761,7 +812,10 @@ export class Sequencer {
761
812
  });
762
813
  // ensure these quotes are still valid for the slot and have the contract validate them
763
814
  const validQuotesPromise = Promise.all(
764
- quotes.filter(x => x.payload.validUntilSlot >= slotNumber).map(x => this.publisher.validateProofQuote(x)),
815
+ quotes
816
+ .filter(x => x.payload.validUntilSlot >= slotNumber)
817
+ .filter(x => x.payload.epochToProve === epochToProve)
818
+ .map(x => this.publisher.validateProofQuote(x)),
765
819
  );
766
820
 
767
821
  const validQuotes = (await validQuotesPromise).filter((q): q is EpochProofQuote => !!q);
@@ -774,7 +828,7 @@ export class Sequencer {
774
828
  (a: EpochProofQuote, b: EpochProofQuote) => a.payload.basisPointFee - b.payload.basisPointFee,
775
829
  );
776
830
  const quote = sortedQuotes[0];
777
- this.log.info(`Selected proof quote for proof claim`, quote.payload);
831
+ this.log.info(`Selected proof quote for proof claim`, { quote: quote.toInspect() });
778
832
  return quote;
779
833
  } catch (err) {
780
834
  this.log.error(`Failed to create proof claim for previous epoch`, err, { slotNumber });
@@ -834,6 +888,29 @@ export class Sequencer {
834
888
  return toReturn;
835
889
  }
836
890
 
891
+ @trackSpan(
892
+ 'Sequencer.claimEpochProofRightIfAvailable',
893
+ slotNumber => ({ [Attributes.SLOT_NUMBER]: Number(slotNumber) }),
894
+ epoch => ({ [Attributes.EPOCH_NUMBER]: Number(epoch) }),
895
+ )
896
+ /** Collects an epoch proof quote if there is an epoch to prove, and submits it to the L1 contract. */
897
+ protected async claimEpochProofRightIfAvailable(slotNumber: bigint) {
898
+ const proofQuote = await this.createProofClaimForPreviousEpoch(slotNumber);
899
+ if (proofQuote === undefined) {
900
+ return;
901
+ }
902
+
903
+ const epoch = proofQuote.payload.epochToProve;
904
+ const ctx = { slotNumber, epoch, quote: proofQuote.toInspect() };
905
+ this.log.verbose(`Claiming proof right for epoch ${epoch}`, ctx);
906
+ const success = await this.publisher.claimEpochProofRight(proofQuote);
907
+ if (!success) {
908
+ throw new Error(`Failed to claim proof right for epoch ${epoch}`);
909
+ }
910
+ this.log.info(`Claimed proof right for epoch ${epoch}`, ctx);
911
+ return epoch;
912
+ }
913
+
837
914
  /**
838
915
  * Returns whether all dependencies have caught up.
839
916
  * We don't check against the previous block submitted since it may have been reorg'd out.
@@ -870,6 +947,19 @@ export class Sequencer {
870
947
  return result;
871
948
  }
872
949
 
950
+ private getSlotStartTimestamp(slotNumber: number | bigint): number {
951
+ return Number(this.l1Constants.l1GenesisTime) + Number(slotNumber) * this.l1Constants.slotDuration;
952
+ }
953
+
954
+ private getSecondsIntoSlot(slotNumber: number | bigint): number {
955
+ const slotStartTimestamp = this.getSlotStartTimestamp(slotNumber);
956
+ return Number((this.dateProvider.now() / 1000 - slotStartTimestamp).toFixed(3));
957
+ }
958
+
959
+ get aztecSlotDuration() {
960
+ return this.l1Constants.slotDuration;
961
+ }
962
+
873
963
  get coinbase(): EthAddress {
874
964
  return this._coinbase;
875
965
  }
@@ -878,7 +968,3 @@ export class Sequencer {
878
968
  return this._feeRecipient;
879
969
  }
880
970
  }
881
-
882
- /**
883
- * State of the sequencer.
884
- */
@@ -27,13 +27,9 @@ export enum SequencerState {
27
27
  */
28
28
  CREATING_BLOCK = 'CREATING_BLOCK',
29
29
  /**
30
- * Publishing blocks to validator peers. Will move to WAITING_FOR_ATTESTATIONS.
30
+ * Collecting attestations from its peers. Will move to PUBLISHING_BLOCK.
31
31
  */
32
- PUBLISHING_BLOCK_TO_PEERS = 'PUBLISHING_BLOCK_TO_PEERS',
33
- /**
34
- * The block has been published to peers, and we are waiting for attestations. Will move to PUBLISHING_CONTRACT_DATA.
35
- */
36
- WAITING_FOR_ATTESTATIONS = 'WAITING_FOR_ATTESTATIONS',
32
+ COLLECTING_ATTESTATIONS = 'COLLECTING_ATTESTATIONS',
37
33
  /**
38
34
  * Sending the tx to L1 with the L2 block data and awaiting it to be mined. Will move to SYNCHRONIZING.
39
35
  */
@@ -72,8 +68,3 @@ export function orderAttestations(attestations: BlockAttestation[], orderAddress
72
68
 
73
69
  return orderedAttestations;
74
70
  }
75
-
76
- export function getSecondsIntoSlot(l1GenesisTime: number, aztecSlotDuration: number, slotNumber: number): number {
77
- const slotStartTimestamp = l1GenesisTime + slotNumber * aztecSlotDuration;
78
- return Number((Date.now() / 1000 - slotStartTimestamp).toFixed(3));
79
- }