@aztec/sequencer-client 0.0.1-commit.2b2662070 → 0.0.1-commit.2c0ee1788

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.
@@ -24,4 +24,9 @@ export type SequencerEvents = {
24
24
  }) => void;
25
25
  ['checkpoint-published']: (args: { checkpoint: CheckpointNumber; slot: SlotNumber }) => void;
26
26
  ['checkpoint-error']: (args: { error: Error }) => void;
27
+ ['pipelined-checkpoint-discarded']: (args: {
28
+ slot: SlotNumber;
29
+ checkpointNumber: CheckpointNumber;
30
+ reason: string;
31
+ }) => void;
27
32
  };
@@ -26,6 +26,7 @@ export class SequencerMetrics {
26
26
  private blockBuildDuration: Histogram;
27
27
  private blockBuildManaPerSecond: Gauge;
28
28
  private stateTransitionBufferDuration: Histogram;
29
+ private stateDuration: Histogram;
29
30
 
30
31
  // these are gauges because for individual sequencers building a block is not something that happens often enough to warrant a histogram
31
32
  private timeToCollectAttestations: Gauge;
@@ -46,6 +47,7 @@ export class SequencerMetrics {
46
47
  private slashingAttempts: UpDownCounter;
47
48
  private pipelineDepth: Gauge;
48
49
  private pipelineDiscards: UpDownCounter;
50
+ private pipelineParentCheckpointMismatches: UpDownCounter;
49
51
 
50
52
  // Fisherman fee analysis metrics
51
53
  private fishermanWouldBeIncluded: UpDownCounter;
@@ -89,6 +91,8 @@ export class SequencerMetrics {
89
91
 
90
92
  this.stateTransitionBufferDuration = this.meter.createHistogram(Metrics.SEQUENCER_STATE_TRANSITION_BUFFER_DURATION);
91
93
 
94
+ this.stateDuration = this.meter.createHistogram(Metrics.SEQUENCER_STATE_DURATION);
95
+
92
96
  this.rewards = this.meter.createGauge(Metrics.SEQUENCER_CURRENT_SLOT_REWARDS);
93
97
 
94
98
  this.slots = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_SLOT_COUNT);
@@ -141,6 +145,19 @@ export class SequencerMetrics {
141
145
 
142
146
  this.pipelineDepth = this.meter.createGauge(Metrics.SEQUENCER_PIPELINE_DEPTH);
143
147
  this.pipelineDiscards = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_PIPELINE_DISCARDS_COUNT);
148
+ this.pipelineParentCheckpointMismatches = createUpDownCounterWithDefault(
149
+ this.meter,
150
+ Metrics.SEQUENCER_PIPELINE_PARENT_CHECKPOINT_MISMATCH_COUNT,
151
+ {
152
+ [Attributes.ERROR_TYPE]: [
153
+ 'archiver-sync-timeout',
154
+ 'parent-not-on-l1',
155
+ 'parent-hash-mismatch',
156
+ 'parent-invalid-attestations',
157
+ 'unexpected-parent-appeared',
158
+ ],
159
+ },
160
+ );
144
161
  this.pipelineDepth.record(0);
145
162
 
146
163
  // Fisherman fee analysis metrics
@@ -238,6 +255,12 @@ export class SequencerMetrics {
238
255
  });
239
256
  }
240
257
 
258
+ recordStateDuration(durationMs: number, state: SequencerState) {
259
+ this.stateDuration.record(Math.ceil(durationMs), {
260
+ [Attributes.SEQUENCER_STATE]: state,
261
+ });
262
+ }
263
+
241
264
  recordPipelineDepth(depth: number) {
242
265
  this.pipelineDepth.record(depth);
243
266
  }
@@ -246,6 +269,12 @@ export class SequencerMetrics {
246
269
  this.pipelineDiscards.add(count);
247
270
  }
248
271
 
