@aztec/sequencer-client 0.72.1 → 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 (65) 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 +4 -4
  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 +17 -10
  26. package/dest/sequencer/sequencer.d.ts.map +1 -1
  27. package/dest/sequencer/sequencer.js +103 -109
  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.map +1 -1
  37. package/dest/tx_validator/gas_validator.js +4 -3
  38. package/dest/tx_validator/test_utils.d.ts +4 -4
  39. package/dest/tx_validator/test_utils.d.ts.map +1 -1
  40. package/dest/tx_validator/test_utils.js +3 -3
  41. package/package.json +21 -20
  42. package/src/client/sequencer-client.ts +60 -14
  43. package/src/config.ts +3 -3
  44. package/src/publisher/config.ts +13 -1
  45. package/src/publisher/index.ts +1 -1
  46. package/src/publisher/{l1-publisher-metrics.ts → sequencer-publisher-metrics.ts} +41 -2
  47. package/src/publisher/sequencer-publisher.ts +730 -0
  48. package/src/sequencer/allowed.ts +3 -3
  49. package/src/sequencer/metrics.ts +2 -3
  50. package/src/sequencer/sequencer.ts +138 -125
  51. package/src/sequencer/utils.ts +5 -2
  52. package/src/slasher/slasher_client.ts +7 -2
  53. package/src/test/index.ts +2 -4
  54. package/src/tx_validator/gas_validator.ts +5 -4
  55. package/src/tx_validator/test_utils.ts +5 -5
  56. package/dest/publisher/l1-publisher-metrics.d.ts.map +0 -1
  57. package/dest/publisher/l1-publisher-metrics.js +0 -85
  58. package/dest/publisher/l1-publisher.d.ts +0 -195
  59. package/dest/publisher/l1-publisher.d.ts.map +0 -1
  60. package/dest/publisher/l1-publisher.js +0 -930
  61. package/dest/test/test-l1-publisher.d.ts +0 -9
  62. package/dest/test/test-l1-publisher.d.ts.map +0 -1
  63. package/dest/test/test-l1-publisher.js +0 -11
  64. package/src/publisher/l1-publisher.ts +0 -1288
  65. 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
