@aztec/sequencer-client 4.0.0-nightly.20260112 → 4.0.0-nightly.20260114

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 (59) hide show
  1. package/dest/client/sequencer-client.d.ts +4 -5
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/config.d.ts +1 -1
  4. package/dest/config.d.ts.map +1 -1
  5. package/dest/config.js +8 -1
  6. package/dest/index.d.ts +2 -3
  7. package/dest/index.d.ts.map +1 -1
  8. package/dest/index.js +1 -2
  9. package/dest/publisher/sequencer-publisher.d.ts +15 -15
  10. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  11. package/dest/publisher/sequencer-publisher.js +44 -46
  12. package/dest/sequencer/block_builder.d.ts +2 -2
  13. package/dest/sequencer/block_builder.d.ts.map +1 -1
  14. package/dest/sequencer/block_builder.js +5 -5
  15. package/dest/sequencer/checkpoint_proposal_job.d.ts +11 -8
  16. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  17. package/dest/sequencer/checkpoint_proposal_job.js +56 -30
  18. package/dest/sequencer/index.d.ts +1 -2
  19. package/dest/sequencer/index.d.ts.map +1 -1
  20. package/dest/sequencer/index.js +0 -1
  21. package/dest/sequencer/metrics.d.ts +3 -3
  22. package/dest/sequencer/metrics.d.ts.map +1 -1
  23. package/dest/sequencer/metrics.js +4 -4
  24. package/dest/sequencer/sequencer.d.ts +13 -12
  25. package/dest/sequencer/sequencer.d.ts.map +1 -1
  26. package/dest/sequencer/sequencer.js +30 -28
  27. package/dest/test/index.d.ts +2 -3
  28. package/dest/test/index.d.ts.map +1 -1
  29. package/dest/test/mock_checkpoint_builder.d.ts +13 -3
  30. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  31. package/dest/test/mock_checkpoint_builder.js +27 -2
  32. package/dest/test/utils.d.ts +8 -4
  33. package/dest/test/utils.d.ts.map +1 -1
  34. package/dest/test/utils.js +19 -9
  35. package/package.json +27 -27
  36. package/src/client/sequencer-client.ts +3 -4
  37. package/src/config.ts +7 -0
  38. package/src/index.ts +0 -3
  39. package/src/publisher/sequencer-publisher.ts +75 -70
  40. package/src/sequencer/block_builder.ts +5 -3
  41. package/src/sequencer/checkpoint_proposal_job.ts +68 -38
  42. package/src/sequencer/index.ts +0 -1
  43. package/src/sequencer/metrics.ts +4 -4
  44. package/src/sequencer/sequencer.ts +47 -34
  45. package/src/test/index.ts +1 -2
  46. package/src/test/mock_checkpoint_builder.ts +45 -3
  47. package/src/test/utils.ts +36 -9
  48. package/dest/sequencer/checkpoint_builder.d.ts +0 -63
  49. package/dest/sequencer/checkpoint_builder.d.ts.map +0 -1
  50. package/dest/sequencer/checkpoint_builder.js +0 -131
  51. package/dest/tx_validator/nullifier_cache.d.ts +0 -14
  52. package/dest/tx_validator/nullifier_cache.d.ts.map +0 -1
  53. package/dest/tx_validator/nullifier_cache.js +0 -24
  54. package/dest/tx_validator/tx_validator_factory.d.ts +0 -18
  55. package/dest/tx_validator/tx_validator_factory.d.ts.map +0 -1
  56. package/dest/tx_validator/tx_validator_factory.js +0 -53
  57. package/src/sequencer/checkpoint_builder.ts +0 -217
  58. package/src/tx_validator/nullifier_cache.ts +0 -30
  59. package/src/tx_validator/tx_validator_factory.ts +0 -133
@@ -25,7 +25,7 @@ import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs'
25
25
  import { FormattedViemError, formatViemError, tryExtractEvent } from '@aztec/ethereum/utils';
26
26
  import { sumBigint } from '@aztec/foundation/bigint';
27
27
  import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
