@aztec/sequencer-client 2.0.3 → 2.1.0-rc.2

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 (44) hide show
  1. package/dest/client/sequencer-client.d.ts.map +1 -1
  2. package/dest/client/sequencer-client.js +7 -4
  3. package/dest/config.d.ts +2 -1
  4. package/dest/config.d.ts.map +1 -1
  5. package/dest/config.js +2 -0
  6. package/dest/publisher/config.d.ts +2 -4
  7. package/dest/publisher/config.d.ts.map +1 -1
  8. package/dest/publisher/config.js +7 -10
  9. package/dest/publisher/index.d.ts +1 -1
  10. package/dest/publisher/index.d.ts.map +1 -1
  11. package/dest/publisher/index.js +1 -1
  12. package/dest/publisher/sequencer-publisher-factory.d.ts +5 -1
  13. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  14. package/dest/publisher/sequencer-publisher-factory.js +8 -1
  15. package/dest/publisher/sequencer-publisher.d.ts +14 -20
  16. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  17. package/dest/publisher/sequencer-publisher.js +53 -58
  18. package/dest/sequencer/errors.d.ts +11 -0
  19. package/dest/sequencer/errors.d.ts.map +1 -0
  20. package/dest/sequencer/errors.js +15 -0
  21. package/dest/sequencer/metrics.d.ts +5 -17
  22. package/dest/sequencer/metrics.d.ts.map +1 -1
  23. package/dest/sequencer/metrics.js +22 -88
  24. package/dest/sequencer/sequencer.d.ts +4 -3
  25. package/dest/sequencer/sequencer.d.ts.map +1 -1
  26. package/dest/sequencer/sequencer.js +27 -21
  27. package/dest/sequencer/timetable.d.ts +0 -6
  28. package/dest/sequencer/timetable.d.ts.map +1 -1
  29. package/dest/sequencer/timetable.js +2 -9
  30. package/dest/sequencer/utils.d.ts +10 -24
  31. package/dest/sequencer/utils.d.ts.map +1 -1
  32. package/dest/sequencer/utils.js +9 -24
  33. package/package.json +28 -28
  34. package/src/client/sequencer-client.ts +6 -2
  35. package/src/config.ts +3 -0
  36. package/src/publisher/config.ts +13 -11
  37. package/src/publisher/index.ts +1 -1
  38. package/src/publisher/sequencer-publisher-factory.ts +12 -2
  39. package/src/publisher/sequencer-publisher.ts +77 -77
  40. package/src/sequencer/errors.ts +21 -0
  41. package/src/sequencer/metrics.ts +24 -100
  42. package/src/sequencer/sequencer.ts +51 -46
  43. package/src/sequencer/timetable.ts +2 -13
  44. package/src/sequencer/utils.ts +10 -24
