@aztec/sequencer-client 0.51.0 → 0.52.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.
@@ -10,7 +10,15 @@ import {
10
10
  } from '@aztec/circuit-types';
11
11
  import { type AllowedElement, BlockProofError, PROVING_STATUS } from '@aztec/circuit-types/interfaces';
12
12
  import { type L2BlockBuiltStats } from '@aztec/circuit-types/stats';
13
- import { AztecAddress, EthAddress, type GlobalVariables, type Header, IS_DEV_NET } from '@aztec/circuits.js';
13
+ import {
14
+ AppendOnlyTreeSnapshot,
15
+ AztecAddress,
16
+ ContentCommitment,
17
+ EthAddress,
18
+ GENESIS_ARCHIVE_ROOT,
19
+ Header,
20
+ StateReference,
21
+ } from '@aztec/circuits.js';
14
22
  import { Fr } from '@aztec/foundation/fields';
15
23
  import { createDebugLogger } from '@aztec/foundation/log';
16
24
  import { RunningPromise } from '@aztec/foundation/running-promise';
@@ -21,6 +29,8 @@ import { Attributes, type TelemetryClient, type Tracer, trackSpan } from '@aztec
21
29
  import { type ValidatorClient } from '@aztec/validator-client';
22
30
  import { type WorldStateStatus, type WorldStateSynchronizer } from '@aztec/world-state';
23
31
 
32
+ import { BaseError, ContractFunctionRevertedError } from 'viem';
33
+
24
34
  import { type BlockBuilderFactory } from '../block_builder/index.js';
25
35
  import { type GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
26
36
  import { type L1Publisher } from '../publisher/l1-publisher.js';
@@ -28,6 +38,12 @@ import { type TxValidatorFactory } from '../tx_validator/tx_validator_factory.js
28
38
  import { type SequencerConfig } from './config.js';
29
39
  import { SequencerMetrics } from './metrics.js';
30
40
 
41
+ export type ShouldProposeArgs = {
42
+ pendingTxsCount?: number;
43
+ validTxsCount?: number;
44
+ processedTxsCount?: number;
45
+ };
46
+
31
47
  /**
32
48
  * Sequencer client
33
49
  * - Wins a period of time to become the sequencer (depending on finalized protocol).
@@ -166,157 +182,237 @@ export class Sequencer {
166
182
  }
167
183
 
168
184
  /**
169
- * 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
170
191
  */
171
192
  protected async work() {
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
+ }
200
+
201
+ if (prevBlockSynced && this.state === SequencerState.PUBLISHING_BLOCK) {
202
+ this.log.debug(`Block has been synced`);
203
+ this.state = SequencerState.IDLE;
204
+ }
205
+
206
+ const chainTip = await this.l2BlockSource.getBlock(-1);
207
+ const historicalHeader = chainTip?.header;
208
+
209
+ const newBlockNumber =
210
+ (historicalHeader === undefined
211
+ ? await this.l2BlockSource.getBlockNumber()
212
+ : Number(historicalHeader.globalVariables.blockNumber.toBigInt())) + 1;
213
+
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();
217
+
218
+ let slot: bigint;
172
219
  try {
173
- // Update state when the previous block has been synced
174
- const prevBlockSynced = await this.isBlockSynced();
175
- // Do not go forward with new block if the previous one has not been mined and processed
176
- if (!prevBlockSynced) {
177
- this.log.debug('Previous block has not been mined and processed yet');
178
- return;
179
- }
220
+ slot = await this.mayProposeBlock(chainTipArchive, BigInt(newBlockNumber));
221
+ } catch (err) {
222
+ this.log.debug(`Cannot propose for block ${newBlockNumber}`);
223
+ return;
224
+ }
225
+
226
+ if (!this.shouldProposeBlock(historicalHeader, {})) {
227
+ return;
228
+ }
229
+
230
+ this.state = SequencerState.WAITING_FOR_TXS;
231
+
232
+ // Get txs to build the new block.
233
+ const pendingTxs = this.p2pClient.getTxs('pending');
234
+
235
+ if (!this.shouldProposeBlock(historicalHeader, { pendingTxsCount: pendingTxs.length })) {
236
+ return;
237
+ }
238
+ this.log.debug(`Retrieved ${pendingTxs.length} txs from P2P pool`);
239
+
240
+ const newGlobalVariables = await this.globalsBuilder.buildGlobalVariables(
241
+ new Fr(newBlockNumber),
242
+ this._coinbase,
243
+ this._feeRecipient,
244
+ slot,
245
+ );
246
+
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
+ );
255
+
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
+ );
180
261
 