272
+ recordPipelineParentCheckpointMismatch(reason: string) {
273
+ this.pipelineParentCheckpointMismatches.add(1, {
274
+ [Attributes.ERROR_TYPE]: reason,
275
+ });
276
+ }
277
+
249
278
  incOpenSlot(slot: SlotNumber, proposer: string) {
250
279
  // sequencer went through the loop a second time. Noop
251
280
  if (slot === this.lastSeenSlot) {
@@ -6,7 +6,7 @@ import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/f
6
6
  import { merge, omit, pick } from '@aztec/foundation/collection';
7
7
  import { Fr } from '@aztec/foundation/curves/bn254';
8
8
  import { EthAddress } from '@aztec/foundation/eth-address';
9
- import { createLogger } from '@aztec/foundation/log';
9
+ import { type Logger, createLogger } from '@aztec/foundation/log';
10
10
  import { RunningPromise } from '@aztec/foundation/running-promise';
11
11
  import type { DateProvider } from '@aztec/foundation/timer';
12
12
  import type { TypedEventEmitter } from '@aztec/foundation/types';
@@ -14,6 +14,7 @@ import type { P2P } from '@aztec/p2p';
14
14
  import type { SlasherClientInterface } from '@aztec/slasher';
15
15
  import type { BlockData, L2BlockSink, L2BlockSource, ValidateCheckpointResult } from '@aztec/stdlib/block';
16
16
  import type { Checkpoint, ProposedCheckpointData } from '@aztec/stdlib/checkpoint';
17
+ import type { ChainConfig } from '@aztec/stdlib/config';
17
18
  import { getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
18
19
  import {
19
20
  type ResolvedSequencerConfig,
@@ -22,6 +23,7 @@ import {
22
23
  type WorldStateSynchronizer,
23
24
  } from '@aztec/stdlib/interfaces/server';
24
25
  import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
26
+ import type { CoordinationSignatureContext } from '@aztec/stdlib/p2p';
25
27
  import { pickFromSchema } from '@aztec/stdlib/schemas';
26
28
  import { MerkleTreeId } from '@aztec/stdlib/trees';
27
29
  import { Attributes, type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
@@ -56,8 +58,11 @@ export { SequencerState };
56
58
  export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<SequencerEvents>) {
57
59
  private runningPromise?: RunningPromise;
58
60
  private state = SequencerState.STOPPED;
61
+ private stateSlotNumber: SlotNumber | undefined;
62
+ private stateEnteredAtMs = performance.now();
59
63
  private metrics: SequencerMetrics;
60
64
  private checkpointProposalJobMetrics: CheckpointProposalJobMetrics;
65
+ private readonly stateLog: Logger;
61
66
 
62
67
  /** The last slot for which we attempted to perform our voting duties with degraded block production */
63
68
  private lastSlotForFallbackVote: SlotNumber | undefined;
@@ -82,6 +87,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
82
87
 
83
88
  /** Config for the sequencer */
84
89
  protected config: ResolvedSequencerConfig = DefaultSequencerConfig;
90
+ private readonly signatureContext: CoordinationSignatureContext;
85
91
 
86
92
  constructor(
87
93
  protected publisherFactory: SequencerPublisherFactory,
@@ -97,17 +103,22 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
97
103
  protected dateProvider: DateProvider,
98
104
  protected epochCache: EpochCache,
99
105
  protected rollupContract: RollupContract,
100
- config: SequencerConfig,
106
+ config: SequencerConfig & Pick<ChainConfig, 'l1ChainId' | 'l1Contracts'>,
101
107
  protected telemetry: TelemetryClient = getTelemetryClient(),
102
108
  protected log = createLogger('sequencer'),
103
109
  ) {
104
110
  super();
111
+ this.stateLog = log.createChild('state');
105
112
 
106
113
  // Add [FISHERMAN] prefix to logger if in fisherman mode
107
114
  if (config.fishermanMode) {
108
115
  this.log = log.createChild('[FISHERMAN]');
109
116
  }
110
117
 
118
+ this.signatureContext = {
119
+ chainId: config.l1ChainId,
120
+ rollupAddress: config.l1Contracts.rollupAddress,
121
+ };
111
122
  this.metrics = new SequencerMetrics(telemetry, this.rollupContract, 'Sequencer');
112
123
  this.checkpointProposalJobMetrics = new CheckpointProposalJobMetrics(telemetry);
113
124
  this.updateConfig(config);
@@ -377,7 +388,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
377
388
  // and the archive at that checkpoint so L1 simulation sees the correct chain tip.
378
389
  const parentCheckpointNumber = CheckpointNumber(checkpointNumber - 1);
379
390
  l1SimulationOverridesBuilder.forPendingCheckpoint(parentCheckpointNumber).withPendingArchive(syncedTo.archive);
380
- this.metrics.recordPipelineDepth(1);
391
+ this.metrics.recordPipelineDepth(syncedTo.checkpointNumber - syncedTo.checkpointedCheckpointNumber);
381
392
 
382
393
  this.log.verbose(
383
394
  `Building on top of proposed checkpoint (pending=${syncedTo.proposedCheckpointData?.checkpointNumber})`,
@@ -489,6 +500,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
489
500
  this.checkpointsBuilder,
490
501
  this.l2BlockSource,
491
502
  this.l1Constants,
503
+ this.signatureContext,
492
504
  this.config,
493
505
  this.timetable,
494
506
  this.slasherClient,
@@ -536,19 +548,35 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
536
548
  this.timetable.assertTimeLeft(proposedState, secondsIntoSlot);
537
549
  }
538
550
 
551
+ const oldState = this.state;
552
+ const oldStateSlotNumber = this.stateSlotNumber;
553
+ const stateChanged = proposedState !== oldState;
554
+ const transitionAtMs = performance.now();
555
+ const stateDurationMs = transitionAtMs - this.stateEnteredAtMs;
556
+
539
557
  const boringStates = [SequencerState.IDLE, SequencerState.SYNCHRONIZING];
540
558
  const logLevel =
541
- boringStates.includes(proposedState) && boringStates.includes(this.state)
542
- ? ('trace' as const)
543
- : ('debug' as const);
544
- this.log[logLevel](`Transitioning from ${this.state} to ${proposedState}`, { slotNumber, secondsIntoSlot });
559
+ boringStates.includes(proposedState) && boringStates.includes(oldState) ? ('trace' as const) : ('debug' as const);
560
+ this.stateLog[logLevel](`Transitioning from ${oldState} to ${proposedState}`, {
561
+ oldState,
562
+ newState: proposedState,
563
+ slotNumber,
564
+ stateSlotNumber: oldStateSlotNumber,
565
+ secondsIntoSlot,
566
+ ...(stateChanged && { stateDurationMs: Math.ceil(stateDurationMs) }),
567
+ });
545
568
 
546
569
  this.emit('state-changed', {
547
- oldState: this.state,
570
+ oldState,
548
571
  newState: proposedState,
549
572
  secondsIntoSlot,
550
573
  slot: slotNumber,
551
574
  });
575
+ if (stateChanged) {
576
+ this.metrics.recordStateDuration(stateDurationMs, oldState);
577
+ this.stateEnteredAtMs = transitionAtMs;
578
+ this.stateSlotNumber = slotNumber;
579
+ }
552
580
  this.state = proposedState;
553
581
  }
554
582
 
@@ -581,7 +609,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
581
609
  this.p2pClient.getStatus().then(p2p => p2p.syncedToL2Block),
582
610
  this.l1ToL2MessageSource.getL2Tips().then(t => ({ proposed: t.proposed, checkpointed: t.checkpointed })),
583
611
  this.l2BlockSource.getPendingChainValidationStatus(),
584
- this.l2BlockSource.getProposedCheckpointOnly(),
612
+ this.l2BlockSource.getLastProposedCheckpoint(),
585
613
  ] as const);
586
614
 
587
615
  const [worldState, l2Tips, p2p, l1ToL2MessageSourceTips, pendingChainValidationStatus, proposedCheckpointData] =
package/src/test/utils.ts CHANGED
@@ -10,7 +10,11 @@ import { PublicDataWrite } from '@aztec/stdlib/avm';
10
10
  import { CommitteeAttestation, L2Block } from '@aztec/stdlib/block';
11
11
  import { BlockProposal, CheckpointAttestation, CheckpointProposal, ConsensusPayload } from '@aztec/stdlib/p2p';
12
12
  import { CheckpointHeader } from '@aztec/stdlib/rollup';
13
- import { makeAppendOnlyTreeSnapshot, mockTxForRollup } from '@aztec/stdlib/testing';
13
+ import {
14
+ TEST_COORDINATION_SIGNATURE_CONTEXT,
15
+ makeAppendOnlyTreeSnapshot,
16
+ mockTxForRollup,
17
+ } from '@aztec/stdlib/testing';
14
18
  import { BlockHeader, GlobalVariables, type Tx, makeProcessedTxFromPrivateOnlyTx } from '@aztec/stdlib/tx';
15
19
 
16
20
  import type { MockProxy } from 'jest-mock-extended';
@@ -109,6 +113,7 @@ export function createBlockProposal(block: L2Block, signature: Signature): Block
109
113
  block.archive.root,
110
114
  txHashes,
111
115
  signature,
116
+ TEST_COORDINATION_SIGNATURE_CONTEXT,
112
117
  );
113
118
  }
114
119
 
@@ -123,12 +128,19 @@ export function createCheckpointProposal(
123
128
  ): CheckpointProposal {
124
129
  const txHashes = block.body.txEffects.map(tx => tx.txHash);
125
130
  const checkpointHeader = createCheckpointHeaderFromBlock(block);
126
- return new CheckpointProposal(checkpointHeader, block.archive.root, feeAssetPriceModifier, checkpointSignature, {
127
- blockHeader: block.header,
128
- indexWithinCheckpoint: block.indexWithinCheckpoint,
129
- txHashes,
130
- signature: blockSignature ?? checkpointSignature, // Use checkpoint signature as block signature if not provided
131
- });
131
+ return new CheckpointProposal(
132
+ checkpointHeader,
133
+ block.archive.root,
134
+ feeAssetPriceModifier,
135
+ checkpointSignature,
136
+ TEST_COORDINATION_SIGNATURE_CONTEXT,
137
+ {
138
+ blockHeader: block.header,
139
+ indexWithinCheckpoint: block.indexWithinCheckpoint,
140
+ txHashes,
141
+ signature: blockSignature ?? checkpointSignature, // Use checkpoint signature as block signature if not provided
142
+ },
143
+ );
132
144
  }
133
145
 
134
146
  /**
@@ -143,10 +155,16 @@ export function createCheckpointAttestation(
143
155
  feeAssetPriceModifier: bigint = 0n,
144
156
  ): CheckpointAttestation {
145
157
  const checkpointHeader = createCheckpointHeaderFromBlock(block);
146
- const payload = new ConsensusPayload(checkpointHeader, block.archive.root, feeAssetPriceModifier);
158
+ const payload = new ConsensusPayload(
159
+ checkpointHeader,
160
+ block.archive.root,
161
+ feeAssetPriceModifier,
162
+ TEST_COORDINATION_SIGNATURE_CONTEXT,
163
+ );
147
164
  const attestation = new CheckpointAttestation(payload, signature, signature);
148
- // Set sender directly for testing (bypasses signature recovery)
149
- (attestation as any).sender = sender;
165
+ // Bypass signature recovery for testing since we use random signatures
166
+ (attestation as any).getSender = () => sender;
167
+ (attestation as any).getProposer = () => sender;
150
168
  return attestation;
151
169
  }
152
170