28
- import { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
28
+ import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
29
29
  import { pick } from '@aztec/foundation/collection';
30
30
  import type { Fr } from '@aztec/foundation/curves/bn254';
31
31
  import { EthAddress } from '@aztec/foundation/eth-address';
@@ -35,7 +35,7 @@ import { bufferToHex } from '@aztec/foundation/string';
35
35
  import { DateProvider, Timer } from '@aztec/foundation/timer';
36
36
  import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
37
37
  import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
38
- import { CommitteeAttestationsAndSigners, type ValidateBlockResult } from '@aztec/stdlib/block';
38
+ import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
39
39
  import type { Checkpoint } from '@aztec/stdlib/checkpoint';
40
40
  import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
41
41
  import type { CheckpointHeader } from '@aztec/stdlib/rollup';
@@ -80,12 +80,12 @@ type GovernanceSignalAction = Extract<Action, 'governance-signal' | 'empire-slas
80
80
  // Sorting for actions such that invalidations go before proposals, and proposals go before votes
81
81
  export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
82
82
 
83
- export type InvalidateBlockRequest = {
83
+ export type InvalidateCheckpointRequest = {
84
84
  request: L1TxRequest;
85
85
  reason: 'invalid-attestation' | 'insufficient-attestations';
86
86
  gasUsed: bigint;
87
- blockNumber: BlockNumber;
88
- forcePendingBlockNumber: BlockNumber;
87
+ checkpointNumber: CheckpointNumber;
88
+ forcePendingCheckpointNumber: CheckpointNumber;
89
89
  };
90
90
 
91
91
  interface RequestWithExpiry {
@@ -417,17 +417,14 @@ export class SequencerPublisher {
417
417
  public canProposeAtNextEthBlock(
418
418
  tipArchive: Fr,
419
419
  msgSender: EthAddress,
420
- opts: { forcePendingBlockNumber?: BlockNumber } = {},
420
+ opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
421
421
  ) {
422
422
  // TODO: #14291 - should loop through multiple keys to check if any of them can propose
423
423
  const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
424
424
 
425
425
  return this.rollupContract
426
426
  .canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
427
- forcePendingCheckpointNumber:
428
- opts.forcePendingBlockNumber !== undefined
429
- ? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber)
430
- : undefined,
427
+ forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
431
428
  })
432
429
  .catch(err => {
433
430
  if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
@@ -449,7 +446,7 @@ export class SequencerPublisher {
449
446
  @trackSpan('SequencerPublisher.validateBlockHeader')
450
447
  public async validateBlockHeader(
451
448
  header: CheckpointHeader,
452
- opts?: { forcePendingBlockNumber: BlockNumber | undefined },
449
+ opts?: { forcePendingCheckpointNumber: CheckpointNumber | undefined },
453
450
  ): Promise<void> {
454
451
  const flags = { ignoreDA: true, ignoreSignatures: true };
455
452
 
@@ -464,12 +461,8 @@ export class SequencerPublisher {
464
461
  ] as const;
465
462
 
466
463
  const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
467
- const optsForcePendingCheckpointNumber =
468
- opts?.forcePendingBlockNumber !== undefined
469
- ? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber)
470
- : undefined;
471
464
  const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
472
- optsForcePendingCheckpointNumber,
465
+ opts?.forcePendingCheckpointNumber,
473
466
  );
474
467
  let balance = 0n;
475
468
  if (this.config.fishermanMode) {
@@ -497,77 +490,90 @@ export class SequencerPublisher {
497
490
  }
498
491
 
499
492
  /**
500
- * Simulate making a call to invalidate a block with invalid attestations. Returns undefined if no need to invalidate.
501
- * @param block - The block to invalidate and the criteria for invalidation (as returned by the archiver)
493
+ * Simulate making a call to invalidate a checkpoint with invalid attestations. Returns undefined if no need to invalidate.
494
+ * @param validationResult - The validation result indicating which checkpoint to invalidate (as returned by the archiver)
502
495
  */
503
- public async simulateInvalidateBlock(
504
- validationResult: ValidateBlockResult,
505
- ): Promise<InvalidateBlockRequest | undefined> {
496
+ public async simulateInvalidateCheckpoint(
497
+ validationResult: ValidateCheckpointResult,
498
+ ): Promise<InvalidateCheckpointRequest | undefined> {
506
499
  if (validationResult.valid) {
507
500
  return undefined;
508
501
  }
509
502
 
510
- const { reason, block } = validationResult;
511
- const blockNumber = block.blockNumber;
512
- const logData = { ...block, reason };
503
+ const { reason, checkpoint } = validationResult;
504
+ const checkpointNumber = checkpoint.checkpointNumber;
505
+ const logData = { ...checkpoint, reason };
513
506
 
514
- const currentBlockNumber = await this.rollupContract.getCheckpointNumber();
515
- if (currentBlockNumber < validationResult.block.blockNumber) {
507
+ const currentCheckpointNumber = await this.rollupContract.getCheckpointNumber();
508
+ if (currentCheckpointNumber < checkpointNumber) {
516
509
  this.log.verbose(
517
- `Skipping block ${blockNumber} invalidation since it has already been removed from the pending chain`,
518
- { currentBlockNumber, ...logData },
510
+ `Skipping checkpoint ${checkpointNumber} invalidation since it has already been removed from the pending chain`,
511
+ { currentCheckpointNumber, ...logData },
519
512
  );
520
513
  return undefined;
521
514
  }
522
515
 
523
- const request = this.buildInvalidateBlockRequest(validationResult);
524
- this.log.debug(`Simulating invalidate block ${blockNumber}`, { ...logData, request });
516
+ const request = this.buildInvalidateCheckpointRequest(validationResult);
517
+ this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
525
518
 
526
519
  try {
527
520
  const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, ErrorsAbi);
528
- this.log.verbose(`Simulation for invalidate block ${blockNumber} succeeded`, { ...logData, request, gasUsed });
521
+ this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} succeeded`, {
522
+ ...logData,
523
+ request,
524
+ gasUsed,
525
+ });
529
526
 
530
- return { request, gasUsed, blockNumber, forcePendingBlockNumber: BlockNumber(blockNumber - 1), reason };
527
+ return {
528
+ request,
529
+ gasUsed,
530
+ checkpointNumber,
531
+ forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
532
+ reason,
533
+ };
531
534
  } catch (err) {
532
535
  const viemError = formatViemError(err);
533
536
 
534
- // If the error is due to the block not being in the pending chain, and it was indeed removed by someone else,
535
- // we can safely ignore it and return undefined so we go ahead with block building.
537
+ // If the error is due to the checkpoint not being in the pending chain, and it was indeed removed by someone else,
538
+ // we can safely ignore it and return undefined so we go ahead with checkpoint building.
536
539
  if (viemError.message?.includes('Rollup__BlockNotInPendingChain')) {
537
540
  this.log.verbose(
538
- `Simulation for invalidate block ${blockNumber} failed due to block not being in pending chain`,
541
+ `Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
539
542
  { ...logData, request, error: viemError.message },
540
543
  );
541
- const latestPendingBlockNumber = await this.rollupContract.getCheckpointNumber();
542
- if (latestPendingBlockNumber < blockNumber) {
543
- this.log.verbose(`Block number ${blockNumber} has already been invalidated`, { ...logData });
544
+ const latestPendingCheckpointNumber = await this.rollupContract.getCheckpointNumber();
545
+ if (latestPendingCheckpointNumber < checkpointNumber) {
546
+ this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, { ...logData });
544
547
  return undefined;
545
548
  } else {
546
549
  this.log.error(
547
- `Simulation for invalidate ${blockNumber} failed and it is still in pending chain`,
550
+ `Simulation for invalidate checkpoint ${checkpointNumber} failed and it is still in pending chain`,
548
551
  viemError,
549
552
  logData,
550
553
  );
551
- throw new Error(`Failed to simulate invalidate block ${blockNumber} while it is still in pending chain`, {
552
- cause: viemError,
553
- });
554
+ throw new Error(
555
+ `Failed to simulate invalidate checkpoint ${checkpointNumber} while it is still in pending chain`,
556
+ {
557
+ cause: viemError,
558
+ },
559
+ );
554
560
  }
555
561
  }
556
562
 
557
- // Otherwise, throw. We cannot build the next block if we cannot invalidate the previous one.
558
- this.log.error(`Simulation for invalidate block ${blockNumber} failed`, viemError, logData);
559
- throw new Error(`Failed to simulate invalidate block ${blockNumber}`, { cause: viemError });
563
+ // Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
564
+ this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
565
+ throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
560
566
  }
561
567
  }
562
568
 
563
- private buildInvalidateBlockRequest(validationResult: ValidateBlockResult) {
569
+ private buildInvalidateCheckpointRequest(validationResult: ValidateCheckpointResult) {
564
570
  if (validationResult.valid) {
565
- throw new Error('Cannot invalidate a valid block');
571
+ throw new Error('Cannot invalidate a valid checkpoint');
566
572
  }
567
573
 
568
- const { block, committee, reason } = validationResult;
569
- const logData = { ...block, reason };
570
- this.log.debug(`Simulating invalidate block ${block.blockNumber}`, logData);
574
+ const { checkpoint, committee, reason } = validationResult;
575
+ const logData = { ...checkpoint, reason };
576
+ this.log.debug(`Building invalidate checkpoint ${checkpoint.checkpointNumber} request`, logData);
571
577
 
572
578
  const attestationsAndSigners = new CommitteeAttestationsAndSigners(
573
579
  validationResult.attestations,
@@ -575,14 +581,14 @@ export class SequencerPublisher {
575
581
 
576
582
  if (reason === 'invalid-attestation') {
577
583
  return this.rollupContract.buildInvalidateBadAttestationRequest(
578
- CheckpointNumber.fromBlockNumber(block.blockNumber),
584
+ checkpoint.checkpointNumber,
579
585
  attestationsAndSigners,
580
586
  committee,
581
587
  validationResult.invalidIndex,
582
588
  );
583
589
  } else if (reason === 'insufficient-attestations') {
584
590
  return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
585
- CheckpointNumber.fromBlockNumber(block.blockNumber),
591
+ checkpoint.checkpointNumber,
586
592
  attestationsAndSigners,
587
593
  committee,
588
594
  );
@@ -598,7 +604,7 @@ export class SequencerPublisher {
598
604
  checkpoint: Checkpoint,
599
605
  attestationsAndSigners: CommitteeAttestationsAndSigners,
600
606
  attestationsAndSignersSignature: Signature,
601
- options: { forcePendingBlockNumber?: BlockNumber }, // TODO(palla/mbps): Should this be forcePendingCheckpointNumber?
607
+ options: { forcePendingCheckpointNumber?: CheckpointNumber },
602
608
  ): Promise<bigint> {
603
609
  const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
604
610
 
@@ -897,7 +903,7 @@ export class SequencerPublisher {
897
903
  checkpoint: Checkpoint,
898
904
  attestationsAndSigners: CommitteeAttestationsAndSigners,
899
905
  attestationsAndSignersSignature: Signature,
900
- opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: BlockNumber } = {},
906
+ opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
901
907
  ): Promise<void> {
902
908
  const checkpointHeader = checkpoint.header;
903
909
 
@@ -930,7 +936,7 @@ export class SequencerPublisher {
930
936
  this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
931
937
  ...checkpoint.getStats(),
932
938
  slotNumber: checkpoint.header.slotNumber,
933
- forcePendingBlockNumber: opts.forcePendingBlockNumber,
939
+ forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
934
940
  });
935
941
  throw err;
936
942
  }
@@ -939,7 +945,10 @@ export class SequencerPublisher {
939
945
  await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
940
946
  }
941
947
 
942
- public enqueueInvalidateBlock(request: InvalidateBlockRequest | undefined, opts: { txTimeoutAt?: Date } = {}) {
948
+ public enqueueInvalidateCheckpoint(
949
+ request: InvalidateCheckpointRequest | undefined,
950
+ opts: { txTimeoutAt?: Date } = {},
951
+ ) {
943
952
  if (!request) {
944
953
  return;
945
954
  }
@@ -947,9 +956,9 @@ export class SequencerPublisher {
947
956
  // We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
948
957
  const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(request.gasUsed) * 64) / 63)));
949
958
 
950
- const { gasUsed, blockNumber } = request;
951
- const logData = { gasUsed, blockNumber, gasLimit, opts };
952
- this.log.verbose(`Enqueuing invalidate block request`, logData);
959
+ const { gasUsed, checkpointNumber } = request;
960
+ const logData = { gasUsed, checkpointNumber, gasLimit, opts };
961
+ this.log.verbose(`Enqueuing invalidate checkpoint request`, logData);
953
962
  this.addRequest({
954
963
  action: `invalidate-by-${request.reason}`,
955
964
  request: request.request,
@@ -962,9 +971,9 @@ export class SequencerPublisher {
962
971
  result.receipt.status === 'success' &&
963
972
  tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointInvalidated');
964
973
  if (!success) {
965
- this.log.warn(`Invalidate block ${request.blockNumber} failed`, { ...result, ...logData });
974
+ this.log.warn(`Invalidate checkpoint ${request.checkpointNumber} failed`, { ...result, ...logData });
966
975
  } else {
967
- this.log.info(`Invalidate block ${request.blockNumber} succeeded`, { ...result, ...logData });
976
+ this.log.info(`Invalidate checkpoint ${request.checkpointNumber} succeeded`, { ...result, ...logData });
968
977
  }
969
978
  return !!success;
970
979
  },
@@ -1043,7 +1052,7 @@ export class SequencerPublisher {
1043
1052
  private async prepareProposeTx(
1044
1053
  encodedData: L1ProcessArgs,
1045
1054
  timestamp: bigint,
1046
- options: { forcePendingBlockNumber?: BlockNumber },
1055
+ options: { forcePendingCheckpointNumber?: CheckpointNumber },
1047
1056
  ) {
1048
1057
  const kzg = Blob.getViemKzgInstance();
1049
1058
  const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
@@ -1124,7 +1133,7 @@ export class SequencerPublisher {
1124
1133
  `0x${string}`,
1125
1134
  ],
1126
1135
  timestamp: bigint,
1127
- options: { forcePendingBlockNumber?: BlockNumber },
1136
+ options: { forcePendingCheckpointNumber?: CheckpointNumber },
1128
1137
  ) {
1129
1138
  const rollupData = encodeFunctionData({
1130
1139
  abi: RollupAbi,
@@ -1133,13 +1142,9 @@ export class SequencerPublisher {
1133
1142
  });
1134
1143
 
1135
1144
  // override the pending checkpoint number if requested
1136
- const optsForcePendingCheckpointNumber =
1137
- options.forcePendingBlockNumber !== undefined
1138
- ? CheckpointNumber.fromBlockNumber(options.forcePendingBlockNumber)
1139
- : undefined;
1140
1145
  const forcePendingCheckpointNumberStateDiff = (
1141
- optsForcePendingCheckpointNumber !== undefined
1142
- ? await this.rollupContract.makePendingCheckpointNumberOverride(optsForcePendingCheckpointNumber)
1146
+ options.forcePendingCheckpointNumber !== undefined
1147
+ ? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber)
1143
1148
  : []
1144
1149
  ).flatMap(override => override.stateDiff ?? []);
1145
1150
 
@@ -1203,7 +1208,7 @@ export class SequencerPublisher {
1203
1208
  private async addProposeTx(
1204
1209
  checkpoint: Checkpoint,
1205
1210
  encodedData: L1ProcessArgs,
1206
- opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: BlockNumber } = {},
1211
+ opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
1207
1212
  timestamp: bigint,
1208
1213
  ): Promise<void> {
1209
1214
  const slot = checkpoint.header.slotNumber;
@@ -28,8 +28,7 @@ import type {
28
28
  } from '@aztec/stdlib/interfaces/server';
29
29
  import { GlobalVariables, Tx } from '@aztec/stdlib/tx';
30
30
  import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
31
-
32
- import { createValidatorForBlockBuilding } from '../tx_validator/tx_validator_factory.js';
31
+ import { createValidatorForBlockBuilding } from '@aztec/validator-client';
33
32
 
34
33
  const log = createLogger('block-builder');
35
34
 
@@ -37,6 +36,7 @@ const log = createLogger('block-builder');
37
36
  async function buildBlock(
38
37
  pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
39
38
  l1ToL2Messages: Fr[],
39
+ previousCheckpointOutHashes: Fr[],
40
40
  newGlobalVariables: GlobalVariables,
41
41
  opts: PublicProcessorLimits = {},
42
42
  worldStateFork: MerkleTreeWriteOperations,
@@ -63,7 +63,7 @@ async function buildBlock(
63
63
  initialArchiveRoot: bufferToHex(archiveTree.root),
64
64
  opts,
65
65
  });
66
- const blockFactory = new LightweightBlockFactory(worldStateFork, telemetryClient);
66
+ const blockFactory = new LightweightBlockFactory(previousCheckpointOutHashes, worldStateFork, telemetryClient);
67
67
  await blockFactory.startNewBlock(newGlobalVariables, l1ToL2Messages);
68
68
 
69
69
  const [publicProcessorDuration, [processedTxs, failedTxs, usedTxs]] = await elapsed(() =>
@@ -168,6 +168,7 @@ export class FullNodeBlockBuilder implements IFullNodeBlockBuilder {
168
168
  async buildBlock(
169
169
  pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
170
170
  l1ToL2Messages: Fr[],
171
+ previousCheckpointOutHashes: Fr[],
171
172
  globalVariables: GlobalVariables,
172
173
  opts: PublicProcessorLimits,
173
174
  suppliedFork?: MerkleTreeWriteOperations,
@@ -182,6 +183,7 @@ export class FullNodeBlockBuilder implements IFullNodeBlockBuilder {
182
183
  const res = await buildBlock(
183
184
  pendingTxs,
184
185
  l1ToL2Messages,
186
+ previousCheckpointOutHashes,
185
187
  globalVariables,
186
188
  opts,
187
189
  fork,
@@ -2,6 +2,7 @@ import { BLOBS_PER_CHECKPOINT, FIELDS_PER_BLOB } from '@aztec/constants';
2
2
  import type { EpochCache } from '@aztec/epoch-cache';
3
3
  import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
4
4
  import { randomInt } from '@aztec/foundation/crypto/random';
5
+ import { Fr } from '@aztec/foundation/curves/bn254';
5
6
  import { EthAddress } from '@aztec/foundation/eth-address';
6
7
  import { Signature } from '@aztec/foundation/eth-signature';
7
8
  import { filter } from '@aztec/foundation/iterator';
@@ -15,6 +16,8 @@ import {
15
16
  CommitteeAttestation,
16
17
  CommitteeAttestationsAndSigners,
17
18
  L2BlockNew,
19
+ type L2BlockSink,
20
+ type L2BlockSource,
18
21
  MaliciousCommitteeAttestationsAndSigners,
19
22
  } from '@aztec/stdlib/block';
20
23
  import type { Checkpoint } from '@aztec/stdlib/checkpoint';
@@ -25,19 +28,17 @@ import type {
25
28
  ResolvedSequencerConfig,
26
29
  WorldStateSynchronizer,
27
30
  } from '@aztec/stdlib/interfaces/server';
28
- import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
29
- import type { BlockProposal, BlockProposalOptions } from '@aztec/stdlib/p2p';
31
+ import { type L1ToL2MessageSource, computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
32
+ import type { BlockProposalOptions, CheckpointProposal, CheckpointProposalOptions } from '@aztec/stdlib/p2p';
30
33
  import { orderAttestations } from '@aztec/stdlib/p2p';
31
- import { CheckpointHeader } from '@aztec/stdlib/rollup';
32
34
  import type { L2BlockBuiltStats } from '@aztec/stdlib/stats';
33
35
  import { type FailedTx, Tx } from '@aztec/stdlib/tx';
34
36
  import { AttestationTimeoutError } from '@aztec/stdlib/validators';
35
37
  import { Attributes, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
36
- import type { ValidatorClient } from '@aztec/validator-client';
38
+ import { CheckpointBuilder, type FullNodeCheckpointsBuilder, type ValidatorClient } from '@aztec/validator-client';
37
39
 
38
40
  import type { GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
39
- import type { InvalidateBlockRequest, SequencerPublisher } from '../publisher/sequencer-publisher.js';
40
- import { CheckpointBuilder, type FullNodeCheckpointsBuilder } from './checkpoint_builder.js';
41
+ import type { InvalidateCheckpointRequest, SequencerPublisher } from '../publisher/sequencer-publisher.js';
41
42
  import { CheckpointVoter } from './checkpoint_voter.js';
42
43
  import { SequencerInterruptedError } from './errors.js';
43
44
  import type { SequencerEvents } from './events.js';
@@ -57,6 +58,7 @@ const TXS_POLLING_MS = 500;
57
58
  */
58
59
  export class CheckpointProposalJob implements Traceable {
59
60
  constructor(
61
+ private readonly epoch: EpochNumber,
60
62
  private readonly slot: SlotNumber,
61
63
  private readonly checkpointNumber: CheckpointNumber,
62
64
  private readonly syncedToBlockNumber: BlockNumber,
@@ -64,13 +66,15 @@ export class CheckpointProposalJob implements Traceable {
64
66
  private readonly proposer: EthAddress | undefined,
65
67
  private readonly publisher: SequencerPublisher,
66
68
  private readonly attestorAddress: EthAddress,
67
- private readonly invalidateBlock: InvalidateBlockRequest | undefined,
69
+ private readonly invalidateCheckpoint: InvalidateCheckpointRequest | undefined,
68
70
  private readonly validatorClient: ValidatorClient,
69
71
  private readonly globalsBuilder: GlobalVariableBuilder,
70
72
  private readonly p2pClient: P2P,
71
73
  private readonly worldState: WorldStateSynchronizer,
72
74
  private readonly l1ToL2MessageSource: L1ToL2MessageSource,
75
+ private readonly l2BlockSource: L2BlockSource,
73
76
  private readonly checkpointsBuilder: FullNodeCheckpointsBuilder,
77
+ private readonly blockSink: L2BlockSink,
74
78
  private readonly l1Constants: SequencerRollupConstants,
75
79
  protected config: ResolvedSequencerConfig,
76
80
  protected timetable: SequencerTimetable,
@@ -152,9 +156,9 @@ export class CheckpointProposalJob implements Traceable {
152
156
  this.setStateFn(SequencerState.INITIALIZING_CHECKPOINT, this.slot);
153
157
  this.metrics.incOpenSlot(this.slot, this.proposer?.toString() ?? 'unknown');
154
158
 
155
- // Enqueues block invalidation (constant for the whole slot)
156
- if (this.invalidateBlock && !this.config.skipInvalidateBlockAsProposer) {
157
- this.publisher.enqueueInvalidateBlock(this.invalidateBlock);
159
+ // Enqueues checkpoint invalidation (constant for the whole slot)
160
+ if (this.invalidateCheckpoint && !this.config.skipInvalidateBlockAsProposer) {
161
+ this.publisher.enqueueInvalidateCheckpoint(this.invalidateCheckpoint);
158
162
  }
159
163
 
160
164
  // Create checkpoint builder for the slot
@@ -164,8 +168,15 @@ export class CheckpointProposalJob implements Traceable {
164
168
  this.slot,
165
169
  );
166
170
 
167
- // Collect L1 to L2 messages for the checkpoint
171
+ // Collect L1 to L2 messages for the checkpoint and compute their hash
168
172
  const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(this.checkpointNumber);
173
+ const inHash = computeInHashFromL1ToL2Messages(l1ToL2Messages);
174
+
175
+ // Collect the out hashes of all the checkpoints before this one in the same epoch
176
+ const previousCheckpoints = (await this.l2BlockSource.getCheckpointsForEpoch(this.epoch)).filter(
177
+ c => c.number < this.checkpointNumber,
178
+ );
179
+ const previousCheckpointOutHashes = previousCheckpoints.map(c => c.getCheckpointOutHash());
169
180
 
170
181
  // Create a long-lived forked world state for the checkpoint builder
171
182
  using fork = await this.worldState.fork(this.syncedToBlockNumber, { closeDelayMs: 12_000 });
@@ -175,6 +186,7 @@ export class CheckpointProposalJob implements Traceable {
175
186
  this.checkpointNumber,
176
187
  checkpointGlobalVariables,
177
188
  l1ToL2Messages,
189
+ previousCheckpointOutHashes,
178
190
  fork,
179
191
  );
180
192
 
@@ -184,10 +196,16 @@ export class CheckpointProposalJob implements Traceable {
184
196
  broadcastInvalidBlockProposal: this.config.broadcastInvalidBlockProposal,
185
197
  };
186
198
 
199
+ const checkpointProposalOptions: CheckpointProposalOptions = {
200
+ publishFullTxs: !!this.config.publishTxsWithProposals,
201
+ broadcastInvalidCheckpointProposal: this.config.broadcastInvalidBlockProposal,
202
+ };
203
+
187
204
  // Main loop: build blocks for the checkpoint
188
- const { blocksInCheckpoint, pendingBroadcast } = await this.buildBlocksForCheckpoint(
205
+ const { blocksInCheckpoint, blockPendingBroadcast } = await this.buildBlocksForCheckpoint(
189
206
  checkpointBuilder,
190
207
  checkpointGlobalVariables.timestamp,
208
+ inHash,
191
209
  blockProposalOptions,
192
210
  );
193
211
 
@@ -217,22 +235,30 @@ export class CheckpointProposalJob implements Traceable {
217
235
  return checkpoint;
218
236
  }
219
237
 
220
- // TODO(palla/mbps): Wire this to the new p2p API once available, including the pendingBroadcast.block
238
+ // Include the block pending broadcast in the checkpoint proposal if any
239
+ const lastBlock = blockPendingBroadcast && {
240
+ blockHeader: blockPendingBroadcast.block.header,
241
+ indexWithinCheckpoint: blockPendingBroadcast.block.indexWithinCheckpoint,
242
+ txs: blockPendingBroadcast.txs,
243
+ };
244
+
245
+ // Create the checkpoint proposal and broadcast it
221
246
  const proposal = await this.validatorClient.createCheckpointProposal(
222
247
  checkpoint.header,
223
248
  checkpoint.archive.root,
224
- pendingBroadcast?.txs ?? [],
249
+ lastBlock,
225
250
  this.proposer,
226
- blockProposalOptions,
251
+ checkpointProposalOptions,
227
252
  );
253
+
228
254
  const blockProposedAt = this.dateProvider.now();
229
- await this.p2pClient.broadcastProposal(proposal);
255
+ await this.p2pClient.broadcastCheckpointProposal(proposal);
230
256
 
231
257
  this.setStateFn(SequencerState.COLLECTING_ATTESTATIONS, this.slot);
232
258
  const attestations = await this.waitForAttestations(proposal);
233
259
  const blockAttestedAt = this.dateProvider.now();
234
260
 
235
- this.metrics.recordBlockAttestationDelay(blockAttestedAt - blockProposedAt);
261
+ this.metrics.recordCheckpointAttestationDelay(blockAttestedAt - blockProposedAt);
236
262
 
237
263
  // Proposer must sign over the attestations before pushing them to L1
238
264
  const signer = this.proposer ?? this.publisher.getSenderAddress();
@@ -245,7 +271,7 @@ export class CheckpointProposalJob implements Traceable {
245
271
  const txTimeoutAt = new Date((slotStartBuildTimestamp + aztecSlotDuration) * 1000);
246
272
  await this.publisher.enqueueProposeCheckpoint(checkpoint, attestations, attestationsSignature, {
247
273
  txTimeoutAt,
248
- forcePendingBlockNumber: this.invalidateBlock?.forcePendingBlockNumber,
274
+ forcePendingCheckpointNumber: this.invalidateCheckpoint?.forcePendingCheckpointNumber,
249
275
  });
250
276
 
251
277
  return checkpoint;
@@ -262,17 +288,18 @@ export class CheckpointProposalJob implements Traceable {
262
288
  private async buildBlocksForCheckpoint(
263
289
  checkpointBuilder: CheckpointBuilder,
264
290
  timestamp: bigint,
291
+ inHash: Fr,
265
292
  blockProposalOptions: BlockProposalOptions,
266
293
  ): Promise<{
267
294
  blocksInCheckpoint: L2BlockNew[];
268
- pendingBroadcast: { block: L2BlockNew; txs: Tx[] } | undefined;
295
+ blockPendingBroadcast: { block: L2BlockNew; txs: Tx[] } | undefined;
269
296
  }> {
270
297
  const blocksInCheckpoint: L2BlockNew[] = [];
271
298
  const txHashesAlreadyIncluded = new Set<string>();
272
299
  const initialBlockNumber = BlockNumber(this.syncedToBlockNumber + 1);
273
300
 
274
301
  // Last block in the checkpoint will usually be flagged as pending broadcast, so we send it along with the checkpoint proposal
275
- let pendingBroadcast: { block: L2BlockNew; txs: Tx[] } | undefined = undefined;
302
+ let blockPendingBroadcast: { block: L2BlockNew; txs: Tx[] } | undefined = undefined;
276
303
 
277
304
  while (true) {
278
305
  const blocksBuilt = blocksInCheckpoint.length;
@@ -342,17 +369,17 @@ export class CheckpointProposalJob implements Traceable {
342
369
  blockNumber,
343
370
  blocksBuilt,
344
371
  });
345
- pendingBroadcast = { block, txs: usedTxs };
372
+ blockPendingBroadcast = { block, txs: usedTxs };
346
373
  break;
347
374
  }
348
375
 
349
376
  // For non-last blocks, broadcast the block proposal (unless we're in fisherman mode)
350
377
  // If the block is the last one, we'll broadcast it along with the checkpoint at the end of the loop
351
378
  if (!this.config.fishermanMode) {
352
- // TODO(palla/mbps): Wire this to the new p2p API once available
353
379
  const proposal = await this.validatorClient.createBlockProposal(
354
- block.header.globalVariables.blockNumber,
355
- (await checkpointBuilder.getCheckpoint()).header,
380
+ block.header,
381
+ block.indexWithinCheckpoint,
382
+ inHash,
356
383
  block.archive.root,
357
384
  usedTxs,
358
385
  this.proposer,
@@ -370,10 +397,7 @@ export class CheckpointProposalJob implements Traceable {
370
397
  blocksBuilt: blocksInCheckpoint.length,
371
398
  });
372
399
 
373
- return {
374
- blocksInCheckpoint,
375
- pendingBroadcast,
376
- };
400
+ return { blocksInCheckpoint, blockPendingBroadcast };
377
401
  }
378
402
 
379
403
  /** Sleeps until it is time to produce the next block in the slot */
@@ -539,7 +563,7 @@ export class CheckpointProposalJob implements Traceable {
539
563
  * This is run after all blocks for the checkpoint have been built.
540
564
  */
541
565
  @trackSpan('CheckpointProposalJob.waitForAttestations')
542
- private async waitForAttestations(proposal: BlockProposal): Promise<CommitteeAttestationsAndSigners> {
566
+ private async waitForAttestations(proposal: CheckpointProposal): Promise<CommitteeAttestationsAndSigners> {
543
567
  if (this.config.fishermanMode) {
544
568
  this.log.debug('Skipping attestation collection in fisherman mode');
545
569
  return CommitteeAttestationsAndSigners.empty();
@@ -588,8 +612,7 @@ export class CheckpointProposalJob implements Traceable {
588
612
 
589
613
  // Manipulate the attestations if we've been configured to do so
590
614
  if (this.config.injectFakeAttestation || this.config.shuffleAttestationOrdering) {
591
- const checkpoint = proposal.payload.header;
592
- return this.manipulateAttestations(checkpoint, epoch, seed, committee, sorted);
615
+ return this.manipulateAttestations(proposal.slotNumber, epoch, seed, committee, sorted);
593
616
  }
594
617
 
595
618
  return new CommitteeAttestationsAndSigners(sorted);
@@ -605,7 +628,7 @@ export class CheckpointProposalJob implements Traceable {
605
628
 
606
629
  /** Breaks the attestations before publishing based on attack configs */
607
630
  private manipulateAttestations(
608
- checkpoint: CheckpointHeader,
631
+ slotNumber: SlotNumber,
609
632
  epoch: EpochNumber,
610
633
  seed: bigint,
611
634
  committee: EthAddress[],
@@ -613,7 +636,6 @@ export class CheckpointProposalJob implements Traceable {
613
636
  ) {
614
637
  // Compute the proposer index in the committee, since we dont want to tweak it.
615
638
  // Otherwise, the L1 rollup contract will reject the block outright.
616
- const { slotNumber } = checkpoint;
617
639
  const proposerIndex = Number(
618
640
  this.epochCache.computeProposerIndex(slotNumber, epoch, seed, BigInt(committee.length)),
619
641
  );
@@ -662,16 +684,24 @@ export class CheckpointProposalJob implements Traceable {
662
684
  }
663
685
 
664
686
  /**
665
- * Placeholder for pushing block to archiver and waiting for sync.
666
- * To be implemented when archiver and world-state support proposed blocks.
687
+ * Adds the proposed block to the archiver so it's available via P2P.
688
+ * Gossip doesn't echo messages back to the sender, so the proposer's archiver/world-state
689
+ * would never receive its own block without this explicit sync.
667
690
  */
668
691
  private async syncProposedBlockToArchiver(block: L2BlockNew): Promise<void> {
669
- this.log.debug(`Syncing proposed block ${block.number}`, {
692
+ // TODO(palla/mbps): Change default to false once block sync is stable.
693
+ if (this.config.skipPushProposedBlocksToArchiver !== false) {
694
+ this.log.warn(`Skipping push of proposed block ${block.number} to archiver`, {
695
+ blockNumber: block.number,
696
+ slot: block.header.globalVariables.slotNumber,
697
+ });
698
+ return;
699
+ }
700
+ this.log.debug(`Syncing proposed block ${block.number} to archiver`, {
670
701
  blockNumber: block.number,
671
702
  slot: block.header.globalVariables.slotNumber,
672
703
  });
673
- // TODO(palla/mbps): Implement actual sync to archiver and world-state
674
- await Promise.resolve();
704
+ await this.blockSink.addBlock(block);
675
705
  }
676
706
 
677
707
  /** Runs fee analysis and logs checkpoint outcome as fisherman */
@@ -1,5 +1,4 @@
1
1
  export * from './block_builder.js';
2
- export * from './checkpoint_builder.js';
3
2
  export * from './checkpoint_proposal_job.js';
4
3
  export * from './checkpoint_voter.js';
5
4
  export * from './config.js';