181
- if (prevBlockSynced && this.state === SequencerState.PUBLISHING_BLOCK) {
182
- this.log.debug(`Block has been synced`);
183
- this.state = SequencerState.IDLE;
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);
268
+
269
+ // Bail if we don't have enough valid txs
270
+ if (!this.shouldProposeBlock(historicalHeader, { validTxsCount: validTxs.length })) {
271
+ return;
272
+ }
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.
278
+ await this.buildBlockAndPublish(validTxs, proposalHeader, historicalHeader);
279
+ } catch (err) {
280
+ if (BlockProofError.isBlockProofError(err)) {
281
+ const txHashes = err.txHashes.filter(h => !h.isZero());
282
+ this.log.warn(`Proving block failed, removing ${txHashes.length} txs from pool`);
283
+ await this.p2pClient.deleteTxs(txHashes);
184
284
  }
285
+ this.log.error(`Rolling back world state DB due to error assembling block`, (err as any).stack);
286
+ await this.worldState.getLatest().rollback();
287
+ }
288
+ }
185
289
 
186
- const historicalHeader = (await this.l2BlockSource.getBlock(-1))?.header;
187
- const newBlockNumber =
188
- (historicalHeader === undefined
189
- ? await this.l2BlockSource.getBlockNumber()
190
- : Number(historicalHeader.globalVariables.blockNumber.toBigInt())) + 1;
290
+ /** Whether to skip the check of min txs per block if more than maxSecondsBetweenBlocks has passed since the previous block. */
291
+ private skipMinTxsPerBlockCheck(historicalHeader: Header | undefined): boolean {
292
+ const lastBlockTime = historicalHeader?.globalVariables.timestamp.toNumber() || 0;
293
+ const currentTime = Math.floor(Date.now() / 1000);
294
+ const elapsed = currentTime - lastBlockTime;
295
+
296
+ return this.maxSecondsBetweenBlocks > 0 && elapsed >= this.maxSecondsBetweenBlocks;
297
+ }
191
298
 
