@aztec/sequencer-client 0.63.1 → 0.64.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,7 @@ import {
17
17
  type Proof,
18
18
  type RootRollupPublicInputs,
19
19
  } from '@aztec/circuits.js';
20
- import { type L1ContractsConfig, createEthereumChain } from '@aztec/ethereum';
20
+ import { type EthereumChain, type L1ContractsConfig, createEthereumChain } from '@aztec/ethereum';
21
21
  import { makeTuple } from '@aztec/foundation/array';
22
22
  import { areArraysEqual, compactArray, times } from '@aztec/foundation/collection';
23
23
  import { type Signature } from '@aztec/foundation/eth-signature';
@@ -58,7 +58,6 @@ import {
58
58
  publicActions,
59
59
  } from 'viem';
60
60
  import { privateKeyToAccount } from 'viem/accounts';
61
- import type * as chains from 'viem/chains';
62
61
 
63
62
  import { type PublisherConfig, type TxSenderConfig } from './config.js';
64
63
  import { L1PublisherMetrics } from './l1-publisher-metrics.js';
@@ -145,19 +144,19 @@ export class L1Publisher {
145
144
 
146
145
  protected log = createDebugLogger('aztec:sequencer:publisher');
147
146
 
148
- private rollupContract: GetContractReturnType<
147
+ protected rollupContract: GetContractReturnType<
149
148
  typeof RollupAbi,
150
- WalletClient<HttpTransport, chains.Chain, PrivateKeyAccount>
149
+ WalletClient<HttpTransport, Chain, PrivateKeyAccount>
151
150
  >;
152
- private governanceProposerContract?: GetContractReturnType<
151
+ protected governanceProposerContract?: GetContractReturnType<
153
152
  typeof GovernanceProposerAbi,
154
- WalletClient<HttpTransport, chains.Chain, PrivateKeyAccount>
153
+ WalletClient<HttpTransport, Chain, PrivateKeyAccount>
155
154
  > = undefined;
156
155
 
157
- private publicClient: PublicClient<HttpTransport, chains.Chain>;
158
- private walletClient: WalletClient<HttpTransport, chains.Chain, PrivateKeyAccount>;
159
- private account: PrivateKeyAccount;
160
- private ethereumSlotDuration: bigint;
156
+ protected publicClient: PublicClient<HttpTransport, Chain>;
157
+ protected walletClient: WalletClient<HttpTransport, Chain, PrivateKeyAccount>;
158
+ protected account: PrivateKeyAccount;
159
+ protected ethereumSlotDuration: bigint;
161
160
 
162
161
  public static PROPOSE_GAS_GUESS: bigint = 12_000_000n;
163
162
  public static PROPOSE_AND_CLAIM_GAS_GUESS: bigint = this.PROPOSE_GAS_GUESS + 100_000n;
@@ -175,11 +174,7 @@ export class L1Publisher {
175
174
  this.account = privateKeyToAccount(publisherPrivateKey);
176
175
  this.log.debug(`Publishing from address ${this.account.address}`);
177
176
 
178
- this.walletClient = createWalletClient({
179
- account: this.account,
180
- chain: chain.chainInfo,
181
- transport: http(chain.rpcUrl),
182
- });
177
+ this.walletClient = this.createWalletClient(this.account, chain);
183
178
 
184
179
  this.publicClient = createPublicClient({
185
180
  chain: chain.chainInfo,
@@ -202,6 +197,17 @@ export class L1Publisher {
202
197
  }
203
198
  }
204
199
 
200
+ protected createWalletClient(
201
+ account: PrivateKeyAccount,
202
+ chain: EthereumChain,
203
+ ): WalletClient<HttpTransport, Chain, PrivateKeyAccount> {
204
+ return createWalletClient({
205
+ account,
206
+ chain: chain.chainInfo,
207
+ transport: http(chain.rpcUrl),
208
+ });
209
+ }
210
+
205
211
  public getPayLoad() {
206
212
  return this.payload;
207
213
  }
@@ -226,7 +232,7 @@ export class L1Publisher {
226
232
 
227
233
  public getRollupContract(): GetContractReturnType<
228
234
  typeof RollupAbi,
229
- WalletClient<HttpTransport, chains.Chain, PrivateKeyAccount>
235
+ WalletClient<HttpTransport, Chain, PrivateKeyAccount>
230
236
  > {
231
237
  return this.rollupContract;
232
238
  }
@@ -732,10 +738,12 @@ export class L1Publisher {
732
738
  : [];
733
739
  const txHashes = encodedData.txHashes ? encodedData.txHashes.map(txHash => txHash.to0xString()) : [];
734
740
  const args = [
735
- `0x${encodedData.header.toString('hex')}`,
736
- `0x${encodedData.archive.toString('hex')}`,
737
- `0x${encodedData.blockHash.toString('hex')}`,
738
- txHashes,
741
+ {
742
+ header: `0x${encodedData.header.toString('hex')}`,
743
+ archive: `0x${encodedData.archive.toString('hex')}`,
744
+ blockHash: `0x${encodedData.blockHash.toString('hex')}`,
745
+ txHashes,
746
+ },
739
747
  attestations,
740
748
  `0x${encodedData.body.toString('hex')}`,
741
749
  ] as const;
@@ -0,0 +1,20 @@
1
+ import { type EthereumChain } from '@aztec/ethereum';
2
+ import { type Delayer, withDelayer } from '@aztec/ethereum/test';
3
+
4
+ import { type Chain, type HttpTransport, type PrivateKeyAccount, type WalletClient } from 'viem';
5
+
6
+ import { L1Publisher } from './l1-publisher.js';
7
+
8
+ export class TestL1Publisher extends L1Publisher {
9
+ public delayer: Delayer | undefined;
10
+
11
+ protected override createWalletClient(
12
+ account: PrivateKeyAccount,
13
+ chain: EthereumChain,
14
+ ): WalletClient<HttpTransport, Chain, PrivateKeyAccount> {
15
+ const baseClient = super.createWalletClient(account, chain);
16
+ const { client, delayer } = withDelayer(baseClient, { ethereumSlotDuration: this.ethereumSlotDuration });
17
+ this.delayer = delayer;
18
+ return client;
19
+ }
20
+ }
@@ -21,6 +21,8 @@ export class SequencerMetrics {
21
21
  private currentBlockNumber: Gauge;
22
22
  private currentBlockSize: Gauge;
23
23
 
24
+ private timeToCollectAttestations: Gauge;
25
+
24
26
  constructor(client: TelemetryClient, getState: SequencerStateCallback, name = 'Sequencer') {
25
27
  const meter = client.getMeter(name);
26
28
  this.tracer = client.getTracer(name);
@@ -60,9 +62,26 @@ export class SequencerMetrics {
60
62
  description: 'Current block number',
61
63
  });
62
64
 
65
+ this.timeToCollectAttestations = meter.createGauge(Metrics.SEQUENCER_TIME_TO_COLLECT_ATTESTATIONS, {
66
+ description: 'The time spent collecting attestations from committee members',
67
+ });
68
+
63
69
  this.setCurrentBlock(0, 0);
64
70
  }
65
71
 
72
+ startCollectingAttestationsTimer(): () => void {
73
+ const startTime = Date.now();
74
+ const stop = () => {
75
+ const duration = Date.now() - startTime;
76
+ this.recordTimeToCollectAttestations(duration);
77
+ };
78
+ return stop.bind(this);
79
+ }
80
+
81
+ recordTimeToCollectAttestations(time: number) {
82
+ this.timeToCollectAttestations.record(time);
83
+ }
84
+
66
85
  recordCancelledBlock() {
67
86
  this.blockCounter.add(1, {
68
87
  [Attributes.STATUS]: 'cancelled',
@@ -15,6 +15,7 @@ import {
15
15
  AppendOnlyTreeSnapshot,
16
16
  ContentCommitment,
17
17
  GENESIS_ARCHIVE_ROOT,
18
+ type GlobalVariables,
18
19
  Header,
19
20
  StateReference,
20
21
  } from '@aztec/circuits.js';
@@ -112,6 +113,9 @@ export class Sequencer {
112
113
  this.updateConfig(config);
113
114
  this.metrics = new SequencerMetrics(telemetry, () => this.state, 'Sequencer');
114
115
  this.log.verbose(`Initialized sequencer with ${this.minTxsPerBLock}-${this.maxTxsPerBlock} txs per block.`);
116
+
117
+ // Register the block builder with the validator client for re-execution
118
+ this.validatorClient?.registerBlockBuilder(this.buildBlock.bind(this));
115
119
  }
116
120
 
117
121
  get tracer(): Tracer {
@@ -167,11 +171,11 @@ export class Sequencer {
167
171
  [SequencerState.IDLE]: this.aztecSlotDuration,
168
172
  [SequencerState.SYNCHRONIZING]: this.aztecSlotDuration,
169
173
  [SequencerState.PROPOSER_CHECK]: this.aztecSlotDuration, // We always want to allow the full slot to check if we are the proposer
170
- [SequencerState.WAITING_FOR_TXS]: 3,
171
- [SequencerState.CREATING_BLOCK]: 5,
172
- [SequencerState.PUBLISHING_BLOCK_TO_PEERS]: 5 + this.maxTxsPerBlock * 2, // if we take 5 seconds to create block, then 4 transactions at 2 seconds each
173
- [SequencerState.WAITING_FOR_ATTESTATIONS]: 5 + this.maxTxsPerBlock * 2 + 3, // it shouldn't take 3 seconds to publish to peers
174
- [SequencerState.PUBLISHING_BLOCK]: 5 + this.maxTxsPerBlock * 2 + 3 + 5, // wait 5 seconds for attestations
174
+ [SequencerState.WAITING_FOR_TXS]: 5,
175
+ [SequencerState.CREATING_BLOCK]: 7,
176
+ [SequencerState.PUBLISHING_BLOCK_TO_PEERS]: 7 + this.maxTxsPerBlock * 2, // if we take 5 seconds to create block, then 4 transactions at 2 seconds each
177
+ [SequencerState.WAITING_FOR_ATTESTATIONS]: 7 + this.maxTxsPerBlock * 2 + 3, // it shouldn't take 3 seconds to publish to peers
178
+ [SequencerState.PUBLISHING_BLOCK]: 7 + this.maxTxsPerBlock * 2 + 3 + 5, // wait 5 seconds for attestations
175
179
  };
176
180
  if (this.enforceTimeTable && newTimeTable[SequencerState.PUBLISHING_BLOCK] > this.aztecSlotDuration) {
177
181
  throw new Error('Sequencer cannot publish block in less than a slot');
@@ -185,7 +189,7 @@ export class Sequencer {
185
189
  public start() {
186
190
  this.runningPromise = new RunningPromise(this.work.bind(this), this.pollingIntervalMs);
187
191
  this.runningPromise.start();
188
- this.setState(SequencerState.IDLE, true /** force */);
192
+ this.setState(SequencerState.IDLE, 0, true /** force */);
189
193
  this.log.info('Sequencer started');
190
194
  return Promise.resolve();
191
195
  }
@@ -197,7 +201,7 @@ export class Sequencer {
197
201
  this.log.debug(`Stopping sequencer`);
198
202
  await this.runningPromise?.stop();
199
203
  this.publisher.interrupt();
200
- this.setState(SequencerState.STOPPED, true /** force */);
204
+ this.setState(SequencerState.STOPPED, 0, true /** force */);
201
205
  this.log.info('Stopped sequencer');
202
206
  }
203
207
 
@@ -208,7 +212,7 @@ export class Sequencer {
208
212
  this.log.info('Restarting sequencer');
209
213
  this.publisher.restart();
210
214
  this.runningPromise!.start();
211
- this.setState(SequencerState.IDLE, true /** force */);
215
+ this.setState(SequencerState.IDLE, 0, true /** force */);
212
216
  }
213
217
 
214
218
  /**
@@ -228,7 +232,7 @@ export class Sequencer {
228
232
  * - If our block for some reason is not included, revert the state
229
233
  */
230
234
  protected async doRealWork() {
231
- this.setState(SequencerState.SYNCHRONIZING);
235
+ this.setState(SequencerState.SYNCHRONIZING, 0);
232
236
  // Update state when the previous block has been synced
233
237
  const prevBlockSynced = await this.isBlockSynced();
234
238
  // Do not go forward with new block if the previous one has not been mined and processed
@@ -239,7 +243,7 @@ export class Sequencer {
239
243
 
240
244
  this.log.debug('Previous block has been mined and processed');
241
245
 
242
- this.setState(SequencerState.PROPOSER_CHECK);
246
+ this.setState(SequencerState.PROPOSER_CHECK, 0);
243
247
 
244
248
  const chainTip = await this.l2BlockSource.getBlock(-1);
245
249
  const historicalHeader = chainTip?.header;
@@ -273,8 +277,9 @@ export class Sequencer {
273
277
  if (!this.shouldProposeBlock(historicalHeader, {})) {
274
278
  return;
275
279
  }
280
+ const secondsIntoSlot = getSecondsIntoSlot(this.l1GenesisTime, this.aztecSlotDuration, Number(slot));
276
281
 
277
- this.setState(SequencerState.WAITING_FOR_TXS);
282
+ this.setState(SequencerState.WAITING_FOR_TXS, secondsIntoSlot);
278
283
 
279
284
  // Get txs to build the new block.
280
285
  const pendingTxs = this.p2pClient.getTxs('pending');
@@ -319,7 +324,7 @@ export class Sequencer {
319
324
  } catch (err) {
320
325
  this.log.error(`Error assembling block`, (err as any).stack);
321
326
  }
322
- this.setState(SequencerState.IDLE);
327
+ this.setState(SequencerState.IDLE, 0);
323
328
  }
324
329
 
325
330
  protected async work() {
@@ -333,7 +338,7 @@ export class Sequencer {
333
338
  throw err;
334
339
  }
335
340
  } finally {
336
- this.setState(SequencerState.IDLE);
341
+ this.setState(SequencerState.IDLE, 0);
337
342
  }
338
343
  }
339
344
 
@@ -392,14 +397,13 @@ export class Sequencer {
392
397
  return true;
393
398
  }
394
399
 
395
- setState(proposedState: SequencerState, force: boolean = false) {
400
+ setState(proposedState: SequencerState, secondsIntoSlot: number, force: boolean = false) {
396
401
  if (this.state === SequencerState.STOPPED && force !== true) {
397
402
  this.log.warn(
398
403
  `Cannot set sequencer from ${this.state} to ${proposedState} as it is stopped. Set force=true to override.`,
399
404
  );
400
405
  return;
401
406
  }
402
- const secondsIntoSlot = getSecondsIntoSlot(this.l1GenesisTime, this.aztecSlotDuration);
403
407
  if (!this.doIHaveEnoughTimeLeft(proposedState, secondsIntoSlot)) {
404
408
  throw new SequencerTooSlowError(this.state, proposedState, this.timeTable[proposedState], secondsIntoSlot);
405
409
  }
@@ -473,37 +477,21 @@ export class Sequencer {
473
477
  }
474
478
 
475
479
  /**
476
- * @notice Build and propose a block to the chain
480
+ * Build a block
477
481
  *
478
- * @dev MUST throw instead of exiting early to ensure that world-state
479
- * is being rolled back if the block is dropped.
482
+ * Shared between the sequencer and the validator for re-execution
480
483
  *
481
484
  * @param validTxs - The valid transactions to construct the block from
482
- * @param proposalHeader - The partial header constructed for the proposal
485
+ * @param newGlobalVariables - The global variables for the new block
483
486
  * @param historicalHeader - The historical header of the parent
487
+ * @param interrupt - The interrupt callback, used to validate the block for submission and check if we should propose the block
484
488
  */
485
- @trackSpan('Sequencer.buildBlockAndAttemptToPublish', (_validTxs, proposalHeader, _historicalHeader) => ({
486
- [Attributes.BLOCK_NUMBER]: proposalHeader.globalVariables.blockNumber.toNumber(),
487
- }))
488
- private async buildBlockAndAttemptToPublish(
489
+ private async buildBlock(
489
490
  validTxs: Tx[],
490
- proposalHeader: Header,
491
- historicalHeader: Header | undefined,
492
- ): Promise<void> {
493
- await this.publisher.validateBlockForSubmission(proposalHeader);
494
-
495
- const newGlobalVariables = proposalHeader.globalVariables;
496
-
497
- this.metrics.recordNewBlock(newGlobalVariables.blockNumber.toNumber(), validTxs.length);
498
- const workTimer = new Timer();
499
- this.setState(SequencerState.CREATING_BLOCK);
500
- this.log.info(
501
- `Building blockNumber=${newGlobalVariables.blockNumber.toNumber()} txCount=${
502
- validTxs.length
503
- } slotNumber=${newGlobalVariables.slotNumber.toNumber()}`,
504
- );
505
-
506
- // Get l1 to l2 messages from the contract
491
+ newGlobalVariables: GlobalVariables,
492
+ historicalHeader?: Header,
493
+ interrupt?: (processedTxs: ProcessedTx[]) => Promise<void>,
494
+ ) {
507
495
  this.log.debug('Requesting L1 to L2 messages from contract');
508
496
  const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(newGlobalVariables.blockNumber.toBigInt());
509
497
  this.log.verbose(
@@ -513,11 +501,15 @@ export class Sequencer {
513
501
  const numRealTxs = validTxs.length;
514
502
  const blockSize = Math.max(2, numRealTxs);
515
503
 
504
+ // Sync to the previous block at least
505
+ await this.worldState.syncImmediate(newGlobalVariables.blockNumber.toNumber() - 1);
506
+ this.log.verbose(`Synced to previous block ${newGlobalVariables.blockNumber.toNumber() - 1}`);
507
+
516
508
  // NB: separating the dbs because both should update the state
517
509
  const publicProcessorFork = await this.worldState.fork();
518
510
  const orchestratorFork = await this.worldState.fork();
511
+
519
512
  try {
520
- // We create a fresh processor each time to reset any cached state (eg storage writes)
521
513
  const processor = this.publicProcessorFactory.create(publicProcessorFork, historicalHeader, newGlobalVariables);
522
514
  const blockBuildingTimer = new Timer();
523
515
  const blockBuilder = this.blockBuilderFactory.create(orchestratorFork);
@@ -537,6 +529,62 @@ export class Sequencer {
537
529
  await this.p2pClient.deleteTxs(Tx.getHashes(failedTxData));
538
530
  }
539
531
 
532
+ await interrupt?.(processedTxs);
533
+
534
+ // All real transactions have been added, set the block as full and complete the proving.
535
+ const block = await blockBuilder.setBlockCompleted();
536
+
537
+ return { block, publicProcessorDuration, numProcessedTxs: processedTxs.length, blockBuildingTimer };
538
+ } finally {
539
+ // We create a fresh processor each time to reset any cached state (eg storage writes)
540
+ await publicProcessorFork.close();
541
+ await orchestratorFork.close();
542
+ }
543
+ }
544
+
545
+ /**
546
+ * @notice Build and propose a block to the chain
547
+ *
548
+ * @dev MUST throw instead of exiting early to ensure that world-state
549
+ * is being rolled back if the block is dropped.
550
+ *
551
+ * @param validTxs - The valid transactions to construct the block from
552
+ * @param proposalHeader - The partial header constructed for the proposal
553
+ * @param historicalHeader - The historical header of the parent
554
+ */
555
+ @trackSpan('Sequencer.buildBlockAndAttemptToPublish', (_validTxs, proposalHeader, _historicalHeader) => ({
556
+ [Attributes.BLOCK_NUMBER]: proposalHeader.globalVariables.blockNumber.toNumber(),
557
+ }))
558
+ private async buildBlockAndAttemptToPublish(
559
+ validTxs: Tx[],
560
+ proposalHeader: Header,
561
+ historicalHeader: Header | undefined,
562
+ ): Promise<void> {
563
+ await this.publisher.validateBlockForSubmission(proposalHeader);
564
+
565
+ const newGlobalVariables = proposalHeader.globalVariables;
566
+
567
+ this.metrics.recordNewBlock(newGlobalVariables.blockNumber.toNumber(), validTxs.length);
568
+ const workTimer = new Timer();
569
+ const secondsIntoSlot = getSecondsIntoSlot(
570
+ this.l1GenesisTime,
571
+ this.aztecSlotDuration,
572
+ newGlobalVariables.slotNumber.toNumber(),
573
+ );
574
+ this.setState(SequencerState.CREATING_BLOCK, secondsIntoSlot);
575
+ this.log.info(
576
+ `Building blockNumber=${newGlobalVariables.blockNumber.toNumber()} txCount=${
577
+ validTxs.length
578
+ } slotNumber=${newGlobalVariables.slotNumber.toNumber()}`,
579
+ );
580
+
581
+ /**
582
+ * BuildBlock is shared between the sequencer and the validator for re-execution
583
+ * We use the interrupt callback to validate the block for submission and check if we should propose the block
584
+ *
585
+ * If we fail, we throw an error in order to roll back
586
+ */
587
+ const interrupt = async (processedTxs: ProcessedTx[]) => {
540
588
  await this.publisher.validateBlockForSubmission(proposalHeader);
541
589
 
542
590
  if (
@@ -548,9 +596,15 @@ export class Sequencer {
548
596
  // TODO: Roll back changes to world state
549
597
  throw new Error('Should not propose the block');
550
598
  }
599
+ };
551
600
 
552
- // All real transactions have been added, set the block as full and complete the proving.
553
- const block = await blockBuilder.setBlockCompleted();
601
+ try {
602
+ const { block, publicProcessorDuration, numProcessedTxs, blockBuildingTimer } = await this.buildBlock(
603
+ validTxs,
604
+ newGlobalVariables,
605
+ historicalHeader,
606
+ interrupt,
607
+ );
554
608
 
555
609
  // TODO(@PhilWindle) We should probably periodically check for things like another
556
610
  // block being published before ours instead of just waiting on our block
@@ -558,7 +612,7 @@ export class Sequencer {
558
612
  await this.publisher.validateBlockForSubmission(block.header);
559
613
 
560
614
  const workDuration = workTimer.ms();
561
- this.log.verbose(
615
+ this.log.info(
562
616
  `Assembled block ${block.number} (txEffectsHash: ${block.header.contentCommitment.txsEffectsHash.toString(
563
617
  'hex',
564
618
  )})`,
@@ -573,35 +627,32 @@ export class Sequencer {
573
627
  );
574
628
 
575
629
  if (this.isFlushing) {
576
- this.log.verbose(`Flushing completed`);
630
+ this.log.info(`Flushing completed`);
577
631
  }
578
632
 
579
633
  const txHashes = validTxs.map(tx => tx.getTxHash());
580
634
 
581
635
  this.isFlushing = false;
582
636
  this.log.verbose('Collecting attestations');
637
+ const stopCollectingAttestationsTimer = this.metrics.startCollectingAttestationsTimer();
583
638
  const attestations = await this.collectAttestations(block, txHashes);
584
639
  this.log.verbose('Attestations collected');
585
-
640
+ stopCollectingAttestationsTimer();
586
641
  this.log.verbose('Collecting proof quotes');
642
+
587
643
  const proofQuote = await this.createProofClaimForPreviousEpoch(newGlobalVariables.slotNumber.toBigInt());
588
- this.log.verbose(proofQuote ? `Using proof quote ${inspect(proofQuote.payload)}` : 'No proof quote available');
589
-
590
- try {
591
- await this.publishL2Block(block, attestations, txHashes, proofQuote);
592
- this.metrics.recordPublishedBlock(workDuration);
593
- this.log.info(
594
- `Submitted rollup block ${block.number} with ${processedTxs.length} transactions duration=${Math.ceil(
595
- workDuration,
596
- )}ms (Submitter: ${this.publisher.getSenderAddress()})`,
597
- );
598
- } catch (err) {
599
- this.metrics.recordFailedBlock();
600
- throw err;
601
- }
602
- } finally {
603
- await publicProcessorFork.close();
604
- await orchestratorFork.close();
644
+ this.log.info(proofQuote ? `Using proof quote ${inspect(proofQuote.payload)}` : 'No proof quote available');
645
+
646
+ await this.publishL2Block(block, attestations, txHashes, proofQuote);
647
+ this.metrics.recordPublishedBlock(workDuration);
648
+ this.log.info(
649
+ `Submitted rollup block ${block.number} with ${numProcessedTxs} transactions duration=${Math.ceil(
650
+ workDuration,
651
+ )}ms (Submitter: ${this.publisher.getSenderAddress()})`,
652
+ );
653
+ } catch (err) {
654
+ this.metrics.recordFailedBlock();
655
+ throw err;
605
656
  }
606
657
  }
607
658
 
@@ -621,7 +672,7 @@ export class Sequencer {
621
672
  this.log.debug(`Attesting committee length ${committee.length}`);
622
673
 
623
674
  if (committee.length === 0) {
624
- this.log.debug(`Attesting committee length is 0, skipping`);
675
+ this.log.verbose(`Attesting committee length is 0, skipping`);
625
676
  return undefined;
626
677
  }
627
678
 
@@ -633,16 +684,28 @@ export class Sequencer {
633
684
 
634
685
  const numberOfRequiredAttestations = Math.floor((committee.length * 2) / 3) + 1;
635
686
 
636
- this.log.verbose('Creating block proposal');
687
+ this.log.info('Creating block proposal');
637
688
  const proposal = await this.validatorClient.createBlockProposal(block.header, block.archive.root, txHashes);
638
689
 
639
- this.setState(SequencerState.PUBLISHING_BLOCK_TO_PEERS);
640
- this.log.verbose('Broadcasting block proposal to validators');
690
+ let secondsIntoSlot = getSecondsIntoSlot(
691
+ this.l1GenesisTime,
692
+ this.aztecSlotDuration,
693
+ block.header.globalVariables.slotNumber.toNumber(),
694
+ );
695
+
696
+ this.setState(SequencerState.PUBLISHING_BLOCK_TO_PEERS, secondsIntoSlot);
697
+ this.log.info('Broadcasting block proposal to validators');
641
698
  this.validatorClient.broadcastBlockProposal(proposal);
642
699
 
643
- this.setState(SequencerState.WAITING_FOR_ATTESTATIONS);
700
+ secondsIntoSlot = getSecondsIntoSlot(
701
+ this.l1GenesisTime,
702
+ this.aztecSlotDuration,
703
+ block.header.globalVariables.slotNumber.toNumber(),
704
+ );
705
+
706
+ this.setState(SequencerState.WAITING_FOR_ATTESTATIONS, secondsIntoSlot);
644
707
  const attestations = await this.validatorClient.collectAttestations(proposal, numberOfRequiredAttestations);
645
- this.log.verbose(`Collected attestations from validators, number of attestations: ${attestations.length}`);
708
+ this.log.info(`Collected attestations from validators, number of attestations: ${attestations.length}`);
646
709
 
647
710
  // note: the smart contract requires that the signatures are provided in the order of the committee
648
711
  return orderAttestations(attestations, committee);
@@ -659,7 +722,7 @@ export class Sequencer {
659
722
 
660
723
  // Get quotes for the epoch to be proven
661
724
  const quotes = await this.p2pClient.getEpochProofQuotes(epochToProve);
662
- this.log.verbose(`Retrieved ${quotes.length} quotes, slot: ${slotNumber}, epoch to prove: ${epochToProve}`);
725
+ this.log.info(`Retrieved ${quotes.length} quotes, slot: ${slotNumber}, epoch to prove: ${epochToProve}`);
663
726
  for (const quote of quotes) {
664
727
  this.log.verbose(inspect(quote.payload));
665
728
  }
@@ -670,7 +733,7 @@ export class Sequencer {
670
733
 
671
734
  const validQuotes = (await validQuotesPromise).filter((q): q is EpochProofQuote => !!q);
672
735
  if (!validQuotes.length) {
673
- this.log.verbose(`Failed to find any valid proof quotes`);
736
+ this.log.warn(`Failed to find any valid proof quotes`);
674
737
  return undefined;
675
738
  }
676
739
  // pick the quote with the lowest fee
@@ -697,8 +760,13 @@ export class Sequencer {
697
760
  txHashes?: TxHash[],
698
761
  proofQuote?: EpochProofQuote,
699
762
  ) {
763
+ const secondsIntoSlot = getSecondsIntoSlot(
764
+ this.l1GenesisTime,
765
+ this.aztecSlotDuration,
766
+ block.header.globalVariables.slotNumber.toNumber(),
767
+ );
700
768
  // Publishes new block to the network and awaits the tx to be mined
701
- this.setState(SequencerState.PUBLISHING_BLOCK);
769
+ this.setState(SequencerState.PUBLISHING_BLOCK, secondsIntoSlot);
702
770
 
703
771
  const publishedL2Block = await this.publisher.proposeL2Block(block, attestations, txHashes, proofQuote);
704
772
  if (!publishedL2Block) {
@@ -73,6 +73,7 @@ export function orderAttestations(attestations: BlockAttestation[], orderAddress
73
73
  return orderedAttestations;
74
74
  }
75
75
 
76
- export function getSecondsIntoSlot(l1GenesisTime: number, aztecSlotDuration: number): number {
77
- return (Date.now() / 1000 - l1GenesisTime) % aztecSlotDuration;
76
+ export function getSecondsIntoSlot(l1GenesisTime: number, aztecSlotDuration: number, slotNumber: number): number {
77
+ const slotStartTimestamp = l1GenesisTime + slotNumber * aztecSlotDuration;
78
+ return Date.now() / 1000 - slotStartTimestamp;
78
79
  }