@@ -21,12 +21,12 @@ export function getDefaultAllowedSetupFunctions(): AllowedElement[] {
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
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
@@ -242,14 +247,14 @@ export class Sequencer {
242
247
  // If we cannot find a tip archive, assume genesis.
243
248
  const chainTipArchive = chainTip?.archive.root ?? new Fr(GENESIS_ARCHIVE_ROOT);
244
249
 
245
- let slot: bigint;
246
- try {
247
- slot = await this.mayProposeBlock(chainTipArchive.toBuffer(), BigInt(newBlockNumber));
248
- } catch (err) {
249
- 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}`);
250
253
  return;
251
254
  }
252
255
 
256
+ this.log.info(`Can propose block ${newBlockNumber} at slot ${slot}`);
257
+
253
258
  const newGlobalVariables = await this.globalsBuilder.buildGlobalVariables(
254
259
  new Fr(newBlockNumber),
255
260
  this._coinbase,
@@ -257,19 +262,19 @@ export class Sequencer {
257
262
  slot,
258
263
  );
259
264
 
260
- void this.publisher.castVote(slot, newGlobalVariables.timestamp.toBigInt(), VoteType.GOVERNANCE);
261
- 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
+ );
262
275
 
263
- // Check the pool has enough txs to build a block
264
- const pendingTxCount = this.p2pClient.getPendingTxCount();
265
- if (pendingTxCount < this.minTxsPerBlock && !this.isFlushing) {
266
- this.log.verbose(`Not enough txs to propose block. Got ${pendingTxCount} min ${this.minTxsPerBlock}.`, {
267
- slot,
268
- blockNumber: newBlockNumber,
269
- });
270
- await this.claimEpochProofRightIfAvailable(slot);
271
- return;
272
- }
276
+ // Start collecting proof quotes for the previous epoch if needed in the background
277
+ const createProofQuotePromise = this.createProofClaimForPreviousEpoch(slot);
273
278
 
274
279
  this.setState(SequencerState.INITIALIZING_PROPOSAL, slot);
275
280
  this.log.verbose(`Preparing proposal for block ${newBlockNumber} at slot ${slot}`, {
@@ -278,10 +283,6 @@ export class Sequencer {
278
283
  slot,
279
284
  });
280
285
 
281
- // We don't fetch exactly maxTxsPerBlock txs here because we may not need all of them if we hit a limit before,
282
- // and also we may need to fetch more if we don't have enough valid txs.
283
- const pendingTxs = this.p2pClient.iteratePendingTxs();
284
-
285
286
  // If I created a "partial" header here that should make our job much easier.
286
287
  const proposalHeader = new BlockHeader(
287
288
  new AppendOnlyTreeSnapshot(chainTipArchive, 1),
@@ -292,18 +293,41 @@ export class Sequencer {
292
293
  Fr.ZERO,
293
294
  );
294
295
 
295
- try {
296
- // TODO(palla/txs) Is the note below still valid? We don't seem to be doing any rollback in there.
297
- // @note It is very important that the following function will FAIL and not just return early
298
- // if it have made any state changes. If not, we won't rollback the state, and you will
299
- // be in for a world of pain.
300
- await this.buildBlockAndAttemptToPublish(pendingTxs, proposalHeader);
301
- } catch (err) {
302
- 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();
303
302
 
304
- // If the block failed to build, we might still want to claim the proving rights
305
- await this.claimEpochProofRightIfAvailable(slot);
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
+ );
306
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;
329
+ }
330
+
307
331
  this.setState(SequencerState.IDLE, 0n);
308
332
  }
309
333
 
@@ -323,24 +347,31 @@ export class Sequencer {
323
347
  }
324
348
  }
325
349
 
326
- async mayProposeBlock(tipArchive: Buffer, proposalBlockNumber: bigint): Promise<bigint> {
327
- // This checks that we can propose, and gives us the slot that we are to propose for
328
- try {
329
- const [slot, blockNumber] = await this.publisher.canProposeAtNextEthBlock(tipArchive);
350
+ public getForwarderAddress() {
351
+ return this.publisher.getForwarderAddress();
352
+ }
330
353
 
331
- if (proposalBlockNumber !== blockNumber) {
332
- const msg = `Sequencer block number mismatch. Expected ${proposalBlockNumber} but got ${blockNumber}.`;
333
- this.log.warn(msg);
334
- throw new Error(msg);
335
- }
336
- return slot;
337
- } catch (err) {
338
- const msg = prettyLogViemErrorMsg(err);
339
- this.log.debug(
340
- `Rejected from being able to propose at next block with ${tipArchive.toString('hex')}: ${msg ? `${msg}` : ''}`,
341
- );
342
- 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);
343
373
  }
374
+ return slot;
344
375
  }
345
376
 
346
377
  /**
@@ -374,13 +405,12 @@ export class Sequencer {
374
405
  * @param opts - Whether to just validate the block as a validator, as opposed to building it as a proposal
375
406
  */
376
407
  protected async buildBlock(
377
- pendingTxs: Iterable<Tx>,
408
+ pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
378
409
  newGlobalVariables: GlobalVariables,
379
410
  opts: { validateOnly?: boolean } = {},
380
411
  ) {
381
412
  const blockNumber = newGlobalVariables.blockNumber.toNumber();
382
413
  const slot = newGlobalVariables.slotNumber.toBigInt();
383
-
384
414
  this.log.debug(`Requesting L1 to L2 messages from contract for block ${blockNumber}`);
385
415
  const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(BigInt(blockNumber));
386
416
  const msgCount = l1ToL2Messages.length;
@@ -450,8 +480,9 @@ export class Sequencer {
450
480
 
451
481
  if (!opts.validateOnly && failedTxs.length > 0) {
452
482
  const failedTxData = failedTxs.map(fail => fail.tx);
453
- this.log.verbose(`Dropping failed txs ${Tx.getHashes(failedTxData).join(', ')}`);
454
- 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);
455
486
  }
456
487
 
457
488
  if (
@@ -492,6 +523,7 @@ export class Sequencer {
492
523
  // We create a fresh processor each time to reset any cached state (eg storage writes)
493
524
  // We wait a bit to close the forks since the processor may still be working on a dangling tx
494
525
  // which was interrupted due to the processingDeadline being hit.
526
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
495
527
  setTimeout(async () => {
496
528
  try {
497
529
  await publicProcessorFork.close();
@@ -513,10 +545,13 @@ export class Sequencer {
513
545
  * @param pendingTxs - Iterable of pending transactions to construct the block from
514
546
  * @param proposalHeader - The partial header constructed for the proposal
515
547
  */
516
- @trackSpan('Sequencer.buildBlockAndAttemptToPublish', (_validTxs, proposalHeader) => ({
548
+ @trackSpan('Sequencer.buildBlockAndEnqueuePublish', (_validTxs, proposalHeader) => ({
517
549
  [Attributes.BLOCK_NUMBER]: proposalHeader.globalVariables.blockNumber.toNumber(),
518
550
  }))
519
- private async buildBlockAndAttemptToPublish(pendingTxs: Iterable<Tx>, proposalHeader: BlockHeader): Promise<void> {
551
+ private async buildBlockAndEnqueuePublish(
552
+ pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
553
+ proposalHeader: BlockHeader,
554
+ ): Promise<void> {
520
555
  await this.publisher.validateBlockForSubmission(proposalHeader);
521
556
 
522
557
  const newGlobalVariables = proposalHeader.globalVariables;
@@ -527,41 +562,38 @@ export class Sequencer {
527
562
  const workTimer = new Timer();
528
563
  this.setState(SequencerState.CREATING_BLOCK, slot);
529
564
 
530
- // Start collecting proof quotes for the previous epoch if needed in the background
531
- const proofQuotePromise = this.createProofClaimForPreviousEpoch(slot);
532
-
533
565
  try {
534
566
  const buildBlockRes = await this.buildBlock(pendingTxs, newGlobalVariables);
535
567
  const { publicGas, block, publicProcessorDuration, numTxs, numMsgs, blockBuildingTimer } = buildBlockRes;
568
+ this.metrics.recordBuiltBlock(workTimer.ms(), publicGas.l2Gas);
536
569
 
537
570
  // TODO(@PhilWindle) We should probably periodically check for things like another
538
571
  // block being published before ours instead of just waiting on our block
539
572
  await this.publisher.validateBlockForSubmission(block.header);
540
573
 
541
- const workDuration = workTimer.ms();
542
574
  const blockStats: L2BlockBuiltStats = {
543
575
  eventName: 'l2-block-built',
544
576
  creator: this.publisher.getSenderAddress().toString(),
545
- duration: workDuration,
577
+ duration: workTimer.ms(),
546
578
  publicProcessDuration: publicProcessorDuration,
547
579
  rollupCircuitsDuration: blockBuildingTimer.ms(),
548
580
  ...block.getStats(),
549
581
  };
550
582
 
551
- const blockHash = block.hash();
583
+ const blockHash = await block.hash();
552
584
  const txHashes = block.body.txEffects.map(tx => tx.txHash);
553
- this.log.info(`Built block ${block.number} for slot ${slot} with ${numTxs} txs`, {
554
- blockHash,
555
- globalVariables: block.header.globalVariables.toInspect(),
556
- txHashes,
557
- ...blockStats,
558
- });
559
-
560
- if (this.isFlushing) {
561
- this.log.verbose(`Sequencer flushing completed`);
562
- }
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
+ );
563
596
 
564
- this.isFlushing = false;
565
597
  this.log.debug('Collecting attestations');
566
598
  const stopCollectingAttestationsTimer = this.metrics.startCollectingAttestationsTimer();
567
599
  const attestations = await this.collectAttestations(block, txHashes);
@@ -570,37 +602,13 @@ export class Sequencer {
570
602
  }
571
603
  stopCollectingAttestationsTimer();
572
604
 
573
- // Get the proof quote for the previous epoch, if any
574
- const proofQuote = await proofQuotePromise;
575
-
576
- await this.publishL2Block(block, attestations, txHashes, proofQuote);
577
- this.metrics.recordPublishedBlock(workDuration, publicGas.l2Gas);
578
- const duration = Math.ceil(workDuration);
579
- const manaPerSecond = Math.ceil((publicGas.l2Gas * 1000) / duration);
580
- this.log.info(
581
- `Published block ${block.number} with ${numTxs} txs and ${numMsgs} messages in ${duration} ms at ${manaPerSecond} mana/s`,
582
- {
583
- publicGas,
584
- blockNumber: block.number,
585
- blockHash: blockHash,
586
- slot,
587
- txCount: txHashes.length,
588
- msgCount: numMsgs,
589
- duration,
590
- submitter: this.publisher.getSenderAddress().toString(),
591
- },
592
- );
605
+ return this.enqueuePublishL2Block(block, attestations, txHashes);
593
606
  } catch (err) {
594
607
  this.metrics.recordFailedBlock();
595
608
  throw err;
596
609
  }
597
610
  }
598
611
 
599
- /** Forces the sequencer to bypass all time and tx count checks for the next block and build anyway. */
600
- public flush() {
601
- this.isFlushing = true;
602
- }
603
-
604
612
  @trackSpan('Sequencer.collectAttestations', (block, txHashes) => ({
605
613
  [Attributes.BLOCK_NUMBER]: block.number,
606
614
  [Attributes.BLOCK_ARCHIVE]: block.archive.toString(),
@@ -655,28 +663,33 @@ export class Sequencer {
655
663
  try {
656
664
  // Find out which epoch we are currently in
657
665
  const epochToProve = await this.publisher.getClaimableEpoch();
666
+
658
667
  if (epochToProve === undefined) {
659
- this.log.trace(`No epoch to prove at slot ${slotNumber}`);
668
+ this.log.trace(`No epoch to claim at slot ${slotNumber}`);
660
669
  return undefined;
661
670
  }
662
671
 
663
672
  // Get quotes for the epoch to be proven
664
673
  this.log.debug(`Collecting proof quotes for epoch ${epochToProve}`);
665
- const quotes = await this.p2pClient.getEpochProofQuotes(epochToProve);
666
- 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}`, {
667
682
  epochToProve,
668
683
  slotNumber,
669
- quotes: quotes.map(q => q.payload),
684
+ quotes: p2pQuotes.map(q => q.payload),
670
685
  });
686
+ if (!p2pQuotes.length) {
687
+ return undefined;
688
+ }
689
+
671
690
  // ensure these quotes are still valid for the slot and have the contract validate them
672
- const validQuotesPromise = Promise.all(
673
- quotes
674
- .filter(x => x.payload.validUntilSlot >= slotNumber)
675
- .filter(x => x.payload.epochToProve === epochToProve)
676
- .map(x => this.publisher.validateProofQuote(x)),
677
- );
691
+ const validQuotes = await this.publisher.filterValidQuotes(p2pQuotes);
678
692
 
679
- const validQuotes = (await validQuotesPromise).filter((q): q is EpochProofQuote => !!q);
680
693
  if (!validQuotes.length) {
681
694
  this.log.warn(`Failed to find any valid proof quotes`);
682
695
  return undefined;
@@ -698,15 +711,14 @@ export class Sequencer {
698
711
  * Publishes the L2Block to the rollup contract.
699
712
  * @param block - The L2Block to be published.
700
713
  */
701
- @trackSpan('Sequencer.publishL2Block', block => ({
714
+ @trackSpan('Sequencer.enqueuePublishL2Block', block => ({
702
715
  [Attributes.BLOCK_NUMBER]: block.number,
703
716
  }))
704
- protected async publishL2Block(
717
+ protected async enqueuePublishL2Block(
705
718
  block: L2Block,
706
719
  attestations?: Signature[],
707
720
  txHashes?: TxHash[],
708
- proofQuote?: EpochProofQuote,
709
- ) {
721
+ ): Promise<void> {
710
722
  // Publishes new block to the network and awaits the tx to be mined
711
723
  this.setState(SequencerState.PUBLISHING_BLOCK, block.header.globalVariables.slotNumber.toBigInt());
712
724
 
@@ -714,11 +726,12 @@ export class Sequencer {
714
726
  const slot = block.header.globalVariables.slotNumber.toNumber();
715
727
  const txTimeoutAt = new Date((this.getSlotStartTimestamp(slot) + this.aztecSlotDuration) * 1000);
716
728
 
717
- const publishedL2Block = await this.publisher.proposeL2Block(block, attestations, txHashes, proofQuote, {
729
+ const enqueued = await this.publisher.enqueueProposeL2Block(block, attestations, txHashes, {
718
730
  txTimeoutAt,
719
731
  });
720
- if (!publishedL2Block) {
721
- 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}`);
722
735
  }
723
736
  }
724
737
 
@@ -737,11 +750,11 @@ export class Sequencer {
737
750
  const epoch = proofQuote.payload.epochToProve;
738
751
  const ctx = { slotNumber, epoch, quote: proofQuote.toInspect() };
739
752
  this.log.verbose(`Claiming proof right for epoch ${epoch}`, ctx);
740
- const success = await this.publisher.claimEpochProofRight(proofQuote);
741
- if (!success) {
742
- 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}`);
743
756
  }
744
- this.log.info(`Claimed proof right for epoch ${epoch}`, ctx);
757
+ this.log.info(`Enqueued claim of proof right for epoch ${epoch}`, ctx);
745
758
  return epoch;
746
759
  }
747
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';
@@ -74,20 +74,21 @@ export class GasTxValidator implements TxValidator<Tx> {
74
74
  // Read current balance of the feePayer
75
75
  const initialBalance = await this.#publicDataSource.storageRead(
76
76
  this.#feeJuiceAddress,
77
- computeFeePayerBalanceStorageSlot(feePayer),
77
+ await computeFeePayerBalanceStorageSlot(feePayer),
78
78
  );
79
79
 
80
80
  // If there is a claim in this tx that increases the fee payer balance in Fee Juice, add it to balance
81
81
  const setupFns = getExecutionRequestsByPhase(tx, TxExecutionPhase.SETUP);
82
+ const increasePublicBalanceSelector = await FunctionSelector.fromSignature(
83
+ '_increase_public_balance((Field),(Field,Field))',
84
+ );
82
85
  const claimFunctionCall = setupFns.find(
83
86
  fn =>
84
87
  fn.callContext.contractAddress.equals(this.#feeJuiceAddress) &&
85
88
  fn.callContext.msgSender.equals(this.#feeJuiceAddress) &&
86
89
  fn.args.length > 2 &&
87
90
  // Public functions get routed through the dispatch function, whose first argument is the target function selector.
88
- fn.args[0].equals(
89
- FunctionSelector.fromSignature('_increase_public_balance((Field),(Field,Field))').toField(),
90
- ) &&
91
+ fn.args[0].equals(increasePublicBalanceSelector.toField()) &&
91
92
  fn.args[1].equals(feePayer.toField()) &&
92
93
  !fn.callContext.isStaticCall,
93
94
  );
@@ -6,7 +6,7 @@ export function patchNonRevertibleFn(
6
6
  tx: Tx,
7
7
  index: number,
8
8
  overrides: { address?: AztecAddress; selector: FunctionSelector; args?: Fr[]; msgSender?: AztecAddress },
9
- ): { address: AztecAddress; selector: FunctionSelector } {
9
+ ): Promise<{ address: AztecAddress; selector: FunctionSelector }> {
10
10
  return patchFn('nonRevertibleAccumulatedData', tx, index, overrides);
11
11
  }
12
12
 
@@ -14,16 +14,16 @@ export function patchRevertibleFn(
14
14
  tx: Tx,
15
15
  index: number,
16
16
  overrides: { address?: AztecAddress; selector: FunctionSelector; args?: Fr[]; msgSender?: AztecAddress },
17
- ): { address: AztecAddress; selector: FunctionSelector } {
17
+ ): Promise<{ address: AztecAddress; selector: FunctionSelector }> {
18
18
  return patchFn('revertibleAccumulatedData', tx, index, overrides);
19
19
  }
20
20
 
21
- function patchFn(
21
+ async function patchFn(
22
22
  where: 'revertibleAccumulatedData' | 'nonRevertibleAccumulatedData',
23
23
  tx: Tx,
24
24
  index: number,
25
25
  overrides: { address?: AztecAddress; selector: FunctionSelector; args?: Fr[]; msgSender?: AztecAddress },
26
- ): { address: AztecAddress; selector: FunctionSelector } {
26
+ ): Promise<{ address: AztecAddress; selector: FunctionSelector }> {
27
27
  const fn = tx.enqueuedPublicFunctionCalls.at(-1 * index - 1)!;
28
28
  fn.callContext.contractAddress = overrides.address ?? fn.callContext.contractAddress;
29
29
  fn.callContext.functionSelector = overrides.selector;
@@ -36,7 +36,7 @@ function patchFn(
36
36
  request.msgSender = fn.callContext.msgSender;
37
37
  request.functionSelector = fn.callContext.functionSelector;
38
38
  request.isStaticCall = fn.callContext.isStaticCall;
39
- request.argsHash = computeVarArgsHash(fn.args);
39
+ request.argsHash = await computeVarArgsHash(fn.args);
40
40
  tx.data.forPublic![where].publicCallRequests[index] = request;
41
41
 
42
42
  return {
@@ -1 +0,0 @@
1
- {"version":3,"file":"l1-publisher-metrics.d.ts","sourceRoot":"","sources":["../../src/publisher/l1-publisher-metrics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC3G,OAAO,EAIL,KAAK,eAAe,EAGrB,MAAM,yBAAyB,CAAC;AAIjC,MAAM,MAAM,QAAQ,GAAG,aAAa,GAAG,SAAS,GAAG,sBAAsB,CAAC;AAE1E,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAY;IAE5B,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,UAAU,CAAY;IAC9B,OAAO,CAAC,KAAK,CAAY;IACzB,OAAO,CAAC,cAAc,CAAY;IAClC,OAAO,CAAC,aAAa,CAAY;IACjC,OAAO,CAAC,iBAAiB,CAAY;IACrC,OAAO,CAAC,iBAAiB,CAAY;gBAEzB,MAAM,EAAE,eAAe,EAAE,IAAI,SAAgB;IAkDzD,cAAc,CAAC,MAAM,EAAE,QAAQ;IAO/B,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB;IAIhE,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB;IAInE,4BAA4B,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc;IAItE,OAAO,CAAC,QAAQ;CA6BjB"}