@aztec/sequencer-client 0.51.1 → 0.53.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.
@@ -17,7 +17,6 @@ import {
17
17
  EthAddress,
18
18
  GENESIS_ARCHIVE_ROOT,
19
19
  Header,
20
- IS_DEV_NET,
21
20
  StateReference,
22
21
  } from '@aztec/circuits.js';
23
22
  import { Fr } from '@aztec/foundation/fields';
@@ -183,91 +182,99 @@ export class Sequencer {
183
182
  }
184
183
 
185
184
  /**
186
- * Grabs up to maxTxsPerBlock from the p2p client, constructs a block, and pushes it to L1.
185
+ * @notice Performs most of the sequencer duties:
186
+ * - Checks if we are up to date
187
+ * - If we are and we are the sequencer, collect txs and build a block
188
+ * - Collect attestations for the block
189
+ * - Submit block
190
+ * - If our block for some reason is not included, revert the state
187
191
  */
188
192
  protected async work() {
189
- try {
190
- // Update state when the previous block has been synced
191
- const prevBlockSynced = await this.isBlockSynced();
192
- // Do not go forward with new block if the previous one has not been mined and processed
193
- if (!prevBlockSynced) {
194
- this.log.debug('Previous block has not been mined and processed yet');
195
- return;
196
- }
193
+ // Update state when the previous block has been synced
194
+ const prevBlockSynced = await this.isBlockSynced();
195
+ // Do not go forward with new block if the previous one has not been mined and processed
196
+ if (!prevBlockSynced) {
197
+ this.log.debug('Previous block has not been mined and processed yet');
198
+ return;
199
+ }
197
200
 
198
- if (prevBlockSynced && this.state === SequencerState.PUBLISHING_BLOCK) {
199
- this.log.debug(`Block has been synced`);
200
- this.state = SequencerState.IDLE;
201
- }
201
+ if (prevBlockSynced && this.state === SequencerState.PUBLISHING_BLOCK) {
202
+ this.log.debug(`Block has been synced`);
203
+ this.state = SequencerState.IDLE;
204
+ }
202
205
 
203
- const chainTip = await this.l2BlockSource.getBlock(-1);
204
- const historicalHeader = chainTip?.header;
206
+ const chainTip = await this.l2BlockSource.getBlock(-1);
207
+ const historicalHeader = chainTip?.header;
205
208
 
206
- const newBlockNumber =
207
- (historicalHeader === undefined
208
- ? await this.l2BlockSource.getBlockNumber()
209
- : Number(historicalHeader.globalVariables.blockNumber.toBigInt())) + 1;
209
+ const newBlockNumber =
210
+ (historicalHeader === undefined
211
+ ? await this.l2BlockSource.getBlockNumber()
212
+ : Number(historicalHeader.globalVariables.blockNumber.toBigInt())) + 1;
210
213
 
211
- // If we cannot find a tip archive, assume genesis.
212
- const chainTipArchive =
213
- chainTip == undefined ? new Fr(GENESIS_ARCHIVE_ROOT).toBuffer() : chainTip?.archive.root.toBuffer();
214
+ // If we cannot find a tip archive, assume genesis.
215
+ const chainTipArchive =
216
+ chainTip == undefined ? new Fr(GENESIS_ARCHIVE_ROOT).toBuffer() : chainTip?.archive.root.toBuffer();
214
217
 
215
- let slot: bigint;
216
- try {
217
- slot = await this.mayProposeBlock(chainTipArchive, BigInt(newBlockNumber));
218
- } catch (err) {
219
- this.log.debug(`Cannot propose for block ${newBlockNumber}`);
220
- return;
221
- }
218
+ let slot: bigint;
219
+ try {
220
+ slot = await this.mayProposeBlock(chainTipArchive, BigInt(newBlockNumber));
221
+ } catch (err) {
222
+ this.log.debug(`Cannot propose for block ${newBlockNumber}`);
223
+ return;
224
+ }
222
225
 
223
- if (!this.shouldProposeBlock(historicalHeader, {})) {
224
- return;
225
- }
226
+ if (!this.shouldProposeBlock(historicalHeader, {})) {
227
+ return;
228
+ }
226
229
 
227
- this.state = SequencerState.WAITING_FOR_TXS;
230
+ this.state = SequencerState.WAITING_FOR_TXS;
228
231
 
229
- // Get txs to build the new block.
230
- const pendingTxs = this.p2pClient.getTxs('pending');
232
+ // Get txs to build the new block.
233
+ const pendingTxs = this.p2pClient.getTxs('pending');
231
234
 
232
- if (!this.shouldProposeBlock(historicalHeader, { pendingTxsCount: pendingTxs.length })) {
233
- return;
234
- }
235
- this.log.debug(`Retrieved ${pendingTxs.length} txs from P2P pool`);
235
+ if (!this.shouldProposeBlock(historicalHeader, { pendingTxsCount: pendingTxs.length })) {
236
+ return;
237
+ }
238
+ this.log.debug(`Retrieved ${pendingTxs.length} txs from P2P pool`);
236
239
 
237
- const newGlobalVariables = await this.globalsBuilder.buildGlobalVariables(
238
- new Fr(newBlockNumber),
239
- this._coinbase,
240
- this._feeRecipient,
241
- slot,
242
- );
240
+ const newGlobalVariables = await this.globalsBuilder.buildGlobalVariables(
241
+ new Fr(newBlockNumber),
242
+ this._coinbase,
243
+ this._feeRecipient,
244
+ slot,
245
+ );
243
246
 
244
- // If I created a "partial" header here that should make our job much easier.
245
- const proposalHeader = new Header(
246
- new AppendOnlyTreeSnapshot(Fr.fromBuffer(chainTipArchive), 1),
247
- ContentCommitment.empty(),
248
- StateReference.empty(),
249
- newGlobalVariables,
250
- Fr.ZERO,
251
- );
247
+ // If I created a "partial" header here that should make our job much easier.
248
+ const proposalHeader = new Header(
249
+ new AppendOnlyTreeSnapshot(Fr.fromBuffer(chainTipArchive), 1),
250
+ ContentCommitment.empty(),
251
+ StateReference.empty(),
252
+ newGlobalVariables,
253
+ Fr.ZERO,
254
+ );
252
255
 
253
- // TODO: It should be responsibility of the P2P layer to validate txs before passing them on here
254
- const allValidTxs = await this.takeValidTxs(
255
- pendingTxs,
256
- this.txValidatorFactory.validatorForNewTxs(newGlobalVariables, this.allowedInSetup),
257
- );
256
+ // TODO: It should be responsibility of the P2P layer to validate txs before passing them on here
257
+ const allValidTxs = await this.takeValidTxs(
258
+ pendingTxs,
259
+ this.txValidatorFactory.validatorForNewTxs(newGlobalVariables, this.allowedInSetup),
260
+ );
258
261
 
259
- // TODO: We are taking the size of the tx from private-land, but we should be doing this after running
260
- // public functions. Only reason why we do it here now is because the public processor and orchestrator
261
- // are set up such that they require knowing the total number of txs in advance. Still, main reason for
262
- // exceeding max block size in bytes is contract class registration, which happens in private-land. This
263
- // may break if we start emitting lots of log data from public-land.
264
- const validTxs = this.takeTxsWithinMaxSize(allValidTxs);
262
+ // TODO: We are taking the size of the tx from private-land, but we should be doing this after running
263
+ // public functions. Only reason why we do it here now is because the public processor and orchestrator
264
+ // are set up such that they require knowing the total number of txs in advance. Still, main reason for
265
+ // exceeding max block size in bytes is contract class registration, which happens in private-land. This
266
+ // may break if we start emitting lots of log data from public-land.
267
+ const validTxs = this.takeTxsWithinMaxSize(allValidTxs);
265
268
 
266
- // Bail if we don't have enough valid txs
267
- if (!this.shouldProposeBlock(historicalHeader, { validTxsCount: validTxs.length })) {
268
- return;
269
- }
269
+ // Bail if we don't have enough valid txs
270
+ if (!this.shouldProposeBlock(historicalHeader, { validTxsCount: validTxs.length })) {
271
+ return;
272
+ }
270
273
 
274
+ try {
275
+ // @note It is very important that the following function will FAIL and not just return early
276
+ // if it have made any state changes. If not, we won't rollback the state, and you will
277
+ // be in for a world of pain.
271
278
  await this.buildBlockAndPublish(validTxs, proposalHeader, historicalHeader);
272
279
  } catch (err) {
273
280
  if (BlockProofError.isBlockProofError(err)) {
@@ -300,13 +307,18 @@ export class Sequencer {
300
307
  throw new Error(msg);
301
308
  }
302
309
 
310
+ this.log.debug(`Can propose block ${proposalBlockNumber} at slot ${slot}`);
303
311
  return slot;
304
312
  } catch (err) {
305
313
  if (err instanceof BaseError) {
306
314
  const revertError = err.walk(err => err instanceof ContractFunctionRevertedError);
307
315
  if (revertError instanceof ContractFunctionRevertedError) {
308
316
  const errorName = revertError.data?.errorName ?? '';
309
- this.log.debug(`canProposeAtTime failed with "${errorName}"`);
317
+ const args =
318
+ revertError.metaMessages && revertError.metaMessages?.length > 1
319
+ ? revertError.metaMessages[1].trimStart()
320
+ : '';
321
+ this.log.debug(`canProposeAtTime failed with "${errorName}${args}"`);
310
322
  }
311
323
  }
312
324
  throw err;
@@ -319,23 +331,21 @@ export class Sequencer {
319
331
  return true;
320
332
  }
321
333
 
322
- if (IS_DEV_NET) {
323
- // Compute time elapsed since the previous block
324
- const lastBlockTime = historicalHeader?.globalVariables.timestamp.toNumber() || 0;
325
- const currentTime = Math.floor(Date.now() / 1000);
326
- const elapsedSinceLastBlock = currentTime - lastBlockTime;
334
+ // Compute time elapsed since the previous block
335
+ const lastBlockTime = historicalHeader?.globalVariables.timestamp.toNumber() || 0;
336
+ const currentTime = Math.floor(Date.now() / 1000);
337
+ const elapsedSinceLastBlock = currentTime - lastBlockTime;
338
+ this.log.debug(
339
+ `Last block mined at ${lastBlockTime} current time is ${currentTime} (elapsed ${elapsedSinceLastBlock})`,
340
+ );
341
+
342
+ // If we haven't hit the maxSecondsBetweenBlocks, we need to have at least minTxsPerBLock txs.
343
+ // Do not go forward with new block if not enough time has passed since last block
344
+ if (this.minSecondsBetweenBlocks > 0 && elapsedSinceLastBlock < this.minSecondsBetweenBlocks) {
327
345
  this.log.debug(
328
- `Last block mined at ${lastBlockTime} current time is ${currentTime} (elapsed ${elapsedSinceLastBlock})`,
346
+ `Not creating block because not enough time ${this.minSecondsBetweenBlocks} has passed since last block`,
329
347
  );
330
-
331
- // If we haven't hit the maxSecondsBetweenBlocks, we need to have at least minTxsPerBLock txs.
332
- // Do not go forward with new block if not enough time has passed since last block
333
- if (this.minSecondsBetweenBlocks > 0 && elapsedSinceLastBlock < this.minSecondsBetweenBlocks) {
334
- this.log.debug(
335
- `Not creating block because not enough time ${this.minSecondsBetweenBlocks} has passed since last block`,
336
- );
337
- return false;
338
- }
348
+ return false;
339
349
  }
340
350
 
341
351
  const skipCheck = this.skipMinTxsPerBlockCheck(historicalHeader);
@@ -381,6 +391,16 @@ export class Sequencer {
381
391
  return true;
382
392
  }
383
393
 
394
+ /**
395
+ * @notice Build and propose a block to the chain
396
+ *
397
+ * @dev MUST throw instead of exiting early to ensure that world-state
398
+ * is being rolled back if the block is dropped.
399
+ *
400
+ * @param validTxs - The valid transactions to construct the block from
401
+ * @param proposalHeader - The partial header constructed for the proposal
402
+ * @param historicalHeader - The historical header of the parent
403
+ */
384
404
  @trackSpan('Sequencer.buildBlockAndPublish', (_validTxs, proposalHeader, _historicalHeader) => ({
385
405
  [Attributes.BLOCK_NUMBER]: proposalHeader.globalVariables.blockNumber.toNumber(),
386
406
  }))
@@ -389,9 +409,7 @@ export class Sequencer {
389
409
  proposalHeader: Header,
390
410
  historicalHeader: Header | undefined,
391
411
  ): Promise<void> {
392
- if (!(await this.publisher.validateBlockForSubmission(proposalHeader))) {
393
- return;
394
- }
412
+ await this.publisher.validateBlockForSubmission(proposalHeader);
395
413
 
396
414
  const newGlobalVariables = proposalHeader.globalVariables;
397
415
 
@@ -426,15 +444,16 @@ export class Sequencer {
426
444
  await this.p2pClient.deleteTxs(Tx.getHashes(failedTxData));
427
445
  }
428
446
 
447
+ await this.publisher.validateBlockForSubmission(proposalHeader);
448
+
429
449
  if (
430
- !(await this.publisher.validateBlockForSubmission(proposalHeader)) ||
431
450
  !this.shouldProposeBlock(historicalHeader, {
432
451
  validTxsCount: validTxs.length,
433
452
  processedTxsCount: processedTxs.length,
434
453
  })
435
454
  ) {
436
455
  blockBuilder.cancelBlock();
437
- return;
456
+ throw new Error('Should not propose the block');
438
457
  }
439
458
 
440
459
  // All real transactions have been added, set the block as full and complete the proving.
@@ -451,9 +470,7 @@ export class Sequencer {
451
470
  // Block is ready, now finalise
452
471
  const { block } = await blockBuilder.finaliseBlock();
453
472
 
454
- if (!(await this.publisher.validateBlockForSubmission(block.header))) {
455
- return;
456
- }
473
+ await this.publisher.validateBlockForSubmission(block.header);
457
474
 
458
475
  const workDuration = workTimer.ms();
459
476
  this.log.verbose(
@@ -496,24 +513,6 @@ export class Sequencer {
496
513
  }
497
514
 
498
515
  protected async collectAttestations(block: L2Block): Promise<Signature[] | undefined> {
499
- // @todo This should collect attestations properly and fix the ordering of them to make sense
500
- // the current implementation is a PLACEHOLDER and should be nuked from orbit.
501
- // It is assuming that there will only be ONE (1) validator, so only one attestation
502
- // is needed.
503
- // @note This is quite a sin, but I'm committing war crimes in this code already.
504
- // _ ._ _ , _ ._
505
- // (_ ' ( ` )_ .__)
506
- // ( ( ( ) `) ) _)
507
- // (__ (_ (_ . _) _) ,__)
508
- // `~~`\ ' . /`~~`
509
- // ; ;
510
- // / \
511
- // _____________/_ __ \_____________
512
-
513
- if (IS_DEV_NET || !this.validatorClient) {
514
- return undefined;
515
- }
516
-
517
516
  // TODO(https://github.com/AztecProtocol/aztec-packages/issues/7962): inefficient to have a round trip in here - this should be cached
518
517
  const committee = await this.publisher.getCurrentEpochCommittee();
519
518
 
@@ -521,6 +520,12 @@ export class Sequencer {
521
520
  return undefined;
522
521
  }
523
522
 
523
+ if (!this.validatorClient) {
524
+ const msg = 'Missing validator client: Cannot collect attestations';
525
+ this.log.error(msg);
526
+ throw new Error(msg);
527
+ }
528
+
524
529
  const numberOfRequiredAttestations = Math.floor((committee.length * 2) / 3) + 1;
525
530
 
526
531
  // TODO(https://github.com/AztecProtocol/aztec-packages/issues/7974): we do not have transaction[] lists in the block for now
@@ -553,7 +558,7 @@ export class Sequencer {
553
558
  if (publishedL2Block) {
554
559
  this.lastPublishedBlock = block.number;
555
560
  } else {
556
- throw new Error(`Failed to publish block`);
561
+ throw new Error(`Failed to publish block ${block.number}`);
557
562
  }
558
563
  }
559
564