@aztec/sequencer-client 2.0.3-rc.16 → 2.0.3-rc.18

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.
@@ -28,7 +28,7 @@ import { sumBigint } from '@aztec/foundation/bigint';
28
28
  import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
29
29
  import { EthAddress } from '@aztec/foundation/eth-address';
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';
@@ -62,11 +62,6 @@ type L1ProcessArgs = {
62
62
  attestations?: CommitteeAttestation[];
63
63
  };
64
64
 
65
- export enum SignalType {
66
- GOVERNANCE,
67
- SLASHING,
68
- }
69
-
70
65
  export const Actions = [
71
66
  'invalidate-by-invalid-attestation',
72
67
  'invalidate-by-insufficient-attestations',
@@ -78,8 +73,11 @@ export const Actions = [
78
73
  'vote-offenses',
79
74
  'execute-slash',
80
75
  ] as const;
76
+
81
77
  export type Action = (typeof Actions)[number];
82
78
 
79
+ type GovernanceSignalAction = Extract<Action, 'governance-signal' | 'empire-slashing-signal'>;
80
+
83
81
  // Sorting for actions such that invalidations go before proposals, and proposals go before votes
84
82
  export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
85
83
 
@@ -104,6 +102,7 @@ interface RequestWithExpiry {
104
102
  }
105
103
 
106
104
  export class SequencerPublisher {
105
+ private enabled: boolean;
107
106
  private interrupted = false;
108
107
  private metrics: SequencerPublisherMetrics;
109
108
  public epochCache: EpochCache;
@@ -111,12 +110,9 @@ export class SequencerPublisher {
111
110
  protected governanceLog = createLogger('sequencer:publisher:governance');
112
111
  protected slashingLog = createLogger('sequencer:publisher:slashing');
113
112
 
114
- private myLastSignals: Record<SignalType, bigint> = {
115
- [SignalType.GOVERNANCE]: 0n,
116
- [SignalType.SLASHING]: 0n,
117
- };
113
+ protected lastActions: Partial<Record<Action, bigint>> = {};
118
114
 
119
- protected log = createLogger('sequencer:publisher');
115
+ protected log: Logger;
120
116
  protected ethereumSlotDuration: bigint;
121
117
 
122
118
  private blobSinkClient: BlobSinkClientInterface;
@@ -152,10 +148,15 @@ export class SequencerPublisher {
152
148
  epochCache: EpochCache;
153
149
  dateProvider: DateProvider;
154
150
  metrics: SequencerPublisherMetrics;
151
+ lastActions: Partial<Record<Action, bigint>>;
152
+ log?: Logger;
155
153
  },
156
154
  ) {
155
+ this.enabled = config.publisherEnabled ?? true;
156
+ this.log = deps.log ?? createLogger('sequencer:publisher');
157
157
  this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
158
158
  this.epochCache = deps.epochCache;
159
+ this.lastActions = deps.lastActions;
159
160
 
160
161
  this.blobSinkClient =
161
162
  deps.blobSinkClient ?? createBlobSinkClient(config, { logger: createLogger('sequencer:blob-sink:client') });
@@ -201,6 +202,14 @@ export class SequencerPublisher {
201
202
  * - undefined if no valid requests are found OR the tx failed to send.
202
203
  */
203
204
  public async sendRequests() {
205
+ if (!this.enabled) {
206
+ this.log.warn(`Sending L1 txs is disabled`, {
207
+ requestsDiscarded: this.requests.map(r => r.action),
208
+ });
209
+ this.requests = [];
210
+ return undefined;
211
+ }
212
+
204
213
  const requestsToProcess = [...this.requests];
205
214
  this.requests = [];
206
215
  if (this.interrupted) {
@@ -528,13 +537,14 @@ export class SequencerPublisher {
528
537
  private async enqueueCastSignalHelper(
529
538
  slotNumber: bigint,
530
539
  timestamp: bigint,
531
- signalType: SignalType,
540
+ signalType: GovernanceSignalAction,
532
541
  payload: EthAddress,
533
542
  base: IEmpireBase,
534
543
  signerAddress: EthAddress,
535
544
  signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
536
545
  ): Promise<boolean> {
537
- if (this.myLastSignals[signalType] >= slotNumber) {
546
+ if (this.lastActions[signalType] && this.lastActions[signalType] === slotNumber) {
547
+ this.log.debug(`Skipping duplicate vote cast signal ${signalType} for slot ${slotNumber}`);
538
548
  return false;
539
549
  }
540
550
  if (payload.equals(EthAddress.ZERO)) {
@@ -551,10 +561,9 @@ export class SequencerPublisher {
551
561
  return false;
552
562
  }
553
563
 
554
- const cachedLastVote = this.myLastSignals[signalType];
555
- this.myLastSignals[signalType] = slotNumber;
556
-
557
- const action = signalType === SignalType.GOVERNANCE ? 'governance-signal' : 'empire-slashing-signal';
564
+ const cachedLastVote = this.lastActions[signalType];
565
+ this.lastActions[signalType] = slotNumber;
566
+ const action = signalType;
558
567
 
559
568
  const request = await base.createSignalRequestWithSignature(
560
569
  payload.toString(),
@@ -597,7 +606,7 @@ export class SequencerPublisher {
597
606
  `Signaling in [${action}] for ${payload} at slot ${slotNumber} in round ${round} failed`,
598
607
  logData,
599
608
  );
600
- this.myLastSignals[signalType] = cachedLastVote;
609
+ this.lastActions[signalType] = cachedLastVote;
601
610
  return false;
602
611
  } else {
603
612
  this.log.info(
@@ -627,7 +636,7 @@ export class SequencerPublisher {
627
636
  return this.enqueueCastSignalHelper(
628
637
  slotNumber,
629
638
  timestamp,
630
- SignalType.GOVERNANCE,
639
+ 'governance-signal',
631
640
  governancePayload,
632
641
  this.govProposerContract,
633
642
  signerAddress,
@@ -661,7 +670,7 @@ export class SequencerPublisher {
661
670
  await this.enqueueCastSignalHelper(
662
671
  slotNumber,
663
672
  timestamp,
664
- SignalType.SLASHING,
673
+ 'empire-slashing-signal',
665
674
  action.payload,
666
675
  this.slashingProposerContract,
667
676
  signerAddress,
@@ -842,16 +851,24 @@ export class SequencerPublisher {
842
851
  }
843
852
 
844
853
  private async simulateAndEnqueueRequest(
845
- action: RequestWithExpiry['action'],
854
+ action: Action,
846
855
  request: L1TxRequest,
847
856
  checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
848
857
  slotNumber: bigint,
849
858
  timestamp: bigint,
850
859
  ) {
851
860
  const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
852
- let gasUsed: bigint;
861
+ if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
862
+ this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
863
+ return false;
864
+ }
853
865
 
854
- this.log.debug(`Simulating ${action}`, logData);
866
+ const cachedLastActionSlot = this.lastActions[action];
867
+ this.lastActions[action] = slotNumber;
868
+
869
+ this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
870
+
871
+ let gasUsed: bigint;
855
872
  try {
856
873
  ({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], ErrorsAbi)); // TODO(palla/slash): Check the timestamp logic
857
874
  this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
@@ -875,6 +892,7 @@ export class SequencerPublisher {
875
892
  const success = result && result.receipt && result.receipt.status === 'success' && checkSuccess(result.receipt);
876
893
  if (!success) {
877
894
  this.log.warn(`Action ${action} at ${slotNumber} failed`, { ...result, ...logData });
895
+ this.lastActions[action] = cachedLastActionSlot;
878
896
  } else {
879
897
  this.log.info(`Action ${action} at ${slotNumber} succeeded`, { ...result, ...logData });
880
898
  }
@@ -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
  }
@@ -1,5 +1,5 @@
1
1
  import type { L2Block } from '@aztec/aztec.js';
2
- import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
2
+ import { BLOBS_PER_BLOCK, FIELDS_PER_BLOB, INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
3
3
  import type { EpochCache } from '@aztec/epoch-cache';
4
4
  import { FormattedViemError, NoCommitteeError, type RollupContract } from '@aztec/ethereum';
5
5
  import { omit, pick } from '@aztec/foundation/collection';
@@ -127,15 +127,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
127
127
  ) {
128
128
  super();
129
129
 
130
- // Set an initial coinbase for metrics purposes, but this will potentially change with each block.
131
- const validatorAddresses = this.validatorClient?.getValidatorAddresses() ?? [];
132
- const coinbase =
133
- validatorAddresses.length === 0
134
- ? EthAddress.ZERO
135
- : (this.validatorClient?.getCoinbaseForAttestor(validatorAddresses[0]) ?? EthAddress.ZERO);
136
-
137
- this.metrics = new SequencerMetrics(telemetry, () => this.state, coinbase, this.rollupContract, 'Sequencer');
138
-
130
+ this.metrics = new SequencerMetrics(telemetry, this.rollupContract, 'Sequencer');
139
131
  // Initialize config
140
132
  this.updateConfig(this.config);
141
133
  }
@@ -220,7 +212,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
220
212
  * Starts the sequencer and moves to IDLE state.
221
213
  */
222
214
  public start() {
223
- this.metrics.start();
224
215
  this.runningPromise = new RunningPromise(this.work.bind(this), this.log, this.pollingIntervalMs);
225
216
  this.setState(SequencerState.IDLE, undefined, { force: true });
226
217
  this.runningPromise.start();
@@ -232,7 +223,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
232
223
  */
233
224
  public async stop(): Promise<void> {
234
225
  this.log.info(`Stopping sequencer`);
235
- this.metrics.stop();
236
226
  this.publisher?.interrupt();
237
227
  await this.validatorClient?.stop();
238
228
  await this.runningPromise?.stop();
@@ -361,8 +351,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
361
351
  const coinbase = this.validatorClient!.getCoinbaseForAttestor(attestorAddress);
362
352
  const feeRecipient = this.validatorClient!.getFeeRecipientForAttestor(attestorAddress);
363
353
 
364
- this.metrics.setCoinbase(coinbase);
365
-
366
354
  // Prepare invalidation request if the pending chain is invalid (returns undefined if no need)
367
355
  const invalidateBlock = await publisher.simulateInvalidateBlock(syncedTo.pendingChainValidationStatus);
368
356
  const canProposeCheck = await publisher.canProposeAtNextEthBlock(
@@ -435,6 +423,8 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
435
423
  }
436
424
 
437
425
  this.setState(SequencerState.INITIALIZING_PROPOSAL, slot);
426
+
427
+ this.metrics.incOpenSlot(slot, proposerAddressInNextSlot.toString());
438
428
  this.log.verbose(`Preparing proposal for block ${newBlockNumber} at slot ${slot}`, {
439
429
  proposer: proposerInNextSlot?.toString(),
440
430
  coinbase,
@@ -494,7 +484,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
494
484
  if (proposedBlock) {
495
485
  this.lastBlockPublished = block;
496
486
  this.emit('block-published', { blockNumber: newBlockNumber, slot: Number(slot) });
497
- this.metrics.incFilledSlot(publisher.getSenderAddress().toString());
487
+ await this.metrics.incFilledSlot(publisher.getSenderAddress().toString(), coinbase);
498
488
  } else if (block) {
499
489
  this.emit('block-publish-failed', l1Response ?? {});
500
490
  }
@@ -584,6 +574,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
584
574
  maxTransactions: this.maxTxsPerBlock,
585
575
  maxBlockSize: this.maxBlockSizeInBytes,
586
576
  maxBlockGas: this.maxBlockGas,
577
+ maxBlobFields: BLOBS_PER_BLOCK * FIELDS_PER_BLOB,
587
578
  deadline,
588
579
  };
589
580
  }
@@ -616,7 +607,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
616
607
  const slot = proposalHeader.slotNumber.toBigInt();
617
608
  const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(blockNumber);
618
609
 
619
- // this.metrics.recordNewBlock(blockNumber, validTxs.length);
620
610
  const workTimer = new Timer();
621
611
  this.setState(SequencerState.CREATING_BLOCK, slot);
622
612