@@ -5,12 +5,11 @@ import type { EpochCache } from '@aztec/epoch-cache';
5
5
  import {
6
6
  type EmpireSlashingProposerContract,
7
7
  FormattedViemError,
8
- type GasPrice,
9
8
  type GovernanceProposerContract,
10
9
  type IEmpireBase,
11
10
  type L1BlobInputs,
12
11
  type L1ContractsConfig,
13
- type L1GasConfig,
12
+ type L1TxConfig,
14
13
  type L1TxRequest,
15
14
  MULTI_CALL_3_ADDRESS,
16
15
  Multicall3,
@@ -27,20 +26,19 @@ import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs'
27
26
  import { sumBigint } from '@aztec/foundation/bigint';
28
27
  import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
29
28
  import { EthAddress } from '@aztec/foundation/eth-address';
29
+ import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
30
30
  import type { Fr } from '@aztec/foundation/fields';
31
- import { createLogger } from '@aztec/foundation/log';
31
+ import { type Logger, createLogger } from '@aztec/foundation/log';
32
32
  import { bufferToHex } from '@aztec/foundation/string';
33
33
  import { DateProvider, Timer } from '@aztec/foundation/timer';
34
34
  import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
35
35
  import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
36
- import { CommitteeAttestation, type ValidateBlockResult } from '@aztec/stdlib/block';
36
+ import { CommitteeAttestation, CommitteeAttestationsAndSigners, type ValidateBlockResult } from '@aztec/stdlib/block';
37
37
  import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
38
- import { ConsensusPayload, SignatureDomainSeparator, getHashedSignaturePayload } from '@aztec/stdlib/p2p';
39
38
  import type { L1PublishBlockStats } from '@aztec/stdlib/stats';
40
- import { type ProposedBlockHeader, StateReference, TxHash } from '@aztec/stdlib/tx';
39
+ import { type ProposedBlockHeader, StateReference } from '@aztec/stdlib/tx';
41
40
  import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
42
41
 
43
- import pick from 'lodash.pick';
44
42
  import { type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
45
43
 
46
44
  import type { PublisherConfig, TxSenderConfig } from './config.js';
@@ -56,17 +54,12 @@ type L1ProcessArgs = {
56
54
  stateReference: StateReference;
57
55
  /** L2 block blobs containing all tx effects. */
58
56
  blobs: Blob[];
59
- /** L2 block tx hashes */
60
- txHashes: TxHash[];
61
57
  /** Attestations */
62
- attestations?: CommitteeAttestation[];
58
+ attestationsAndSigners: CommitteeAttestationsAndSigners;
59
+ /** Attestations and signers signature */
60
+ attestationsAndSignersSignature: Signature;
63
61
  };
64
62
 
65
- export enum SignalType {
66
- GOVERNANCE,
67
- SLASHING,
68
- }
69
-
70
63
  export const Actions = [
71
64
  'invalidate-by-invalid-attestation',
72
65
  'invalidate-by-insufficient-attestations',
@@ -78,8 +71,11 @@ export const Actions = [
78
71
  'vote-offenses',
79
72
  'execute-slash',
80
73
  ] as const;
74
+
81
75
  export type Action = (typeof Actions)[number];
82
76
 
77
+ type GovernanceSignalAction = Extract<Action, 'governance-signal' | 'empire-slashing-signal'>;
78
+
83
79
  // Sorting for actions such that invalidations go before proposals, and proposals go before votes
84
80
  export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
85
81
 
@@ -95,11 +91,11 @@ interface RequestWithExpiry {
95
91
  action: Action;
96
92
  request: L1TxRequest;
97
93
  lastValidL2Slot: bigint;
98
- gasConfig?: Pick<L1GasConfig, 'txTimeoutAt' | 'gasLimit'>;
94
+ gasConfig?: Pick<L1TxConfig, 'txTimeoutAt' | 'gasLimit'>;
99
95
  blobConfig?: L1BlobInputs;
100
96
  checkSuccess: (
101
97
  request: L1TxRequest,
102
- result?: { receipt: TransactionReceipt; gasPrice: GasPrice; stats?: TransactionStats; errorMsg?: string },
98
+ result?: { receipt: TransactionReceipt; stats?: TransactionStats; errorMsg?: string },
103
99
  ) => boolean;
104
100
  }
105
101
 
@@ -111,12 +107,9 @@ export class SequencerPublisher {
111
107
  protected governanceLog = createLogger('sequencer:publisher:governance');
112
108
  protected slashingLog = createLogger('sequencer:publisher:slashing');
113
109
 
114
- private myLastSignals: Record<SignalType, bigint> = {
115
- [SignalType.GOVERNANCE]: 0n,
116
- [SignalType.SLASHING]: 0n,
117
- };
110
+ protected lastActions: Partial<Record<Action, bigint>> = {};
118
111
 
119
- protected log = createLogger('sequencer:publisher');
112
+ protected log: Logger;
120
113
  protected ethereumSlotDuration: bigint;
121
114
 
122
115
  private blobSinkClient: BlobSinkClientInterface;
@@ -152,10 +145,14 @@ export class SequencerPublisher {
152
145
  epochCache: EpochCache;
153
146
  dateProvider: DateProvider;
154
147
  metrics: SequencerPublisherMetrics;
148
+ lastActions: Partial<Record<Action, bigint>>;
149
+ log?: Logger;
155
150
  },
156
151
  ) {
152
+ this.log = deps.log ?? createLogger('sequencer:publisher');
157
153
  this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
158
154
  this.epochCache = deps.epochCache;
155
+ this.lastActions = deps.lastActions;
159
156
 
160
157
  this.blobSinkClient =
161
158
  deps.blobSinkClient ?? createBlobSinkClient(config, { logger: createLogger('sequencer:blob-sink:client') });
@@ -249,18 +246,21 @@ export class SequencerPublisher {
249
246
  const gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
250
247
  const txTimeoutAts = gasConfigs.map(g => g?.txTimeoutAt).filter((g): g is Date => g !== undefined);
251
248
  const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map(g => g.getTime()))) : undefined; // earliest
252
- const gasConfig: RequestWithExpiry['gasConfig'] = { gasLimit, txTimeoutAt };
249
+ const txConfig: RequestWithExpiry['gasConfig'] = { gasLimit, txTimeoutAt };
253
250
 
254
251
  // Sort the requests so that proposals always go first
255
252
  // This ensures the committee gets precomputed correctly
256
253
  validRequests.sort((a, b) => compareActions(a.action, b.action));
257
254
 
258
255
  try {
259
- this.log.debug('Forwarding transactions', { validRequests: validRequests.map(request => request.action) });
256
+ this.log.debug('Forwarding transactions', {
257
+ validRequests: validRequests.map(request => request.action),
258
+ txConfig,
259
+ });
260
260
  const result = await Multicall3.forward(
261
261
  validRequests.map(request => request.request),
262
262
  this.l1TxUtils,
263
- gasConfig,
263
+ txConfig,
264
264
  blobConfig,
265
265
  this.rollupContract.address,
266
266
  this.log,
@@ -285,7 +285,7 @@ export class SequencerPublisher {
285
285
 
286
286
  private callbackBundledTransactions(
287
287
  requests: RequestWithExpiry[],
288
- result?: { receipt: TransactionReceipt; gasPrice: GasPrice } | FormattedViemError,
288
+ result?: { receipt: TransactionReceipt } | FormattedViemError,
289
289
  ) {
290
290
  const actionsListStr = requests.map(r => r.action).join(', ');
291
291
  if (result instanceof FormattedViemError) {
@@ -346,8 +346,9 @@ export class SequencerPublisher {
346
346
 
347
347
  const args = [
348
348
  header.toViem(),
349
- RollupContract.packAttestations([]),
349
+ CommitteeAttestationsAndSigners.empty().getPackedAttestations(),
350
350
  [], // no signers
351
+ Signature.empty().toViemSignature(),
351
352
  `0x${'0'.repeat(64)}`, // 32 empty bytes
352
353
  header.contentCommitment.blobsHash.toString(),
353
354
  flags,
@@ -446,17 +447,19 @@ export class SequencerPublisher {
446
447
  const logData = { ...block, reason };
447
448
  this.log.debug(`Simulating invalidate block ${block.blockNumber}`, logData);
448
449
 
450
+ const attestationsAndSigners = new CommitteeAttestationsAndSigners(attestations).getPackedAttestations();
451
+
449
452
  if (reason === 'invalid-attestation') {
450
453
  return this.rollupContract.buildInvalidateBadAttestationRequest(
451
454
  block.blockNumber,
452
- attestations.map(a => a.toViem()),
455
+ attestationsAndSigners,
453
456
  committee,
454
457
  validationResult.invalidIndex,
455
458
  );
456
459
  } else if (reason === 'insufficient-attestations') {
457
460
  return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
458
461
  block.blockNumber,
459
- attestations.map(a => a.toViem()),
462
+ attestationsAndSigners,
460
463
  committee,
461
464
  );
462
465
  } else {
@@ -476,24 +479,22 @@ export class SequencerPublisher {
476
479
  */
477
480
  public async validateBlockForSubmission(
478
481
  block: L2Block,
479
- attestationData: { digest: Buffer; attestations: CommitteeAttestation[] } = {
480
- digest: Buffer.alloc(32),
481
- attestations: [],
482
- },
482
+ attestationsAndSigners: CommitteeAttestationsAndSigners,
483
+ attestationsAndSignersSignature: Signature,
483
484
  options: { forcePendingBlockNumber?: number },
484
485
  ): Promise<bigint> {
485
486
  const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
486
487
 
487
488
  // If we have no attestations, we still need to provide the empty attestations
488
489
  // so that the committee is recalculated correctly
489
- const ignoreSignatures = attestationData.attestations.length === 0;
490
+ const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
490
491
  if (ignoreSignatures) {
491
492
  const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber.toBigInt());
492
493
  if (!committee) {
493
494
  this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber.toBigInt()}`);
494
495
  throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber.toBigInt()}`);
495
496
  }
496
- attestationData.attestations = committee.map(committeeMember =>
497
+ attestationsAndSigners.attestations = committee.map(committeeMember =>
497
498
  CommitteeAttestation.fromAddress(committeeMember),
498
499
  );
499
500
  }
@@ -501,23 +502,18 @@ export class SequencerPublisher {
501
502
  const blobs = await Blob.getBlobsPerBlock(block.body.toBlobFields());
502
503
  const blobInput = Blob.getPrefixedEthBlobCommitments(blobs);
503
504
 
504
- const formattedAttestations = attestationData.attestations.map(attest => attest.toViem());
505
- const signers = attestationData.attestations
506
- .filter(attest => !attest.signature.isEmpty())
507
- .map(attest => attest.address.toString());
508
-
509
505
  const args = [
510
506
  {
511
507
  header: block.header.toPropose().toViem(),
512
508
  archive: toHex(block.archive.root.toBuffer()),
513
509
  stateReference: block.header.state.toViem(),
514
- txHashes: block.body.txEffects.map(txEffect => txEffect.txHash.toString()),
515
510
  oracleInput: {
516
511
  feeAssetPriceModifier: 0n,
517
512
  },
518
513
  },
519
- RollupContract.packAttestations(formattedAttestations),
520
- signers,
514
+ attestationsAndSigners.getPackedAttestations(),
515
+ attestationsAndSigners.getSigners().map(signer => signer.toString()),
516
+ attestationsAndSignersSignature.toViemSignature(),
521
517
  blobInput,
522
518
  ] as const;
523
519
 
@@ -528,13 +524,14 @@ export class SequencerPublisher {
528
524
  private async enqueueCastSignalHelper(
529
525
  slotNumber: bigint,
530
526
  timestamp: bigint,
531
- signalType: SignalType,
527
+ signalType: GovernanceSignalAction,
532
528
  payload: EthAddress,
533
529
  base: IEmpireBase,
534
530
  signerAddress: EthAddress,
535
531
  signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
536
532
  ): Promise<boolean> {
537
- if (this.myLastSignals[signalType] >= slotNumber) {
533
+ if (this.lastActions[signalType] && this.lastActions[signalType] === slotNumber) {
534
+ this.log.debug(`Skipping duplicate vote cast signal ${signalType} for slot ${slotNumber}`);
538
535
  return false;
539
536
  }
540
537
  if (payload.equals(EthAddress.ZERO)) {
@@ -551,10 +548,9 @@ export class SequencerPublisher {
551
548
  return false;
552
549
  }
553
550
 
554
- const cachedLastVote = this.myLastSignals[signalType];
555
- this.myLastSignals[signalType] = slotNumber;
556
-
557
- const action = signalType === SignalType.GOVERNANCE ? 'governance-signal' : 'empire-slashing-signal';
551
+ const cachedLastVote = this.lastActions[signalType];
552
+ this.lastActions[signalType] = slotNumber;
553
+ const action = signalType;
558
554
 
559
555
  const request = await base.createSignalRequestWithSignature(
560
556
  payload.toString(),
@@ -597,7 +593,7 @@ export class SequencerPublisher {
597
593
  `Signaling in [${action}] for ${payload} at slot ${slotNumber} in round ${round} failed`,
598
594
  logData,
599
595
  );
600
- this.myLastSignals[signalType] = cachedLastVote;
596
+ this.lastActions[signalType] = cachedLastVote;
601
597
  return false;
602
598
  } else {
603
599
  this.log.info(
@@ -627,7 +623,7 @@ export class SequencerPublisher {
627
623
  return this.enqueueCastSignalHelper(
628
624
  slotNumber,
629
625
  timestamp,
630
- SignalType.GOVERNANCE,
626
+ 'governance-signal',
631
627
  governancePayload,
632
628
  this.govProposerContract,
633
629
  signerAddress,
@@ -661,7 +657,7 @@ export class SequencerPublisher {
661
657
  await this.enqueueCastSignalHelper(
662
658
  slotNumber,
663
659
  timestamp,
664
- SignalType.SLASHING,
660
+ 'empire-slashing-signal',
665
661
  action.payload,
666
662
  this.slashingProposerContract,
667
663
  signerAddress,
@@ -766,15 +762,12 @@ export class SequencerPublisher {
766
762
  */
767
763
  public async enqueueProposeL2Block(
768
764
  block: L2Block,
769
- attestations?: CommitteeAttestation[],
770
- txHashes?: TxHash[],
765
+ attestationsAndSigners: CommitteeAttestationsAndSigners,
766
+ attestationsAndSignersSignature: Signature,
771
767
  opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: number } = {},
772
768
  ): Promise<boolean> {
773
769
  const proposedBlockHeader = block.header.toPropose();
774
770
 
775
- const consensusPayload = ConsensusPayload.fromBlock(block);
776
- const digest = getHashedSignaturePayload(consensusPayload, SignatureDomainSeparator.blockAttestation);
777
-
778
771
  const blobs = await Blob.getBlobsPerBlock(block.body.toBlobFields());
779
772
  const proposeTxArgs = {
780
773
  header: proposedBlockHeader,
@@ -782,8 +775,8 @@ export class SequencerPublisher {
782
775
  stateReference: block.header.state,
783
776
  body: block.body.toBuffer(),
784
777
  blobs,
785
- attestations,
786
- txHashes: txHashes ?? [],
778
+ attestationsAndSigners,
779
+ attestationsAndSignersSignature,
787
780
  };
788
781
 
789
782
  let ts: bigint;
@@ -793,9 +786,8 @@ export class SequencerPublisher {
793
786
  // This means that we can avoid the simulation issues in later checks.
794
787
  // By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
795
788
  // make time consistency checks break.
796
- const attestationData = { digest: digest.toBuffer(), attestations: attestations ?? [] };
797
789
  // TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
798
- ts = await this.validateBlockForSubmission(block, attestationData, opts);
790
+ ts = await this.validateBlockForSubmission(block, attestationsAndSigners, attestationsAndSignersSignature, opts);
799
791
  } catch (err: any) {
800
792
  this.log.error(`Block validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
801
793
  ...block.getStats(),
@@ -818,7 +810,8 @@ export class SequencerPublisher {
818
810
  // We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
819
811
  const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(request.gasUsed) * 64) / 63)));
820
812
 
821
- const logData = { ...pick(request, 'gasUsed', 'blockNumber'), gasLimit, opts };
813
+ const { gasUsed, blockNumber } = request;
814
+ const logData = { gasUsed, blockNumber, gasLimit, opts };
822
815
  this.log.verbose(`Enqueuing invalidate block request`, logData);
823
816
  this.addRequest({
824
817
  action: `invalidate-by-${request.reason}`,
@@ -842,16 +835,24 @@ export class SequencerPublisher {
842
835
  }
843
836
 
844
837
  private async simulateAndEnqueueRequest(
845
- action: RequestWithExpiry['action'],
838
+ action: Action,
846
839
  request: L1TxRequest,
847
840
  checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
848
841
  slotNumber: bigint,
849
842
  timestamp: bigint,
850
843
  ) {
851
844
  const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
852
- let gasUsed: bigint;
845
+ if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
846
+ this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
847
+ return false;
848
+ }
849
+
850
+ const cachedLastActionSlot = this.lastActions[action];
851
+ this.lastActions[action] = slotNumber;
853
852
 
854
- this.log.debug(`Simulating ${action}`, logData);
853
+ this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
854
+
855
+ let gasUsed: bigint;
855
856
  try {
856
857
  ({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], ErrorsAbi)); // TODO(palla/slash): Check the timestamp logic
857
858
  this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
@@ -875,6 +876,7 @@ export class SequencerPublisher {
875
876
  const success = result && result.receipt && result.receipt.status === 'success' && checkSuccess(result.receipt);
876
877
  if (!success) {
877
878
  this.log.warn(`Action ${action} at ${slotNumber} failed`, { ...result, ...logData });
879
+ this.lastActions[action] = cachedLastActionSlot;
878
880
  } else {
879
881
  this.log.info(`Action ${action} at ${slotNumber} succeeded`, { ...result, ...logData });
880
882
  }
@@ -932,12 +934,7 @@ export class SequencerPublisher {
932
934
  throw new Error('Failed to validate blobs');
933
935
  });
934
936
 
935
- const attestations = encodedData.attestations ? encodedData.attestations.map(attest => attest.toViem()) : [];
936
- const txHashes = encodedData.txHashes ? encodedData.txHashes.map(txHash => txHash.toString()) : [];
937
-
938
- const signers = encodedData.attestations
939
- ?.filter(attest => !attest.signature.isEmpty())
940
- .map(attest => attest.address.toString());
937
+ const signers = encodedData.attestationsAndSigners.getSigners().map(signer => signer.toString());
941
938
 
942
939
  const args = [
943
940
  {
@@ -948,10 +945,10 @@ export class SequencerPublisher {
948
945
  // We are currently not modifying these. See #9963
949
946
  feeAssetPriceModifier: 0n,
950
947
  },
951
- txHashes,
952
948
  },
953
- RollupContract.packAttestations(attestations),
954
- signers ?? [],
949
+ encodedData.attestationsAndSigners.getPackedAttestations(),
950
+ signers,
951
+ encodedData.attestationsAndSignersSignature.toViemSignature(),
955
952
  blobInput,
956
953
  ] as const;
957
954
 
@@ -972,13 +969,13 @@ export class SequencerPublisher {
972
969
  readonly header: ViemHeader;
973
970
  readonly archive: `0x${string}`;
974
971
  readonly stateReference: ViemStateReference;
975
- readonly txHashes: `0x${string}`[];
976
972
  readonly oracleInput: {
977
973
  readonly feeAssetPriceModifier: 0n;
978
974
  };
979
975
  },
980
976
  ViemCommitteeAttestations,
981
- `0x${string}`[],
977
+ `0x${string}`[], // Signers
978
+ ViemSignature,
982
979
  `0x${string}`,
983
980
  ],
984
981
  timestamp: bigint,
@@ -1084,13 +1081,16 @@ export class SequencerPublisher {
1084
1081
  if (success) {
1085
1082
  const endBlock = receipt.blockNumber;
1086
1083
  const inclusionBlocks = Number(endBlock - startBlock);
1084
+ const { calldataGas, calldataSize, sender } = stats!;
1087
1085
  const publishStats: L1PublishBlockStats = {
1088
1086
  gasPrice: receipt.effectiveGasPrice,
1089
1087
  gasUsed: receipt.gasUsed,
1090
1088
  blobGasUsed: receipt.blobGasUsed ?? 0n,
1091
1089
  blobDataGas: receipt.blobGasPrice ?? 0n,
1092
1090
  transactionHash: receipt.transactionHash,
1093
- ...pick(stats!, 'calldataGas', 'calldataSize', 'sender'),
1091
+ calldataGas,
1092
+ calldataSize,
1093
+ sender,
1094
1094
  ...block.getStats(),
1095
1095
  eventName: 'rollup-published-to-l1',
1096
1096
  blobCount: encodedData.blobs.length,
@@ -0,0 +1,21 @@
1
+ import type { SequencerState } from './utils.js';
2
+
3
+ export class SequencerTooSlowError extends Error {
4
+ constructor(
5
+ public readonly proposedState: SequencerState,
6
+ public readonly maxAllowedTime: number,
7
+ public readonly currentTime: number,
8
+ ) {
9
+ super(
10
+ `Too far into slot for ${proposedState} (time into slot ${currentTime}s greater than ${maxAllowedTime}s allowance)`,
11
+ );
12
+ this.name = 'SequencerTooSlowError';
13
+ }
14
+ }
15
+
16
+ export class SequencerInterruptedError extends Error {
17
+ constructor() {
18
+ super(`Sequencer was interrupted`);
19
+ this.name = 'SequencerInterruptedError';
20
+ }
21
+ }
@@ -2,21 +2,19 @@ import type { EthAddress } from '@aztec/aztec.js';
2
2
  import type { RollupContract } from '@aztec/ethereum';
3
3
  import {
4
4
  Attributes,
5
- type BatchObservableResult,
6
5
  type Gauge,
7
6
  type Histogram,
8
7
  type Meter,
9
8
  Metrics,
10
- type ObservableGauge,
11
9
  type TelemetryClient,
12
10
  type Tracer,
13
11
  type UpDownCounter,
14
12
  ValueType,
15
13
  } from '@aztec/telemetry-client';
16
14
 
17
- import { formatUnits } from 'viem';
15
+ import { type Hex, formatUnits } from 'viem';
18
16
 
19
- import { type SequencerState, type SequencerStateCallback, sequencerStateToNumber } from './utils.js';
17
+ import type { SequencerState } from './utils.js';
20
18
 
21
19
  export class SequencerMetrics {
22
20
  public readonly tracer: Tracer;
@@ -26,9 +24,6 @@ export class SequencerMetrics {
26
24
  private blockBuildDuration: Histogram;
27
25
  private blockBuildManaPerSecond: Gauge;
28
26
  private stateTransitionBufferDuration: Histogram;
29
- private currentBlockNumber: Gauge;
30
- private currentBlockSize: Gauge;
31
- private blockBuilderInsertions: Histogram;
32
27
 
33
28
  // these are gauges because for individual sequencers building a block is not something that happens often enough to warrant a histogram
34
29
  private timeToCollectAttestations: Gauge;
@@ -36,18 +31,15 @@ export class SequencerMetrics {
36
31
  private requiredAttestions: Gauge;
37
32
  private collectedAttestions: Gauge;
38
33
 
39
- private rewards: ObservableGauge;
34
+ private rewards: Gauge;
40
35
 
41
36
  private slots: UpDownCounter;
42
37
  private filledSlots: UpDownCounter;
43
- private missedSlots: UpDownCounter;
44
38
 
45
39
  private lastSeenSlot?: bigint;
46
40
 
47
41
  constructor(
48
42
  client: TelemetryClient,
49
- getState: SequencerStateCallback,
50
- private coinbase: EthAddress,
51
43
  private rollup: RollupContract,
52
44
  name = 'Sequencer',
53
45
  ) {
@@ -78,35 +70,7 @@ export class SequencerMetrics {
78
70
  },
79
71
  );
80
72
 
81
- const currentState = this.meter.createObservableGauge(Metrics.SEQUENCER_CURRENT_STATE, {
82
- description: 'Current state of the sequencer',
83
- });
84
-
85
- currentState.addCallback(observer => {
86
- observer.observe(sequencerStateToNumber(getState()));
87
- });
88
-
89
- this.currentBlockNumber = this.meter.createGauge(Metrics.SEQUENCER_CURRENT_BLOCK_NUMBER, {
90
- description: 'Current block number',
91
- valueType: ValueType.INT,
92
- });
93
-
94
- this.currentBlockSize = this.meter.createGauge(Metrics.SEQUENCER_CURRENT_BLOCK_SIZE, {
95
- description: 'Current block size',
96
- valueType: ValueType.INT,
97
- });
98
-
99
- this.blockBuilderInsertions = this.meter.createHistogram(Metrics.SEQUENCER_BLOCK_BUILD_INSERTION_TIME, {
100
- description: 'Timer for tree insertions performed by the block builder',
101
- unit: 'us',
102
- valueType: ValueType.INT,
103
- });
104
-
105
73
  // Init gauges and counters
106
- this.setCurrentBlock(0, 0);
107
- this.blockCounter.add(0, {
108
- [Attributes.STATUS]: 'cancelled',
109
- });
110
74
  this.blockCounter.add(0, {
111
75
  [Attributes.STATUS]: 'failed',
112
76
  });
@@ -114,7 +78,7 @@ export class SequencerMetrics {
114
78
  [Attributes.STATUS]: 'built',
115
79
  });
116
80
 
117
- this.rewards = this.meter.createObservableGauge(Metrics.SEQUENCER_CURRENT_BLOCK_REWARDS, {
81
+ this.rewards = this.meter.createGauge(Metrics.SEQUENCER_CURRENT_BLOCK_REWARDS, {
118
82
  valueType: ValueType.DOUBLE,
119
83
  description: 'The rewards earned',
120
84
  });
@@ -124,16 +88,15 @@ export class SequencerMetrics {
124
88
  description: 'The number of slots this sequencer was selected for',
125
89
  });
126
90
 
91
+ /**
92
+ * NOTE: we do not track missed slots as a separate metric. That would be difficult to determine
93
+ * Instead, use a computed metric, `slots - filledSlots` to get the number of slots a sequencer has missed.
94
+ */
127
95
  this.filledSlots = this.meter.createUpDownCounter(Metrics.SEQUENCER_FILLED_SLOT_COUNT, {
128
96
  valueType: ValueType.INT,
129
97
  description: 'The number of slots this sequencer has filled',
130
98
  });
131
99
 
132
- this.missedSlots = this.meter.createUpDownCounter(Metrics.SEQUENCER_MISSED_SLOT_COUNT, {
133
- valueType: ValueType.INT,
134
- description: 'The number of slots this sequencer has missed to fill',
135
- });
136
-
137
100
  this.timeToCollectAttestations = this.meter.createGauge(Metrics.SEQUENCER_COLLECT_ATTESTATIONS_DURATION, {
138
101
  description: 'The time spent collecting attestations from committee members',
139
102
  unit: 'ms',
@@ -160,28 +123,6 @@ export class SequencerMetrics {
160
123
  });
161
124
  }
162
125
 
163
- public setCoinbase(coinbase: EthAddress) {
164
- this.coinbase = coinbase;
165
- }
166
-
167
- public start() {
168
- this.meter.addBatchObservableCallback(this.observe, [this.rewards]);
169
- }
170
-
171
- public stop() {
172
- this.meter.removeBatchObservableCallback(this.observe, [this.rewards]);
173
- }
174
-
175
- private observe = async (observer: BatchObservableResult): Promise<void> => {
176
- let rewards = 0n;
177
- rewards = await this.rollup.getSequencerRewards(this.coinbase);
178
-
179
- const fmt = parseFloat(formatUnits(rewards, 18));
180
- observer.observe(this.rewards, fmt, {
181
- [Attributes.COINBASE]: this.coinbase.toString(),
182
- });
183
- };
184
-
185
126
  public recordRequiredAttestations(requiredAttestationsCount: number, allowanceMs: number) {
186
127
  this.requiredAttestions.record(requiredAttestationsCount);
187
128
  this.allowanceToCollectAttestations.record(Math.ceil(allowanceMs));
@@ -196,17 +137,6 @@ export class SequencerMetrics {
196
137
  this.timeToCollectAttestations.record(Math.ceil(durationMs));
197
138
  }
198
139
 
199
- recordBlockBuilderTreeInsertions(timeUs: number) {
200
- this.blockBuilderInsertions.record(Math.ceil(timeUs));
201
- }
202
-
203
- recordCancelledBlock() {
204
- this.blockCounter.add(1, {
205
- [Attributes.STATUS]: 'cancelled',
206
- });
207
- this.setCurrentBlock(0, 0);
208
- }
209
-
210
140
  recordBuiltBlock(buildDurationMs: number, totalMana: number) {
211
141
  this.blockCounter.add(1, {
212
142
  [Attributes.STATUS]: 'built',
@@ -219,11 +149,6 @@ export class SequencerMetrics {
219
149
  this.blockCounter.add(1, {
220
150
  [Attributes.STATUS]: 'failed',
221
151
  });
222
- this.setCurrentBlock(0, 0);
223
- }
224
-
225
- recordNewBlock(blockNumber: number, txCount: number) {
226
- this.setCurrentBlock(blockNumber, txCount);
227
152
  }
228
153
 
229
154
  recordStateTransitionBufferMs(durationMs: number, state: SequencerState) {
@@ -232,36 +157,35 @@ export class SequencerMetrics {
232
157
  });
233
158
  }
234
159
 
235
- observeSlotChange(slot: bigint | undefined, proposer: string) {
160
+ incOpenSlot(slot: bigint, proposer: string) {
236
161
  // sequencer went through the loop a second time. Noop
237
162
  if (slot === this.lastSeenSlot) {
238
163
  return;
239
164
  }
240
165
 
241
- if (typeof this.lastSeenSlot === 'bigint') {
242
- this.missedSlots.add(1, {
243
- [Attributes.BLOCK_PROPOSER]: proposer,
244
- });
245
- }
246
-
247
- if (typeof slot === 'bigint') {
248
- this.slots.add(1, {
249
- [Attributes.BLOCK_PROPOSER]: proposer,
250
- });
251
- }
166
+ this.slots.add(1, {
167
+ [Attributes.BLOCK_PROPOSER]: proposer,
168
+ });
252
169
 
253
170
  this.lastSeenSlot = slot;
254
171
  }
255
172
 
256
- incFilledSlot(proposer: string) {
173
+ async incFilledSlot(proposer: string, coinbase: Hex | EthAddress | undefined): Promise<void> {
257
174
  this.filledSlots.add(1, {
258
175
  [Attributes.BLOCK_PROPOSER]: proposer,
259
176
  });
260
177
  this.lastSeenSlot = undefined;
261
- }
262
178
 
263
- private setCurrentBlock(blockNumber: number, txCount: number) {
264
- this.currentBlockNumber.record(blockNumber);
265
- this.currentBlockSize.record(txCount);
179
+ if (coinbase) {
180
+ try {
181
+ const rewards = await this.rollup.getSequencerRewards(coinbase);
182
+ const fmt = parseFloat(formatUnits(rewards, 18));
183
+ this.rewards.record(fmt, {
184
+ [Attributes.COINBASE]: coinbase.toString(),
185
+ });
186
+ } catch {
187
+ // no-op
188
+ }
189
+ }
266
190
  }
267
191
  }