192
- // Do not go forward with new block if not my turn
193
- if (!(await this.publisher.isItMyTurnToSubmit())) {
194
- this.log.debug('Not my turn to submit block');
195
- return;
299
+ async mayProposeBlock(tipArchive: Buffer, proposalBlockNumber: bigint): Promise<bigint> {
300
+ // This checks that we can propose, and gives us the slot that we are to propose for
301
+ try {
302
+ const [slot, blockNumber] = await this.publisher.canProposeAtNextEthBlock(tipArchive);
303
+
304
+ if (proposalBlockNumber !== blockNumber) {
305
+ const msg = `Block number mismatch. Expected ${proposalBlockNumber} but got ${blockNumber}`;
306
+ this.log.debug(msg);
307
+ throw new Error(msg);
196
308
  }
197
309
 
198
- if (this.isFlushing) {
199
- this.log.verbose(`Flushing all pending txs in new block`);
310
+ return slot;
311
+ } catch (err) {
312
+ if (err instanceof BaseError) {
313
+ const revertError = err.walk(err => err instanceof ContractFunctionRevertedError);
314
+ if (revertError instanceof ContractFunctionRevertedError) {
315
+ const errorName = revertError.data?.errorName ?? '';
316
+ this.log.debug(`canProposeAtTime failed with "${errorName}"`);
317
+ }
200
318
  }
319
+ throw err;
320
+ }
321
+ }
201
322
 
202
- // Compute time elapsed since the previous block
203
- const lastBlockTime = historicalHeader?.globalVariables.timestamp.toNumber() || 0;
204
- const currentTime = Math.floor(Date.now() / 1000);
205
- const elapsedSinceLastBlock = currentTime - lastBlockTime;
206
- this.log.debug(
207
- `Last block mined at ${lastBlockTime} current time is ${currentTime} (elapsed ${elapsedSinceLastBlock})`,
208
- );
323
+ shouldProposeBlock(historicalHeader: Header | undefined, args: ShouldProposeArgs): boolean {
324
+ if (this.isFlushing) {
325
+ this.log.verbose(`Flushing all pending txs in new block`);
326
+ return true;
327
+ }
209
328
 
210
- // Do not go forward with new block if not enough time has passed since last block
211
- if (
212
- !this.isFlushing &&
213
- this.minSecondsBetweenBlocks > 0 &&
214
- elapsedSinceLastBlock < this.minSecondsBetweenBlocks
215
- ) {
216
- this.log.debug(
217
- `Not creating block because not enough time ${this.minSecondsBetweenBlocks} has passed since last block`,
218
- );
219
- return;
220
- }
329
+ // Compute time elapsed since the previous block
330
+ const lastBlockTime = historicalHeader?.globalVariables.timestamp.toNumber() || 0;
331
+ const currentTime = Math.floor(Date.now() / 1000);
332
+ const elapsedSinceLastBlock = currentTime - lastBlockTime;
333
+ this.log.debug(
334
+ `Last block mined at ${lastBlockTime} current time is ${currentTime} (elapsed ${elapsedSinceLastBlock})`,
335
+ );
221
336
 
222
- this.state = SequencerState.WAITING_FOR_TXS;
337
+ // If we haven't hit the maxSecondsBetweenBlocks, we need to have at least minTxsPerBLock txs.
338
+ // Do not go forward with new block if not enough time has passed since last block
339
+ if (this.minSecondsBetweenBlocks > 0 && elapsedSinceLastBlock < this.minSecondsBetweenBlocks) {
340
+ this.log.debug(
341
+ `Not creating block because not enough time ${this.minSecondsBetweenBlocks} has passed since last block`,
342
+ );
343
+ return false;
344
+ }
223
345
 
224
- // Get txs to build the new block.
225
- const pendingTxs = this.p2pClient.getTxs('pending');
346
+ const skipCheck = this.skipMinTxsPerBlockCheck(historicalHeader);
226
347
 
227
- // If we haven't hit the maxSecondsBetweenBlocks, we need to have at least minTxsPerBLock txs.
228
- if (!this.isFlushing && pendingTxs.length < this.minTxsPerBLock) {
229
- if (this.skipMinTxsPerBlockCheck(elapsedSinceLastBlock)) {
348
+ // If we haven't hit the maxSecondsBetweenBlocks, we need to have at least minTxsPerBLock txs.
349
+ if (args.pendingTxsCount != undefined) {
350
+ if (args.pendingTxsCount < this.minTxsPerBLock) {
351
+ if (skipCheck) {
230
352
  this.log.debug(
231
- `Creating block with only ${pendingTxs.length} txs as more than ${this.maxSecondsBetweenBlocks}s have passed since last block`,
353
+ `Creating block with only ${args.pendingTxsCount} txs as more than ${this.maxSecondsBetweenBlocks}s have passed since last block`,
232
354
  );
233
355
  } else {
234
356
  this.log.debug(
235
- `Not creating block because not enough txs in the pool (got ${pendingTxs.length} min ${this.minTxsPerBLock})`,
357
+ `Not creating block because not enough txs in the pool (got ${args.pendingTxsCount} min ${this.minTxsPerBLock})`,
236
358
  );
237
- return;
359
+ return false;
238
360
  }
239
361
  }
240
- this.log.debug(`Retrieved ${pendingTxs.length} txs from P2P pool`);
241
-
242
- const newGlobalVariables = await this.globalsBuilder.buildGlobalVariables(
243
- new Fr(newBlockNumber),
244
- this._coinbase,
245
- this._feeRecipient,
246
- );
247
-
248
- // @todo @LHerskind Include some logic to consider slots
249
-
250
- // TODO: It should be responsibility of the P2P layer to validate txs before passing them on here
251
- const allValidTxs = await this.takeValidTxs(
252
- pendingTxs,
253
- this.txValidatorFactory.validatorForNewTxs(newGlobalVariables, this.allowedInSetup),
254
- );
255
-
256
- // TODO: We are taking the size of the tx from private-land, but we should be doing this after running
257
- // public functions. Only reason why we do it here now is because the public processor and orchestrator
258
- // are set up such that they require knowing the total number of txs in advance. Still, main reason for
259
- // exceeding max block size in bytes is contract class registration, which happens in private-land. This
260
- // may break if we start emitting lots of log data from public-land.
261
- const validTxs = this.takeTxsWithinMaxSize(allValidTxs);
362
+ }
262
363
 
364
+ // Bail if we don't have enough valid txs
365
+ if (args.validTxsCount != undefined) {
263
366
  // Bail if we don't have enough valid txs
264
- if (
265
- !this.isFlushing &&
266
- !this.skipMinTxsPerBlockCheck(elapsedSinceLastBlock) &&
267
- validTxs.length < this.minTxsPerBLock
268
- ) {
367
+ if (!skipCheck && args.validTxsCount < this.minTxsPerBLock) {
269
368
  this.log.debug(
270
- `Not creating block because not enough valid txs loaded from the pool (got ${validTxs.length} min ${this.minTxsPerBLock})`,
369
+ `Not creating block because not enough valid txs loaded from the pool (got ${args.validTxsCount} min ${this.minTxsPerBLock})`,
271
370
  );
272
- return;
371
+ return false;
273
372
  }
373
+ }
274
374
 
275
- await this.buildBlockAndPublish(validTxs, newGlobalVariables, historicalHeader, elapsedSinceLastBlock);
276
- } catch (err) {
277
- if (BlockProofError.isBlockProofError(err)) {
278
- const txHashes = err.txHashes.filter(h => !h.isZero());
279
- this.log.warn(`Proving block failed, removing ${txHashes.length} txs from pool`);
280
- await this.p2pClient.deleteTxs(txHashes);
375
+ // TODO: This check should be processedTxs.length < this.minTxsPerBLock, so we don't publish a block with
376
+ // less txs than the minimum. But that'd cause the entire block to be aborted and retried. Instead, we should
377
+ // go back to the p2p pool and load more txs until we hit our minTxsPerBLock target. Only if there are no txs
378
+ // we should bail.
379
+ if (args.processedTxsCount != undefined) {
380
+ if (args.processedTxsCount === 0 && !skipCheck && this.minTxsPerBLock > 0) {
381
+ this.log.verbose('No txs processed correctly to build block. Exiting');
382
+ return false;
281
383
  }
282
- this.log.error(`Rolling back world state DB due to error assembling block`, (err as any).stack);
283
- await this.worldState.getLatest().rollback();
284
384
  }
285
- }
286
385
 
287
- /** Whether to skip the check of min txs per block if more than maxSecondsBetweenBlocks has passed since the previous block. */
288
- private skipMinTxsPerBlockCheck(elapsed: number): boolean {
289
- return this.maxSecondsBetweenBlocks > 0 && elapsed >= this.maxSecondsBetweenBlocks;
386
+ return true;
290
387
  }
291
388
 
292
- @trackSpan('Sequencer.buildBlockAndPublish', (_validTxs, newGlobalVariables, _historicalHeader) => ({
293
- [Attributes.BLOCK_NUMBER]: newGlobalVariables.blockNumber.toNumber(),
389
+ /**
390
+ * @notice Build and propose a block to the chain
391
+ *
392
+ * @dev MUST throw instead of exiting early to ensure that world-state
393
+ * is being rolled back if the block is dropped.
394
+ *
395
+ * @param validTxs - The valid transactions to construct the block from
396
+ * @param proposalHeader - The partial header constructed for the proposal
397
+ * @param historicalHeader - The historical header of the parent
398
+ */
399
+ @trackSpan('Sequencer.buildBlockAndPublish', (_validTxs, proposalHeader, _historicalHeader) => ({
400
+ [Attributes.BLOCK_NUMBER]: proposalHeader.globalVariables.blockNumber.toNumber(),
294
401
  }))
295
402
  private async buildBlockAndPublish(
296
403
  validTxs: Tx[],
297
- newGlobalVariables: GlobalVariables,
404
+ proposalHeader: Header,
298
405
  historicalHeader: Header | undefined,
299
- elapsedSinceLastBlock: number,
300
406
  ): Promise<void> {
407
+ await this.publisher.validateBlockForSubmission(proposalHeader);
408
+
409
+ const newGlobalVariables = proposalHeader.globalVariables;
410
+
301
411
  this.metrics.recordNewBlock(newGlobalVariables.blockNumber.toNumber(), validTxs.length);
302
412
  const workTimer = new Timer();
303
413
  this.state = SequencerState.CREATING_BLOCK;
304
414
  this.log.info(`Building block ${newGlobalVariables.blockNumber.toNumber()} with ${validTxs.length} transactions`);
305
415
 
306
- const assertBlockHeight = async () => {
307
- const currentBlockNumber = await this.l2BlockSource.getBlockNumber();
308
- if (currentBlockNumber + 1 !== newGlobalVariables.blockNumber.toNumber()) {
309
- this.metrics.recordCancelledBlock();
310
- throw new Error('New block was emitted while building block');
311
- }
312
-
313
- if (!(await this.publisher.isItMyTurnToSubmit())) {
314
- throw new Error(`Not this sequencer turn to submit block`);
315
- }
316
-
317
- // @todo @LHerskind Should take into account, block number, proposer and slot number
318
- };
319
-
320
416
  // Get l1 to l2 messages from the contract
321
417
  this.log.debug('Requesting L1 to L2 messages from contract');
322
418
  const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(newGlobalVariables.blockNumber.toBigInt());
@@ -343,27 +439,22 @@ export class Sequencer {
343
439
  await this.p2pClient.deleteTxs(Tx.getHashes(failedTxData));
344
440
  }
345
441
 
346
- // TODO: This check should be processedTxs.length < this.minTxsPerBLock, so we don't publish a block with
347
- // less txs than the minimum. But that'd cause the entire block to be aborted and retried. Instead, we should
348
- // go back to the p2p pool and load more txs until we hit our minTxsPerBLock target. Only if there are no txs
349
- // we should bail.
442
+ await this.publisher.validateBlockForSubmission(proposalHeader);
443
+
350
444
  if (
351
- !this.isFlushing &&
352
- processedTxs.length === 0 &&
353
- !this.skipMinTxsPerBlockCheck(elapsedSinceLastBlock) &&
354
- this.minTxsPerBLock > 0
445
+ !this.shouldProposeBlock(historicalHeader, {
446
+ validTxsCount: validTxs.length,
447
+ processedTxsCount: processedTxs.length,
448
+ })
355
449
  ) {
356
- this.log.verbose('No txs processed correctly to build block. Exiting');
357
450
  blockBuilder.cancelBlock();
358
- return;
451
+ throw new Error('Should not propose the block');
359
452
  }
360
453
 
361
- await assertBlockHeight();
362
-
363
454
  // All real transactions have been added, set the block as full and complete the proving.
364
455
  await blockBuilder.setBlockCompleted();
365
456
 
366
- // Here we are now waiting for the block to be proven.
457
+ // Here we are now waiting for the block to be proven (using simulated[fake] proofs).
367
458
  // TODO(@PhilWindle) We should probably periodically check for things like another
368
459
  // block being published before ours instead of just waiting on our block
369
460
  const result = await blockTicket.provingPromise;
@@ -371,12 +462,10 @@ export class Sequencer {
371
462
  throw new Error(`Block proving failed, reason: ${result.reason}`);
372
463
  }
373
464
 
374
- await assertBlockHeight();
375
-
376
- // Block is ready, now finalise and publish!
465
+ // Block is ready, now finalise
377
466
  const { block } = await blockBuilder.finaliseBlock();
378
467
 
379
- await assertBlockHeight();
468
+ await this.publisher.validateBlockForSubmission(block.header);
380
469
 
381
470
  const workDuration = workTimer.ms();
382
471
  this.log.verbose(
@@ -395,10 +484,11 @@ export class Sequencer {
395
484
  if (this.isFlushing) {
396
485
  this.log.verbose(`Flushing completed`);
397
486
  }
487
+
398
488
  this.isFlushing = false;
489
+ const attestations = await this.collectAttestations(block);
399
490
 
400
491
  try {
401
- const attestations = await this.collectAttestations(block);
402
492
  await this.publishL2Block(block, attestations);
403
493
  this.metrics.recordPublishedBlock(workDuration);
404
494
  this.log.info(
@@ -418,25 +508,19 @@ export class Sequencer {
418
508
  }
419
509
 
420
510
  protected async collectAttestations(block: L2Block): Promise<Signature[] | undefined> {
421
- // @todo This should collect attestations properly and fix the ordering of them to make sense
422
- // the current implementation is a PLACEHOLDER and should be nuked from orbit.
423
- // It is assuming that there will only be ONE (1) validator, so only one attestation
424
- // is needed.
425
- // @note This is quite a sin, but I'm committing war crimes in this code already.
426
- // _ ._ _ , _ ._
427
- // (_ ' ( ` )_ .__)
428
- // ( ( ( ) `) ) _)
429
- // (__ (_ (_ . _) _) ,__)
430
- // `~~`\ ' . /`~~`
431
- // ; ;
432
- // / \
433
- // _____________/_ __ \_____________
434
- if (IS_DEV_NET || !this.validatorClient) {
511
+ // TODO(https://github.com/AztecProtocol/aztec-packages/issues/7962): inefficient to have a round trip in here - this should be cached
512
+ const committee = await this.publisher.getCurrentEpochCommittee();
513
+
514
+ if (committee.length === 0) {
435
515
  return undefined;
436
516
  }
437
517
 
438
- // TODO(https://github.com/AztecProtocol/aztec-packages/issues/7962): inefficient to have a round trip in here - this should be cached
439
- const committee = await this.publisher.getCurrentEpochCommittee();
518
+ if (!this.validatorClient) {
519
+ const msg = 'Missing validator client: Cannot collect attestations';
520
+ this.log.error(msg);
521
+ throw new Error(msg);
522
+ }
523
+
440
524
  const numberOfRequiredAttestations = Math.floor((committee.length * 2) / 3) + 1;
441
525
 
442
526
  // TODO(https://github.com/AztecProtocol/aztec-packages/issues/7974): we do not have transaction[] lists in the block for now
@@ -448,10 +532,7 @@ export class Sequencer {
448
532
  this.validatorClient.broadcastBlockProposal(proposal);
449
533
 
450
534
  this.state = SequencerState.WAITING_FOR_ATTESTATIONS;
451
- const attestations = await this.validatorClient.collectAttestations(
452
- proposal.header.globalVariables.slotNumber.toBigInt(),
453
- numberOfRequiredAttestations,
454
- );
535
+ const attestations = await this.validatorClient.collectAttestations(proposal, numberOfRequiredAttestations);
455
536
 
456
537
  // note: the smart contract requires that the signatures are provided in the order of the committee
457
538
  return await orderAttestations(attestations, committee);
@@ -472,7 +553,7 @@ export class Sequencer {
472
553
  if (publishedL2Block) {
473
554
  this.lastPublishedBlock = block.number;
474
555
  } else {
475
- throw new Error(`Failed to publish block`);
556
+ throw new Error(`Failed to publish block ${block.number}`);
476
557
  }
477
558
  }
478
559