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

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 +1 -1
  13. package/dest/sequencer/block_builder.d.ts.map +1 -1
  14. package/dest/sequencer/block_builder.js +1 -1
  15. package/dest/sequencer/checkpoint_proposal_job.d.ts +8 -7
  16. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  17. package/dest/sequencer/checkpoint_proposal_job.js +47 -28
  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 +12 -11
  25. package/dest/sequencer/sequencer.d.ts.map +1 -1
  26. package/dest/sequencer/sequencer.js +28 -26
  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 +10 -2
  30. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  31. package/dest/test/mock_checkpoint_builder.js +23 -0
  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 +18 -8
  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 +1 -2
  41. package/src/sequencer/checkpoint_proposal_job.ts +58 -38
  42. package/src/sequencer/index.ts +0 -1
  43. package/src/sequencer/metrics.ts +4 -4
  44. package/src/sequencer/sequencer.ts +41 -33
  45. package/src/test/index.ts +1 -2
  46. package/src/test/mock_checkpoint_builder.ts +34 -2
  47. package/src/test/utils.ts +35 -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
 
@@ -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,7 @@ import {
15
16
  CommitteeAttestation,
16
17
  CommitteeAttestationsAndSigners,
17
18
  L2BlockNew,
19
+ type L2BlockSink,
18
20
  MaliciousCommitteeAttestationsAndSigners,
19
21
  } from '@aztec/stdlib/block';
20
22
  import type { Checkpoint } from '@aztec/stdlib/checkpoint';
@@ -25,19 +27,17 @@ import type {
25
27
  ResolvedSequencerConfig,
26
28
  WorldStateSynchronizer,
27
29
  } from '@aztec/stdlib/interfaces/server';
