@aztec/sequencer-client 0.71.0 → 0.73.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 (66) hide show
  1. package/dest/client/sequencer-client.d.ts +11 -6
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +41 -10
  4. package/dest/config.d.ts +1 -1
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +3 -4
  7. package/dest/publisher/config.d.ts +5 -0
  8. package/dest/publisher/config.d.ts.map +1 -1
  9. package/dest/publisher/config.js +9 -2
  10. package/dest/publisher/index.d.ts +1 -1
  11. package/dest/publisher/index.d.ts.map +1 -1
  12. package/dest/publisher/index.js +2 -2
  13. package/dest/publisher/{l1-publisher-metrics.d.ts → sequencer-publisher-metrics.d.ts} +6 -2
  14. package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -0
  15. package/dest/publisher/sequencer-publisher-metrics.js +111 -0
  16. package/dest/publisher/sequencer-publisher.d.ts +158 -0
  17. package/dest/publisher/sequencer-publisher.d.ts.map +1 -0
  18. package/dest/publisher/sequencer-publisher.js +555 -0
  19. package/dest/sequencer/allowed.d.ts +1 -1
  20. package/dest/sequencer/allowed.d.ts.map +1 -1
  21. package/dest/sequencer/allowed.js +6 -6
  22. package/dest/sequencer/metrics.d.ts +1 -1
  23. package/dest/sequencer/metrics.d.ts.map +1 -1
  24. package/dest/sequencer/metrics.js +3 -4
  25. package/dest/sequencer/sequencer.d.ts +18 -12
  26. package/dest/sequencer/sequencer.d.ts.map +1 -1
  27. package/dest/sequencer/sequencer.js +118 -125
  28. package/dest/sequencer/utils.d.ts +1 -1
  29. package/dest/sequencer/utils.d.ts.map +1 -1
  30. package/dest/sequencer/utils.js +3 -3
  31. package/dest/slasher/slasher_client.d.ts.map +1 -1
  32. package/dest/slasher/slasher_client.js +6 -3
  33. package/dest/test/index.d.ts +3 -3
  34. package/dest/test/index.d.ts.map +1 -1
  35. package/dest/test/index.js +1 -2
  36. package/dest/tx_validator/gas_validator.d.ts +1 -1
  37. package/dest/tx_validator/gas_validator.d.ts.map +1 -1
  38. package/dest/tx_validator/gas_validator.js +10 -5
  39. package/dest/tx_validator/test_utils.d.ts +4 -4
  40. package/dest/tx_validator/test_utils.d.ts.map +1 -1
  41. package/dest/tx_validator/test_utils.js +3 -3
  42. package/package.json +22 -21
  43. package/src/client/sequencer-client.ts +60 -14
  44. package/src/config.ts +3 -3
  45. package/src/publisher/config.ts +13 -1
  46. package/src/publisher/index.ts +1 -1
  47. package/src/publisher/{l1-publisher-metrics.ts → sequencer-publisher-metrics.ts} +41 -2
  48. package/src/publisher/sequencer-publisher.ts +730 -0
  49. package/src/sequencer/allowed.ts +5 -5
  50. package/src/sequencer/metrics.ts +2 -3
  51. package/src/sequencer/sequencer.ts +153 -150
  52. package/src/sequencer/utils.ts +5 -2
  53. package/src/slasher/slasher_client.ts +7 -2
  54. package/src/test/index.ts +2 -4
  55. package/src/tx_validator/gas_validator.ts +17 -4
  56. package/src/tx_validator/test_utils.ts +5 -5
  57. package/dest/publisher/l1-publisher-metrics.d.ts.map +0 -1
  58. package/dest/publisher/l1-publisher-metrics.js +0 -85
  59. package/dest/publisher/l1-publisher.d.ts +0 -195
  60. package/dest/publisher/l1-publisher.d.ts.map +0 -1
  61. package/dest/publisher/l1-publisher.js +0 -864
  62. package/dest/test/test-l1-publisher.d.ts +0 -9
  63. package/dest/test/test-l1-publisher.d.ts.map +0 -1
  64. package/dest/test/test-l1-publisher.js +0 -11
  65. package/src/publisher/l1-publisher.ts +0 -1208
  66. package/src/test/test-l1-publisher.ts +0 -20
@@ -6,7 +6,7 @@ import { ProtocolContractAddress } from '@aztec/protocol-contracts';
6
6
 
7
7
  let defaultAllowedSetupFunctions: AllowedElement[] | undefined = undefined;
8
8
 