28
- import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
29
- import type { BlockProposal, BlockProposalOptions } from '@aztec/stdlib/p2p';
30
+ import { type L1ToL2MessageSource, computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
31
+ import type { BlockProposalOptions, CheckpointProposal, CheckpointProposalOptions } from '@aztec/stdlib/p2p';
30
32
  import { orderAttestations } from '@aztec/stdlib/p2p';
31
- import { CheckpointHeader } from '@aztec/stdlib/rollup';
32
33
  import type { L2BlockBuiltStats } from '@aztec/stdlib/stats';
33
34
  import { type FailedTx, Tx } from '@aztec/stdlib/tx';
34
35
  import { AttestationTimeoutError } from '@aztec/stdlib/validators';
35
36
  import { Attributes, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
36
- import type { ValidatorClient } from '@aztec/validator-client';
37
+ import { CheckpointBuilder, type FullNodeCheckpointsBuilder, type ValidatorClient } from '@aztec/validator-client';
37
38
 
38
39
  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';
40
+ import type { InvalidateCheckpointRequest, SequencerPublisher } from '../publisher/sequencer-publisher.js';
41
41
  import { CheckpointVoter } from './checkpoint_voter.js';
42
42
  import { SequencerInterruptedError } from './errors.js';
43
43
  import type { SequencerEvents } from './events.js';
@@ -64,13 +64,14 @@ export class CheckpointProposalJob implements Traceable {
64
64
  private readonly proposer: EthAddress | undefined,
65
65
  private readonly publisher: SequencerPublisher,
66
66
  private readonly attestorAddress: EthAddress,
67
- private readonly invalidateBlock: InvalidateBlockRequest | undefined,
67
+ private readonly invalidateCheckpoint: InvalidateCheckpointRequest | undefined,
68
68
  private readonly validatorClient: ValidatorClient,
69
69
  private readonly globalsBuilder: GlobalVariableBuilder,
70
70
  private readonly p2pClient: P2P,
71
71
  private readonly worldState: WorldStateSynchronizer,
72
72
  private readonly l1ToL2MessageSource: L1ToL2MessageSource,
73
73
  private readonly checkpointsBuilder: FullNodeCheckpointsBuilder,
74
+ private readonly blockSink: L2BlockSink,
74
75
  private readonly l1Constants: SequencerRollupConstants,
75
76
  protected config: ResolvedSequencerConfig,
76
77
  protected timetable: SequencerTimetable,
@@ -152,9 +153,9 @@ export class CheckpointProposalJob implements Traceable {
152
153
  this.setStateFn(SequencerState.INITIALIZING_CHECKPOINT, this.slot);
153
154
  this.metrics.incOpenSlot(this.slot, this.proposer?.toString() ?? 'unknown');
154
155
 
155
- // Enqueues block invalidation (constant for the whole slot)
156
- if (this.invalidateBlock && !this.config.skipInvalidateBlockAsProposer) {
157
- this.publisher.enqueueInvalidateBlock(this.invalidateBlock);
156
+ // Enqueues checkpoint invalidation (constant for the whole slot)
157
+ if (this.invalidateCheckpoint && !this.config.skipInvalidateBlockAsProposer) {
158
+ this.publisher.enqueueInvalidateCheckpoint(this.invalidateCheckpoint);
158
159
  }
159
160
 
160
161
  // Create checkpoint builder for the slot
@@ -164,8 +165,9 @@ export class CheckpointProposalJob implements Traceable {
164
165
  this.slot,
165
166
  );
166
167
 
167
- // Collect L1 to L2 messages for the checkpoint
168
+ // Collect L1 to L2 messages for the checkpoint and compute their hash
168
169
  const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(this.checkpointNumber);
170
+ const inHash = computeInHashFromL1ToL2Messages(l1ToL2Messages);
169
171
 
170
172
  // Create a long-lived forked world state for the checkpoint builder
171
173
  using fork = await this.worldState.fork(this.syncedToBlockNumber, { closeDelayMs: 12_000 });
@@ -184,10 +186,16 @@ export class CheckpointProposalJob implements Traceable {
184
186
  broadcastInvalidBlockProposal: this.config.broadcastInvalidBlockProposal,
185
187
  };
186
188
 
189
+ const checkpointProposalOptions: CheckpointProposalOptions = {
190
+ publishFullTxs: !!this.config.publishTxsWithProposals,
191
+ broadcastInvalidCheckpointProposal: this.config.broadcastInvalidBlockProposal,
192
+ };
193
+
187
194
  // Main loop: build blocks for the checkpoint
188
- const { blocksInCheckpoint, pendingBroadcast } = await this.buildBlocksForCheckpoint(
195
+ const { blocksInCheckpoint, blockPendingBroadcast } = await this.buildBlocksForCheckpoint(
189
196
  checkpointBuilder,
190
197
  checkpointGlobalVariables.timestamp,
198
+ inHash,
191
199
  blockProposalOptions,
192
200
  );
193
201
 
@@ -217,22 +225,30 @@ export class CheckpointProposalJob implements Traceable {
217
225
  return checkpoint;
218
226
  }
219
227
 
220
- // TODO(palla/mbps): Wire this to the new p2p API once available, including the pendingBroadcast.block
228
+ // Include the block pending broadcast in the checkpoint proposal if any
229
+ const lastBlock = blockPendingBroadcast && {
230
+ blockHeader: blockPendingBroadcast.block.header,
231
+ indexWithinCheckpoint: blockPendingBroadcast.block.indexWithinCheckpoint,
232
+ txs: blockPendingBroadcast.txs,
233
+ };
234
+
235
+ // Create the checkpoint proposal and broadcast it
221
236
  const proposal = await this.validatorClient.createCheckpointProposal(
222
237
  checkpoint.header,
223
238
  checkpoint.archive.root,
224
- pendingBroadcast?.txs ?? [],
239
+ lastBlock,
225
240
  this.proposer,
226
- blockProposalOptions,
241
+ checkpointProposalOptions,
227
242
  );
243
+
228
244
  const blockProposedAt = this.dateProvider.now();
229
- await this.p2pClient.broadcastProposal(proposal);
245
+ await this.p2pClient.broadcastCheckpointProposal(proposal);
230
246
 
231
247
  this.setStateFn(SequencerState.COLLECTING_ATTESTATIONS, this.slot);
232
248
  const attestations = await this.waitForAttestations(proposal);
233
249
  const blockAttestedAt = this.dateProvider.now();
234
250
 
235
- this.metrics.recordBlockAttestationDelay(blockAttestedAt - blockProposedAt);
251
+ this.metrics.recordCheckpointAttestationDelay(blockAttestedAt - blockProposedAt);
236
252
 
237
253
  // Proposer must sign over the attestations before pushing them to L1
238
254
  const signer = this.proposer ?? this.publisher.getSenderAddress();
@@ -245,7 +261,7 @@ export class CheckpointProposalJob implements Traceable {
245
261
  const txTimeoutAt = new Date((slotStartBuildTimestamp + aztecSlotDuration) * 1000);
246
262
  await this.publisher.enqueueProposeCheckpoint(checkpoint, attestations, attestationsSignature, {
247
263
  txTimeoutAt,
248
- forcePendingBlockNumber: this.invalidateBlock?.forcePendingBlockNumber,
264
+ forcePendingCheckpointNumber: this.invalidateCheckpoint?.forcePendingCheckpointNumber,
249
265
  });
250
266
 
251
267
  return checkpoint;
@@ -262,17 +278,18 @@ export class CheckpointProposalJob implements Traceable {
262
278
  private async buildBlocksForCheckpoint(
263
279
  checkpointBuilder: CheckpointBuilder,
264
280
  timestamp: bigint,
281
+ inHash: Fr,
265
282
  blockProposalOptions: BlockProposalOptions,
266
283
  ): Promise<{
267
284
  blocksInCheckpoint: L2BlockNew[];
268
- pendingBroadcast: { block: L2BlockNew; txs: Tx[] } | undefined;
285
+ blockPendingBroadcast: { block: L2BlockNew; txs: Tx[] } | undefined;
269
286
  }> {
270
287
  const blocksInCheckpoint: L2BlockNew[] = [];
271
288
  const txHashesAlreadyIncluded = new Set<string>();
272
289
  const initialBlockNumber = BlockNumber(this.syncedToBlockNumber + 1);
273
290
 
274
291
  // 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;
292
+ let blockPendingBroadcast: { block: L2BlockNew; txs: Tx[] } | undefined = undefined;
276
293
 
277
294
  while (true) {
278
295
  const blocksBuilt = blocksInCheckpoint.length;
@@ -342,17 +359,17 @@ export class CheckpointProposalJob implements Traceable {
342
359
  blockNumber,
343
360
  blocksBuilt,
344
361
  });
345
- pendingBroadcast = { block, txs: usedTxs };
362
+ blockPendingBroadcast = { block, txs: usedTxs };
346
363
  break;
347
364
  }
348
365
 
349
366
  // For non-last blocks, broadcast the block proposal (unless we're in fisherman mode)
350
367
  // If the block is the last one, we'll broadcast it along with the checkpoint at the end of the loop
351
368
  if (!this.config.fishermanMode) {
352
- // TODO(palla/mbps): Wire this to the new p2p API once available
353
369
  const proposal = await this.validatorClient.createBlockProposal(
354
- block.header.globalVariables.blockNumber,
355
- (await checkpointBuilder.getCheckpoint()).header,
370
+ block.header,
371
+ block.indexWithinCheckpoint,
372
+ inHash,
356
373
  block.archive.root,
357
374
  usedTxs,
358
375
  this.proposer,
@@ -370,10 +387,7 @@ export class CheckpointProposalJob implements Traceable {
370
387
  blocksBuilt: blocksInCheckpoint.length,
371
388
  });
372
389
 
373
- return {
374
- blocksInCheckpoint,
375
- pendingBroadcast,
376
- };
390
+ return { blocksInCheckpoint, blockPendingBroadcast };
377
391
  }
378
392
 
379
393
  /** Sleeps until it is time to produce the next block in the slot */
@@ -539,7 +553,7 @@ export class CheckpointProposalJob implements Traceable {
539
553
  * This is run after all blocks for the checkpoint have been built.
540
554
  */
541
555
  @trackSpan('CheckpointProposalJob.waitForAttestations')
542
- private async waitForAttestations(proposal: BlockProposal): Promise<CommitteeAttestationsAndSigners> {
556
+ private async waitForAttestations(proposal: CheckpointProposal): Promise<CommitteeAttestationsAndSigners> {
543
557
  if (this.config.fishermanMode) {
544
558
  this.log.debug('Skipping attestation collection in fisherman mode');
545
559
  return CommitteeAttestationsAndSigners.empty();
@@ -588,8 +602,7 @@ export class CheckpointProposalJob implements Traceable {
588
602
 
589
603
  // Manipulate the attestations if we've been configured to do so
590
604
  if (this.config.injectFakeAttestation || this.config.shuffleAttestationOrdering) {
591
- const checkpoint = proposal.payload.header;
592
- return this.manipulateAttestations(checkpoint, epoch, seed, committee, sorted);
605
+ return this.manipulateAttestations(proposal.slotNumber, epoch, seed, committee, sorted);
593
606
  }
594
607
 
595
608
  return new CommitteeAttestationsAndSigners(sorted);
@@ -605,7 +618,7 @@ export class CheckpointProposalJob implements Traceable {
605
618
 
606
619
  /** Breaks the attestations before publishing based on attack configs */
607
620
  private manipulateAttestations(
608
- checkpoint: CheckpointHeader,
621
+ slotNumber: SlotNumber,
609
622
  epoch: EpochNumber,
610
623
  seed: bigint,
611
624
  committee: EthAddress[],
@@ -613,7 +626,6 @@ export class CheckpointProposalJob implements Traceable {
613
626
  ) {
614
627
  // Compute the proposer index in the committee, since we dont want to tweak it.
615
628
  // Otherwise, the L1 rollup contract will reject the block outright.
616
- const { slotNumber } = checkpoint;
617
629
  const proposerIndex = Number(
618
630
  this.epochCache.computeProposerIndex(slotNumber, epoch, seed, BigInt(committee.length)),
619
631
  );
@@ -662,16 +674,24 @@ export class CheckpointProposalJob implements Traceable {
662
674
  }
663
675
 
664
676
  /**
665
- * Placeholder for pushing block to archiver and waiting for sync.
666
- * To be implemented when archiver and world-state support proposed blocks.
677
+ * Adds the proposed block to the archiver so it's available via P2P.
678
+ * Gossip doesn't echo messages back to the sender, so the proposer's archiver/world-state
679
+ * would never receive its own block without this explicit sync.
667
680
  */
668
681
  private async syncProposedBlockToArchiver(block: L2BlockNew): Promise<void> {
669
- this.log.debug(`Syncing proposed block ${block.number}`, {
682
+ // TODO(palla/mbps): Change default to false once block sync is stable.
683
+ if (this.config.skipPushProposedBlocksToArchiver !== false) {
684
+ this.log.warn(`Skipping push of proposed block ${block.number} to archiver`, {
685
+ blockNumber: block.number,
686
+ slot: block.header.globalVariables.slotNumber,
687
+ });
688
+ return;
689
+ }
690
+ this.log.debug(`Syncing proposed block ${block.number} to archiver`, {
670
691
  blockNumber: block.number,
671
692
  slot: block.header.globalVariables.slotNumber,
672
693
  });
673
- // TODO(palla/mbps): Implement actual sync to archiver and world-state
674
- await Promise.resolve();
694
+ await this.blockSink.addBlock(block);
675
695
  }
676
696
 
677
697
  /** 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';
@@ -43,7 +43,7 @@ export class SequencerMetrics {
43
43
  private blockProposalPrecheckFailed: UpDownCounter;
44
44
  private checkpointSuccess: UpDownCounter;
45
45
  private slashingAttempts: UpDownCounter;
46
- private blockAttestationDelay: Histogram;
46
+ private checkpointAttestationDelay: Histogram;
47
47
 
48
48
  // Fisherman fee analysis metrics
49
49
  private fishermanWouldBeIncluded: UpDownCounter;
@@ -75,7 +75,7 @@ export class SequencerMetrics {
75
75
 
76
76
  this.stateTransitionBufferDuration = this.meter.createHistogram(Metrics.SEQUENCER_STATE_TRANSITION_BUFFER_DURATION);
77
77
 
78
- this.blockAttestationDelay = this.meter.createHistogram(Metrics.SEQUENCER_BLOCK_ATTESTATION_DELAY);
78
+ this.checkpointAttestationDelay = this.meter.createHistogram(Metrics.SEQUENCER_CHECKPOINT_ATTESTATION_DELAY);
79
79
 
80
80
  // Init gauges and counters
81
81
  this.blockCounter.add(0, {
@@ -156,8 +156,8 @@ export class SequencerMetrics {
156
156
  this.timeToCollectAttestations.record(0);
157
157
  }
158
158
 
159
- public recordBlockAttestationDelay(duration: number) {
160
- this.blockAttestationDelay.record(duration);
159
+ public recordCheckpointAttestationDelay(duration: number) {
160
+ this.checkpointAttestationDelay.record(duration);
161
161
  }
162
162
 
163
163
  public recordCollectedAttestations(count: number, durationMs: number) {