9
- export function getDefaultAllowedSetupFunctions(): AllowedElement[] {
9
+ export async function getDefaultAllowedSetupFunctions(): Promise<AllowedElement[]> {
10
10
  if (defaultAllowedSetupFunctions === undefined) {
11
11
  defaultAllowedSetupFunctions = [
12
12
  // needed for authwit support
@@ -17,16 +17,16 @@ export function getDefaultAllowedSetupFunctions(): AllowedElement[] {
17
17
  {
18
18
  address: ProtocolContractAddress.FeeJuice,
19
19
  // We can't restrict the selector because public functions get routed via dispatch.
20
- // selector: FunctionSelector.fromSignature('_increase_public_balance((Field),Field)'),
20
+ // selector: FunctionSelector.fromSignature('_increase_public_balance((Field),(Field,Field))'),
21
21
  },
22
22
  // needed for private transfers via FPC
23
23
  {
24
- classId: getContractClassFromArtifact(TokenContractArtifact).id,
24
+ classId: (await getContractClassFromArtifact(TokenContractArtifact)).id,
25
25
  // We can't restrict the selector because public functions get routed via dispatch.
26
- // selector: FunctionSelector.fromSignature('_increase_public_balance((Field),Field)'),
26
+ // selector: FunctionSelector.fromSignature('_increase_public_balance((Field),(Field,Field))'),
27
27
  },
28
28
  {
29
- classId: getContractClassFromArtifact(FPCContract.artifact).id,
29
+ classId: (await getContractClassFromArtifact(FPCContract.artifact)).id,
30
30
  // We can't restrict the selector because public functions get routed via dispatch.
31
31
  // selector: FunctionSelector.fromSignature('prepare_fee((Field),Field,(Field),Field)'),
32
32
  },
@@ -105,13 +105,12 @@ export class SequencerMetrics {
105
105
  this.setCurrentBlock(0, 0);
106
106
  }
107
107
 
108
- recordPublishedBlock(buildDurationMs: number, totalMana: number) {
108
+ recordBuiltBlock(buildDurationMs: number, totalMana: number) {
109
109
  this.blockCounter.add(1, {
110
- [Attributes.STATUS]: 'published',
110
+ [Attributes.STATUS]: 'built',
111
111
  });
112
112
  this.blockBuildDuration.record(Math.ceil(buildDurationMs));
113
113
  this.blockBuildManaPerSecond.record(Math.ceil((totalMana * 1000) / buildDurationMs));
114
- this.setCurrentBlock(0, 0);
115
114
  }
116
115
 
117
116
  recordFailedBlock() {
@@ -21,7 +21,6 @@ import {
21
21
  type GlobalVariables,
22
22
  StateReference,
23
23
  } from '@aztec/circuits.js';
24
- import { prettyLogViemErrorMsg } from '@aztec/ethereum';
25
24
  import { AztecAddress } from '@aztec/foundation/aztec-address';
26
25
  import { omit } from '@aztec/foundation/collection';
27
26
  import { EthAddress } from '@aztec/foundation/eth-address';
@@ -37,7 +36,7 @@ import { Attributes, type TelemetryClient, type Tracer, getTelemetryClient, trac
37
36
  import { type ValidatorClient } from '@aztec/validator-client';
38
37
 
39
38
  import { type GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
40
- import { type L1Publisher, VoteType } from '../publisher/l1-publisher.js';
39
+ import { type SequencerPublisher, VoteType } from '../publisher/sequencer-publisher.js';
41
40
  import { type SlasherClient } from '../slasher/slasher_client.js';
42
41
  import { createValidatorsForBlockBuilding } from '../tx_validator/tx_validator_factory.js';
43
42
  import { getDefaultAllowedSetupFunctions } from './allowed.js';
@@ -69,7 +68,7 @@ export class Sequencer {
69
68
  private _coinbase = EthAddress.ZERO;
70
69
  private _feeRecipient = AztecAddress.ZERO;
71
70
  private state = SequencerState.STOPPED;
72
- private allowedInSetup: AllowedElement[] = getDefaultAllowedSetupFunctions();
71
+ private allowedInSetup: AllowedElement[] = [];
73
72
  private maxBlockSizeInBytes: number = 1024 * 1024;
74
73
  private maxBlockGas: Gas = new Gas(100e9, 100e9);
75
74
  private metrics: SequencerMetrics;
@@ -81,7 +80,7 @@ export class Sequencer {
81
80
  protected enforceTimeTable: boolean = false;
82
81
 
83
82
  constructor(
84
- protected publisher: L1Publisher,
83
+ protected publisher: SequencerPublisher,
85
84
  protected validatorClient: ValidatorClient | undefined, // During migration the validator client can be inactive
86
85
  protected globalsBuilder: GlobalVariableBuilder,
87
86
  protected p2pClient: P2P,
@@ -98,7 +97,6 @@ export class Sequencer {
98
97
  telemetry: TelemetryClient = getTelemetryClient(),
99
98
  protected log = createLogger('sequencer'),
100
99
  ) {
101
- this.updateConfig(config);
102
100
  this.metrics = new SequencerMetrics(telemetry, () => this.state, 'Sequencer');
103
101
 
104
102
  // Register the block builder with the validator client for re-execution
@@ -116,7 +114,7 @@ export class Sequencer {
116
114
  * Updates sequencer config.
117
115
  * @param config - New parameters.
118
116
  */
119
- public updateConfig(config: SequencerConfig) {
117
+ public async updateConfig(config: SequencerConfig) {
120
118
  this.log.info(`Sequencer config set`, omit(pickFromSchema(config, SequencerConfigSchema), 'allowedInSetup'));
121
119
 
122
120
  if (config.transactionPollingIntervalMS !== undefined) {
@@ -142,6 +140,8 @@ export class Sequencer {
142
140
  }
143
141
  if (config.allowedInSetup) {
144
142
  this.allowedInSetup = config.allowedInSetup;
143
+ } else {
144
+ this.allowedInSetup = await getDefaultAllowedSetupFunctions();
145
145
  }
146
146
  if (config.maxBlockSizeInBytes !== undefined) {
147
147
  this.maxBlockSizeInBytes = config.maxBlockSizeInBytes;
@@ -177,12 +177,12 @@ export class Sequencer {
177
177
  /**
178
178
  * Starts the sequencer and moves to IDLE state.
179
179
  */
180
- public start() {
180
+ public async start() {
181
+ await this.updateConfig(this.config);
181
182
  this.runningPromise = new RunningPromise(this.work.bind(this), this.log, this.pollingIntervalMs);
182
183
  this.setState(SequencerState.IDLE, 0n, true /** force */);
183
184
  this.runningPromise.start();
184
185
  this.log.info(`Sequencer started with address ${this.publisher.getSenderAddress().toString()}`);
185
- return Promise.resolve();
186
186
  }
187
187
 
188
188
  /**
@@ -216,6 +216,11 @@ export class Sequencer {
216
216
  return { state: this.state };
217
217
  }
218
218
 
219
+ /** Forces the sequencer to bypass all time and tx count checks for the next block and build anyway. */
220
+ public flush() {
221
+ this.isFlushing = true;
222
+ }
223
+
219
224
  /**
220
225
  * @notice Performs most of the sequencer duties:
221
226
  * - Checks if we are up to date
@@ -236,25 +241,20 @@ export class Sequencer {
236
241
  this.setState(SequencerState.PROPOSER_CHECK, 0n);
237
242
 
238
243
  const chainTip = await this.l2BlockSource.getBlock(-1);
239
- const historicalHeader = chainTip?.header;
240
244
 
241
- const newBlockNumber =
242
- (historicalHeader === undefined
243
- ? await this.l2BlockSource.getBlockNumber()
244
- : Number(historicalHeader.globalVariables.blockNumber.toBigInt())) + 1;
245
+ const newBlockNumber = (chainTip?.header.globalVariables.blockNumber.toNumber() ?? 0) + 1;
245
246
 
246
247
  // If we cannot find a tip archive, assume genesis.
247
- const chainTipArchive =
248
- chainTip == undefined ? new Fr(GENESIS_ARCHIVE_ROOT).toBuffer() : chainTip?.archive.root.toBuffer();
248
+ const chainTipArchive = chainTip?.archive.root ?? new Fr(GENESIS_ARCHIVE_ROOT);
249
249
 
250
- let slot: bigint;
251
- try {
252
- slot = await this.mayProposeBlock(chainTipArchive, BigInt(newBlockNumber));
253
- } catch (err) {
254
- this.log.debug(`Cannot propose for block ${newBlockNumber}`);
250
+ const slot = await this.slotForProposal(chainTipArchive.toBuffer(), BigInt(newBlockNumber));
251
+ if (!slot) {
252
+ this.log.debug(`Cannot propose block ${newBlockNumber}`);
255
253
  return;
256
254
  }
257
255
 
256
+ this.log.info(`Can propose block ${newBlockNumber} at slot ${slot}`);
257
+
258
258
  const newGlobalVariables = await this.globalsBuilder.buildGlobalVariables(
259
259
  new Fr(newBlockNumber),
260
260
  this._coinbase,
@@ -262,34 +262,30 @@ export class Sequencer {
262
262
  slot,
263
263
  );
264
264
 
265
- void this.publisher.castVote(slot, newGlobalVariables.timestamp.toBigInt(), VoteType.GOVERNANCE);
266
- void this.publisher.castVote(slot, newGlobalVariables.timestamp.toBigInt(), VoteType.SLASHING);
265
+ const enqueueGovernanceVotePromise = this.publisher.enqueueCastVote(
266
+ slot,
267
+ newGlobalVariables.timestamp.toBigInt(),
268
+ VoteType.GOVERNANCE,
269
+ );
270
+ const enqueueSlashingVotePromise = this.publisher.enqueueCastVote(
271
+ slot,
272
+ newGlobalVariables.timestamp.toBigInt(),
273
+ VoteType.SLASHING,
274
+ );
267
275
 
268
- // Check the pool has enough txs to build a block
269
- const pendingTxCount = this.p2pClient.getPendingTxCount();
270
- if (pendingTxCount < this.minTxsPerBlock && !this.isFlushing) {
271
- this.log.verbose(`Not enough txs to propose block. Got ${pendingTxCount} min ${this.minTxsPerBlock}.`, {
272
- slot,
273
- blockNumber: newBlockNumber,
274
- });
275
- await this.claimEpochProofRightIfAvailable(slot);
276
- return;
277
- }
276
+ // Start collecting proof quotes for the previous epoch if needed in the background
277
+ const createProofQuotePromise = this.createProofClaimForPreviousEpoch(slot);
278
278
 
279
279
  this.setState(SequencerState.INITIALIZING_PROPOSAL, slot);
280
280
  this.log.verbose(`Preparing proposal for block ${newBlockNumber} at slot ${slot}`, {
281
- chainTipArchive: new Fr(chainTipArchive),
281
+ chainTipArchive,
282
282
  blockNumber: newBlockNumber,
283
283
  slot,
284
284
  });
285
285
 
286
- // We don't fetch exactly maxTxsPerBlock txs here because we may not need all of them if we hit a limit before,
287
- // and also we may need to fetch more if we don't have enough valid txs.
288
- const pendingTxs = this.p2pClient.iteratePendingTxs();
289
-
290
286
  // If I created a "partial" header here that should make our job much easier.
291
287
  const proposalHeader = new BlockHeader(
292
- new AppendOnlyTreeSnapshot(Fr.fromBuffer(chainTipArchive), 1),
288
+ new AppendOnlyTreeSnapshot(chainTipArchive, 1),
293
289
  ContentCommitment.empty(),
294
290
  StateReference.empty(),
295
291
  newGlobalVariables,
@@ -297,15 +293,41 @@ export class Sequencer {
297
293
  Fr.ZERO,
298
294
  );
299
295
 
300
- try {
301
- // TODO(palla/txs) Is the note below still valid? We don't seem to be doing any rollback in there.
302
- // @note It is very important that the following function will FAIL and not just return early
303
- // if it have made any state changes. If not, we won't rollback the state, and you will
304
- // be in for a world of pain.
305
- await this.buildBlockAndAttemptToPublish(pendingTxs, proposalHeader, historicalHeader);
306
- } catch (err) {
307
- this.log.error(`Error assembling block`, err, { blockNumber: newBlockNumber, slot });
296
+ let finishedFlushing = false;
297
+ const pendingTxCount = await this.p2pClient.getPendingTxCount();
298
+ if (pendingTxCount >= this.minTxsPerBlock || this.isFlushing) {
299
+ // We don't fetch exactly maxTxsPerBlock txs here because we may not need all of them if we hit a limit before,
300
+ // and also we may need to fetch more if we don't have enough valid txs.
301
+ const pendingTxs = this.p2pClient.iteratePendingTxs();
302
+
303
+ await this.buildBlockAndEnqueuePublish(pendingTxs, proposalHeader).catch(err => {
304
+ this.log.error(`Error building/enqueuing block`, err, { blockNumber: newBlockNumber, slot });
305
+ });
306
+ finishedFlushing = true;
307
+ } else {
308
+ this.log.debug(
309
+ `Not enough txs to build block ${newBlockNumber} at slot ${slot}: got ${pendingTxCount} txs, need ${this.minTxsPerBlock}`,
310
+ );
311
+ }
312
+
313
+ await enqueueGovernanceVotePromise.catch(err => {
314
+ this.log.error(`Error enqueuing governance vote`, err, { blockNumber: newBlockNumber, slot });
315
+ });
316
+ await enqueueSlashingVotePromise.catch(err => {
317
+ this.log.error(`Error enqueuing slashing vote`, err, { blockNumber: newBlockNumber, slot });
318
+ });
319
+ await createProofQuotePromise
320
+ .then(quote => (quote ? this.publisher.enqueueClaimEpochProofRight(quote) : undefined))
321
+ .catch(err => {
322
+ this.log.error(`Error creating proof quote`, err, { blockNumber: newBlockNumber, slot });
323
+ });
324
+
325
+ await this.publisher.sendRequests();
326
+
327
+ if (finishedFlushing) {
328
+ this.isFlushing = false;
308
329
  }
330
+
309
331
  this.setState(SequencerState.IDLE, 0n);
310
332
  }
311
333
 
@@ -325,24 +347,31 @@ export class Sequencer {
325
347
  }
326
348
  }
327
349
 
328
- async mayProposeBlock(tipArchive: Buffer, proposalBlockNumber: bigint): Promise<bigint> {
329
- // This checks that we can propose, and gives us the slot that we are to propose for
330
- try {
331
- const [slot, blockNumber] = await this.publisher.canProposeAtNextEthBlock(tipArchive);
350
+ public getForwarderAddress() {
351
+ return this.publisher.getForwarderAddress();
352
+ }
332
353
 
333
- if (proposalBlockNumber !== blockNumber) {
334
- const msg = `Sequencer block number mismatch. Expected ${proposalBlockNumber} but got ${blockNumber}.`;
335
- this.log.warn(msg);
336
- throw new Error(msg);
337
- }
338
- return slot;
339
- } catch (err) {
340
- const msg = prettyLogViemErrorMsg(err);
341
- this.log.debug(
342
- `Rejected from being able to propose at next block with ${tipArchive.toString('hex')}: ${msg ? `${msg}` : ''}`,
343
- );
344
- throw err;
354
+ /**
355
+ * Checks if we can propose at the next block and returns the slot number if we can.
356
+ * @param tipArchive - The archive of the previous block.
357
+ * @param proposalBlockNumber - The block number of the proposal.
358
+ * @returns The slot number if we can propose at the next block, otherwise undefined.
359
+ */
360
+ async slotForProposal(tipArchive: Buffer, proposalBlockNumber: bigint): Promise<bigint | undefined> {
361
+ const result = await this.publisher.canProposeAtNextEthBlock(tipArchive);
362
+
363
+ if (!result) {
364
+ return undefined;
365
+ }
366
+
367
+ const [slot, blockNumber] = result;
368
+
369
+ if (proposalBlockNumber !== blockNumber) {
370
+ const msg = `Sequencer block number mismatch. Expected ${proposalBlockNumber} but got ${blockNumber}.`;
371
+ this.log.warn(msg);
372
+ throw new Error(msg);
345
373
  }
374
+ return slot;
346
375
  }
347
376
 
348
377
  /**
@@ -376,16 +405,14 @@ export class Sequencer {
376
405
  * @param opts - Whether to just validate the block as a validator, as opposed to building it as a proposal
377
406
  */
378
407
  protected async buildBlock(
379
- pendingTxs: Iterable<Tx>,
408
+ pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
380
409
  newGlobalVariables: GlobalVariables,
381
- historicalHeader?: BlockHeader,
382
410
  opts: { validateOnly?: boolean } = {},
383
411
  ) {
384
- const blockNumber = newGlobalVariables.blockNumber.toBigInt();
412
+ const blockNumber = newGlobalVariables.blockNumber.toNumber();
385
413
  const slot = newGlobalVariables.slotNumber.toBigInt();
386
-
387
414
  this.log.debug(`Requesting L1 to L2 messages from contract for block ${blockNumber}`);
388
- const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(blockNumber);
415
+ const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(BigInt(blockNumber));
389
416
  const msgCount = l1ToL2Messages.length;
390
417
 
391
418
  this.log.verbose(`Building block ${blockNumber} for slot ${slot}`, {
@@ -396,23 +423,21 @@ export class Sequencer {
396
423
  });
397
424
 
398
425
  // Sync to the previous block at least
399
- await this.worldState.syncImmediate(newGlobalVariables.blockNumber.toNumber() - 1);
400
- this.log.debug(`Synced to previous block ${newGlobalVariables.blockNumber.toNumber() - 1}`);
426
+ await this.worldState.syncImmediate(blockNumber - 1);
427
+ this.log.debug(`Synced to previous block ${blockNumber - 1}`);
401
428
 
402
429
  // NB: separating the dbs because both should update the state
403
430
  const publicProcessorFork = await this.worldState.fork();
404
431
  const orchestratorFork = await this.worldState.fork();
405
432
 
433
+ const previousBlockHeader =
434
+ (await this.l2BlockSource.getBlock(blockNumber - 1))?.header ?? orchestratorFork.getInitialHeader();
435
+
406
436
  try {
407
- const processor = this.publicProcessorFactory.create(
408
- publicProcessorFork,
409
- historicalHeader,
410
- newGlobalVariables,
411
- true,
412
- );
437
+ const processor = this.publicProcessorFactory.create(publicProcessorFork, newGlobalVariables, true);
413
438
  const blockBuildingTimer = new Timer();
414
439
  const blockBuilder = this.blockBuilderFactory.create(orchestratorFork);
415
- await blockBuilder.startNewBlock(newGlobalVariables, l1ToL2Messages);
440
+ await blockBuilder.startNewBlock(newGlobalVariables, l1ToL2Messages, previousBlockHeader);
416
441
 
417
442
  // Deadline for processing depends on whether we're proposing a block
418
443
  const secondsIntoSlot = this.getSecondsIntoSlot(slot);
@@ -455,8 +480,9 @@ export class Sequencer {
455
480
 
456
481
  if (!opts.validateOnly && failedTxs.length > 0) {
457
482
  const failedTxData = failedTxs.map(fail => fail.tx);
458
- this.log.verbose(`Dropping failed txs ${Tx.getHashes(failedTxData).join(', ')}`);
459
- await this.p2pClient.deleteTxs(Tx.getHashes(failedTxData));
483
+ const failedTxHashes = await Tx.getHashes(failedTxData);
484
+ this.log.verbose(`Dropping failed txs ${failedTxHashes.join(', ')}`);
485
+ await this.p2pClient.deleteTxs(failedTxHashes);
460
486
  }
461
487
 
462
488
  if (
@@ -497,6 +523,7 @@ export class Sequencer {
497
523
  // We create a fresh processor each time to reset any cached state (eg storage writes)
498
524
  // We wait a bit to close the forks since the processor may still be working on a dangling tx
499
525
  // which was interrupted due to the processingDeadline being hit.
526
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
500
527
  setTimeout(async () => {
501
528
  try {
502
529
  await publicProcessorFork.close();
@@ -517,15 +544,13 @@ export class Sequencer {
517
544
  *
518
545
  * @param pendingTxs - Iterable of pending transactions to construct the block from
519
546
  * @param proposalHeader - The partial header constructed for the proposal
520
- * @param historicalHeader - The historical header of the parent
521
547
  */
522
- @trackSpan('Sequencer.buildBlockAndAttemptToPublish', (_validTxs, proposalHeader, _historicalHeader) => ({
548
+ @trackSpan('Sequencer.buildBlockAndEnqueuePublish', (_validTxs, proposalHeader) => ({
523
549
  [Attributes.BLOCK_NUMBER]: proposalHeader.globalVariables.blockNumber.toNumber(),
524
550
  }))
525
- private async buildBlockAndAttemptToPublish(
526
- pendingTxs: Iterable<Tx>,
551
+ private async buildBlockAndEnqueuePublish(
552
+ pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
527
553
  proposalHeader: BlockHeader,
528
- historicalHeader: BlockHeader | undefined,
529
554
  ): Promise<void> {
530
555
  await this.publisher.validateBlockForSubmission(proposalHeader);
531
556
 
@@ -537,41 +562,38 @@ export class Sequencer {
537
562
  const workTimer = new Timer();
538
563
  this.setState(SequencerState.CREATING_BLOCK, slot);
539
564
 
540
- // Start collecting proof quotes for the previous epoch if needed in the background
541
- const proofQuotePromise = this.createProofClaimForPreviousEpoch(slot);
542
-
543
565
  try {
544
- const buildBlockRes = await this.buildBlock(pendingTxs, newGlobalVariables, historicalHeader);
566
+ const buildBlockRes = await this.buildBlock(pendingTxs, newGlobalVariables);
545
567
  const { publicGas, block, publicProcessorDuration, numTxs, numMsgs, blockBuildingTimer } = buildBlockRes;
568
+ this.metrics.recordBuiltBlock(workTimer.ms(), publicGas.l2Gas);
546
569
 
547
570
  // TODO(@PhilWindle) We should probably periodically check for things like another
548
571
  // block being published before ours instead of just waiting on our block
549
572
  await this.publisher.validateBlockForSubmission(block.header);
550
573
 
551
- const workDuration = workTimer.ms();
552
574
  const blockStats: L2BlockBuiltStats = {
553
575
  eventName: 'l2-block-built',
554
576
  creator: this.publisher.getSenderAddress().toString(),
555
- duration: workDuration,
577
+ duration: workTimer.ms(),
556
578
  publicProcessDuration: publicProcessorDuration,
557
579
  rollupCircuitsDuration: blockBuildingTimer.ms(),
558
580
  ...block.getStats(),
559
581
  };
560
582
 
561
- const blockHash = block.hash();
583
+ const blockHash = await block.hash();
562
584
  const txHashes = block.body.txEffects.map(tx => tx.txHash);
563
- this.log.info(`Built block ${block.number} for slot ${slot} with ${numTxs} txs`, {
564
- blockHash,
565
- globalVariables: block.header.globalVariables.toInspect(),
566
- txHashes,
567
- ...blockStats,
568
- });
569
-
570
- if (this.isFlushing) {
571
- this.log.verbose(`Sequencer flushing completed`);
572
- }
585
+ this.log.info(
586
+ `Built block ${block.number} for slot ${slot} with ${numTxs} txs and ${numMsgs} messages. ${
587
+ publicGas.l2Gas / workTimer.s()
588
+ } mana/s`,
589
+ {
590
+ blockHash,
591
+ globalVariables: block.header.globalVariables.toInspect(),
592
+ txHashes,
593
+ ...blockStats,
594
+ },
595
+ );
573
596
 
574
- this.isFlushing = false;
575
597
  this.log.debug('Collecting attestations');
576
598
  const stopCollectingAttestationsTimer = this.metrics.startCollectingAttestationsTimer();
577
599
  const attestations = await this.collectAttestations(block, txHashes);
@@ -580,37 +602,13 @@ export class Sequencer {
580
602
  }
581
603
  stopCollectingAttestationsTimer();
582
604
 
583
- // Get the proof quote for the previous epoch, if any
584
- const proofQuote = await proofQuotePromise;
585
-
586
- await this.publishL2Block(block, attestations, txHashes, proofQuote);
587
- this.metrics.recordPublishedBlock(workDuration, publicGas.l2Gas);
588
- const duration = Math.ceil(workDuration);
589
- const manaPerSecond = Math.ceil((publicGas.l2Gas * 1000) / duration);
590
- this.log.info(
591
- `Published block ${block.number} with ${numTxs} txs and ${numMsgs} messages in ${duration} ms at ${manaPerSecond} mana/s`,
592
- {
593
- publicGas,
594
- blockNumber: block.number,
595
- blockHash: blockHash,
596
- slot,
597
- txCount: txHashes.length,
598
- msgCount: numMsgs,
599
- duration,
600
- submitter: this.publisher.getSenderAddress().toString(),
601
- },
602
- );
605
+ return this.enqueuePublishL2Block(block, attestations, txHashes);
603
606
  } catch (err) {
604
607
  this.metrics.recordFailedBlock();
605
608
  throw err;
606
609
  }
607
610
  }
608
611
 
609
- /** Forces the sequencer to bypass all time and tx count checks for the next block and build anyway. */
610
- public flush() {
611
- this.isFlushing = true;
612
- }
613
-
614
612
  @trackSpan('Sequencer.collectAttestations', (block, txHashes) => ({
615
613
  [Attributes.BLOCK_NUMBER]: block.number,
616
614
  [Attributes.BLOCK_ARCHIVE]: block.archive.toString(),
@@ -640,8 +638,8 @@ export class Sequencer {
640
638
  this.log.debug('Creating block proposal for validators');
641
639
  const proposal = await this.validatorClient.createBlockProposal(block.header, block.archive.root, txHashes);
642
640
  if (!proposal) {
643
- this.log.warn(`Failed to create block proposal, skipping collecting attestations`);
644
- return undefined;
641
+ const msg = `Failed to create block proposal`;
642
+ throw new Error(msg);
645
643
  }
646
644
 
647
645
  this.log.debug('Broadcasting block proposal to validators');
@@ -665,28 +663,33 @@ export class Sequencer {
665
663
  try {
666
664
  // Find out which epoch we are currently in
667
665
  const epochToProve = await this.publisher.getClaimableEpoch();
666
+
668
667
  if (epochToProve === undefined) {
669
- this.log.trace(`No epoch to prove at slot ${slotNumber}`);
668
+ this.log.trace(`No epoch to claim at slot ${slotNumber}`);
670
669
  return undefined;
671
670
  }
672
671
 
673
672
  // Get quotes for the epoch to be proven
674
673
  this.log.debug(`Collecting proof quotes for epoch ${epochToProve}`);
675
- const quotes = await this.p2pClient.getEpochProofQuotes(epochToProve);
676
- this.log.verbose(`Retrieved ${quotes.length} quotes for slot ${slotNumber} epoch ${epochToProve}`, {
674
+ const p2pQuotes = await this.p2pClient
675
+ .getEpochProofQuotes(epochToProve)
676
+ .then(quotes =>
677
+ quotes
678
+ .filter(x => x.payload.validUntilSlot >= slotNumber)
679
+ .filter(x => x.payload.epochToProve === epochToProve),
680
+ );
681
+ this.log.verbose(`Retrieved ${p2pQuotes.length} quotes for slot ${slotNumber} epoch ${epochToProve}`, {
677
682
  epochToProve,
678
683
  slotNumber,
679
- quotes: quotes.map(q => q.payload),
684
+ quotes: p2pQuotes.map(q => q.payload),
680
685
  });
686
+ if (!p2pQuotes.length) {
687
+ return undefined;
688
+ }
689
+
681
690
  // ensure these quotes are still valid for the slot and have the contract validate them
682
- const validQuotesPromise = Promise.all(
683
- quotes
684
- .filter(x => x.payload.validUntilSlot >= slotNumber)
685
- .filter(x => x.payload.epochToProve === epochToProve)
686
- .map(x => this.publisher.validateProofQuote(x)),
687
- );
691
+ const validQuotes = await this.publisher.filterValidQuotes(p2pQuotes);
688
692
 
689
- const validQuotes = (await validQuotesPromise).filter((q): q is EpochProofQuote => !!q);
690
693
  if (!validQuotes.length) {
691
694
  this.log.warn(`Failed to find any valid proof quotes`);
692
695
  return undefined;
@@ -708,15 +711,14 @@ export class Sequencer {
708
711
  * Publishes the L2Block to the rollup contract.
709
712
  * @param block - The L2Block to be published.
710
713
  */
711
- @trackSpan('Sequencer.publishL2Block', block => ({
714
+ @trackSpan('Sequencer.enqueuePublishL2Block', block => ({
712
715
  [Attributes.BLOCK_NUMBER]: block.number,
713
716
  }))
714
- protected async publishL2Block(
717
+ protected async enqueuePublishL2Block(
715
718
  block: L2Block,
716
719
  attestations?: Signature[],
717
720
  txHashes?: TxHash[],
718
- proofQuote?: EpochProofQuote,
719
- ) {
721
+ ): Promise<void> {
720
722
  // Publishes new block to the network and awaits the tx to be mined
721
723
  this.setState(SequencerState.PUBLISHING_BLOCK, block.header.globalVariables.slotNumber.toBigInt());
722
724
 
@@ -724,11 +726,12 @@ export class Sequencer {
724
726
  const slot = block.header.globalVariables.slotNumber.toNumber();
725
727
  const txTimeoutAt = new Date((this.getSlotStartTimestamp(slot) + this.aztecSlotDuration) * 1000);
726
728
 
727
- const publishedL2Block = await this.publisher.proposeL2Block(block, attestations, txHashes, proofQuote, {
729
+ const enqueued = await this.publisher.enqueueProposeL2Block(block, attestations, txHashes, {
728
730
  txTimeoutAt,
729
731
  });
730
- if (!publishedL2Block) {
731
- throw new Error(`Failed to publish block ${block.number}`);
732
+
733
+ if (!enqueued) {
734
+ throw new Error(`Failed to enqueue publish of block ${block.number}`);
732
735
  }
733
736
  }
734
737
 
@@ -747,11 +750,11 @@ export class Sequencer {
747
750
  const epoch = proofQuote.payload.epochToProve;
748
751
  const ctx = { slotNumber, epoch, quote: proofQuote.toInspect() };
749
752
  this.log.verbose(`Claiming proof right for epoch ${epoch}`, ctx);
750
- const success = await this.publisher.claimEpochProofRight(proofQuote);
751
- if (!success) {
752
- throw new Error(`Failed to claim proof right for epoch ${epoch}`);
753
+ const enqueued = this.publisher.enqueueClaimEpochProofRight(proofQuote);
754
+ if (!enqueued) {
755
+ throw new Error(`Failed to enqueue claim of proof right for epoch ${epoch}`);
753
756
  }
754
- this.log.info(`Claimed proof right for epoch ${epoch}`, ctx);
757
+ this.log.info(`Enqueued claim of proof right for epoch ${epoch}`, ctx);
755
758
  return epoch;
756
759
  }
757
760
 
@@ -49,12 +49,15 @@ export function sequencerStateToNumber(state: SequencerState): number {
49
49
  *
50
50
  * @todo: perform this logic within the memory attestation store instead?
51
51
  */
52
- export function orderAttestations(attestations: BlockAttestation[], orderAddresses: EthAddress[]): Signature[] {
52
+ export async function orderAttestations(
53
+ attestations: BlockAttestation[],
54
+ orderAddresses: EthAddress[],
55
+ ): Promise<Signature[]> {
53
56
  // Create a map of sender addresses to BlockAttestations
54
57
  const attestationMap = new Map<string, BlockAttestation>();
55
58
 
56
59
  for (const attestation of attestations) {
57
- const sender = attestation.getSender();
60
+ const sender = await attestation.getSender();
58
61
  if (sender) {
59
62
  attestationMap.set(sender.toString(), attestation);
60
63
  }
@@ -315,7 +315,10 @@ export class SlasherClient extends WithTracer {
315
315
  const blockHash =
316
316
  blockNumber == 0
317
317
  ? ''
318
- : await this.l2BlockSource.getBlockHeader(blockNumber).then(header => header?.hash().toString());
318
+ : await this.l2BlockSource
319
+ .getBlockHeader(blockNumber)
320
+ .then(header => header?.hash())
321
+ .then(hash => hash?.toString());
319
322
  return Promise.resolve({
320
323
  state: this.currentState,
321
324
  syncedToL2Block: { number: blockNumber, hash: blockHash },
@@ -333,7 +336,9 @@ export class SlasherClient extends WithTracer {
333
336
  }
334
337
 
335
338
  const lastBlockNum = blocks[blocks.length - 1].number;
336
- await Promise.all(blocks.map(block => this.synchedBlockHashes.set(block.number, block.hash().toString())));
339
+ await Promise.all(
340
+ blocks.map(async block => this.synchedBlockHashes.set(block.number, (await block.hash()).toString())),
341
+ );
337
342
  await this.synchedLatestBlockNumber.set(lastBlockNum);
338
343
  this.log.debug(`Synched to latest block ${lastBlockNum}`);
339
344
  this.startServiceIfSynched();
package/src/test/index.ts CHANGED
@@ -1,14 +1,14 @@
1
1
  import { type PublicProcessorFactory } from '@aztec/simulator/server';
2
2
 
3
3
  import { SequencerClient } from '../client/sequencer-client.js';
4
- import { type L1Publisher } from '../publisher/l1-publisher.js';
4
+ import { type SequencerPublisher } from '../publisher/sequencer-publisher.js';
5
5
  import { Sequencer } from '../sequencer/sequencer.js';
6
6
  import { type SequencerTimetable } from '../sequencer/timetable.js';
7
7
 
8
8
  class TestSequencer_ extends Sequencer {
9
9
  public override publicProcessorFactory!: PublicProcessorFactory;
10
10
  public override timetable!: SequencerTimetable;
11
- public override publisher!: L1Publisher;
11
+ public override publisher!: SequencerPublisher;
12
12
  }
13
13
 
14
14
  export type TestSequencer = TestSequencer_;
@@ -18,5 +18,3 @@ class TestSequencerClient_ extends SequencerClient {
18
18
  }
19
19
 
20
20
  export type TestSequencerClient = TestSequencerClient_;
21
-
22
- export * from './test-l1-publisher.js';