@aztec/sequencer-client 0.0.1-commit.c7c42ec → 0.0.1-commit.f295ac2

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 (71) 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/global_variable_builder/global_builder.d.ts +4 -4
  7. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  8. package/dest/global_variable_builder/global_builder.js +12 -12
  9. package/dest/index.d.ts +2 -3
  10. package/dest/index.d.ts.map +1 -1
  11. package/dest/index.js +1 -2
  12. package/dest/publisher/sequencer-publisher-metrics.d.ts +1 -1
  13. package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
  14. package/dest/publisher/sequencer-publisher-metrics.js +15 -86
  15. package/dest/publisher/sequencer-publisher.d.ts +17 -16
  16. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  17. package/dest/publisher/sequencer-publisher.js +442 -49
  18. package/dest/sequencer/checkpoint_proposal_job.d.ts +14 -9
  19. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  20. package/dest/sequencer/checkpoint_proposal_job.js +551 -38
  21. package/dest/sequencer/checkpoint_voter.d.ts +3 -2
  22. package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
  23. package/dest/sequencer/checkpoint_voter.js +34 -10
  24. package/dest/sequencer/index.d.ts +1 -3
  25. package/dest/sequencer/index.d.ts.map +1 -1
  26. package/dest/sequencer/index.js +0 -2
  27. package/dest/sequencer/metrics.d.ts +3 -3
  28. package/dest/sequencer/metrics.d.ts.map +1 -1
  29. package/dest/sequencer/metrics.js +30 -121
  30. package/dest/sequencer/sequencer.d.ts +24 -14
  31. package/dest/sequencer/sequencer.d.ts.map +1 -1
  32. package/dest/sequencer/sequencer.js +485 -41
  33. package/dest/test/index.d.ts +2 -3
  34. package/dest/test/index.d.ts.map +1 -1
  35. package/dest/test/mock_checkpoint_builder.d.ts +16 -7
  36. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  37. package/dest/test/mock_checkpoint_builder.js +32 -3
  38. package/dest/test/utils.d.ts +8 -4
  39. package/dest/test/utils.d.ts.map +1 -1
  40. package/dest/test/utils.js +23 -14
  41. package/package.json +30 -28
  42. package/src/client/sequencer-client.ts +3 -4
  43. package/src/config.ts +7 -0
  44. package/src/global_variable_builder/global_builder.ts +12 -12
  45. package/src/index.ts +1 -9
  46. package/src/publisher/sequencer-publisher-metrics.ts +14 -70
  47. package/src/publisher/sequencer-publisher.ts +84 -73
  48. package/src/sequencer/checkpoint_proposal_job.ts +183 -53
  49. package/src/sequencer/checkpoint_voter.ts +32 -7
  50. package/src/sequencer/index.ts +0 -2
  51. package/src/sequencer/metrics.ts +23 -131
  52. package/src/sequencer/sequencer.ts +122 -39
  53. package/src/test/index.ts +1 -2
  54. package/src/test/mock_checkpoint_builder.ts +62 -14
  55. package/src/test/utils.ts +42 -21
  56. package/dest/sequencer/block_builder.d.ts +0 -26
  57. package/dest/sequencer/block_builder.d.ts.map +0 -1
  58. package/dest/sequencer/block_builder.js +0 -129
  59. package/dest/sequencer/checkpoint_builder.d.ts +0 -63
  60. package/dest/sequencer/checkpoint_builder.d.ts.map +0 -1
  61. package/dest/sequencer/checkpoint_builder.js +0 -131
  62. package/dest/tx_validator/nullifier_cache.d.ts +0 -14
  63. package/dest/tx_validator/nullifier_cache.d.ts.map +0 -1
  64. package/dest/tx_validator/nullifier_cache.js +0 -24
  65. package/dest/tx_validator/tx_validator_factory.d.ts +0 -18
  66. package/dest/tx_validator/tx_validator_factory.d.ts.map +0 -1
  67. package/dest/tx_validator/tx_validator_factory.js +0 -53
  68. package/src/sequencer/block_builder.ts +0 -217
  69. package/src/sequencer/checkpoint_builder.ts +0 -217
  70. package/src/tx_validator/nullifier_cache.ts +0 -30
  71. package/src/tx_validator/tx_validator_factory.ts +0 -133
@@ -11,7 +11,6 @@ import {
11
11
  type TelemetryClient,
12
12
  type Tracer,
13
13
  type UpDownCounter,
14
- ValueType,
15
14
  } from '@aztec/telemetry-client';
16
15
 
17
16
  import { type Hex, formatUnits } from 'viem';
@@ -44,7 +43,7 @@ export class SequencerMetrics {
44
43
  private blockProposalPrecheckFailed: UpDownCounter;
45
44
  private checkpointSuccess: UpDownCounter;
46
45
  private slashingAttempts: UpDownCounter;
47
- private blockAttestationDelay: Histogram;
46
+ private checkpointAttestationDelay: Histogram;
48
47
 
49
48
  // Fisherman fee analysis metrics
50
49
  private fishermanWouldBeIncluded: UpDownCounter;
@@ -70,33 +69,13 @@ export class SequencerMetrics {
70
69
 
71
70
  this.blockCounter = this.meter.createUpDownCounter(Metrics.SEQUENCER_BLOCK_COUNT);
72
71
 
73
- this.blockBuildDuration = this.meter.createHistogram(Metrics.SEQUENCER_BLOCK_BUILD_DURATION, {
74
- unit: 'ms',
75
- description: 'Duration to build a block',
76
- valueType: ValueType.INT,
77
- });
72
+ this.blockBuildDuration = this.meter.createHistogram(Metrics.SEQUENCER_BLOCK_BUILD_DURATION);
78
73
 
79
- this.blockBuildManaPerSecond = this.meter.createGauge(Metrics.SEQUENCER_BLOCK_BUILD_MANA_PER_SECOND, {
80
- unit: 'mana/s',
81
- description: 'Mana per second when building a block',
82
- valueType: ValueType.INT,
83
- });
74
+ this.blockBuildManaPerSecond = this.meter.createGauge(Metrics.SEQUENCER_BLOCK_BUILD_MANA_PER_SECOND);
84
75
 
85
- this.stateTransitionBufferDuration = this.meter.createHistogram(
86
- Metrics.SEQUENCER_STATE_TRANSITION_BUFFER_DURATION,
87
- {
88
- unit: 'ms',
89
- description:
90
- 'The time difference between when the sequencer needed to transition to a new state and when it actually did.',
91
- valueType: ValueType.INT,
92
- },
93
- );
76
+ this.stateTransitionBufferDuration = this.meter.createHistogram(Metrics.SEQUENCER_STATE_TRANSITION_BUFFER_DURATION);
94
77
 
95
- this.blockAttestationDelay = this.meter.createHistogram(Metrics.SEQUENCER_BLOCK_ATTESTATION_DELAY, {
96
- unit: 'ms',
97
- description: 'The time difference between block proposal and minimal attestation count reached,',
98
- valueType: ValueType.INT,
99
- });
78
+ this.checkpointAttestationDelay = this.meter.createHistogram(Metrics.SEQUENCER_CHECKPOINT_ATTESTATION_DELAY);
100
79
 
101
80
  // Init gauges and counters
102
81
  this.blockCounter.add(0, {
@@ -106,152 +85,65 @@ export class SequencerMetrics {
106
85
  [Attributes.STATUS]: 'built',
107
86
  });
108
87
 
109
- this.rewards = this.meter.createGauge(Metrics.SEQUENCER_CURRENT_BLOCK_REWARDS, {
110
- valueType: ValueType.DOUBLE,
111
- description: 'The rewards earned',
112
- });
88
+ this.rewards = this.meter.createGauge(Metrics.SEQUENCER_CURRENT_BLOCK_REWARDS);
113
89
 
114
- this.slots = this.meter.createUpDownCounter(Metrics.SEQUENCER_SLOT_COUNT, {
115
- valueType: ValueType.INT,
116
- description: 'The number of slots this sequencer was selected for',
117
- });
90
+ this.slots = this.meter.createUpDownCounter(Metrics.SEQUENCER_SLOT_COUNT);
118
91
 
119
92
  /**
120
93
  * NOTE: we do not track missed slots as a separate metric. That would be difficult to determine
121
94
  * Instead, use a computed metric, `slots - filledSlots` to get the number of slots a sequencer has missed.
122
95
  */
123
- this.filledSlots = this.meter.createUpDownCounter(Metrics.SEQUENCER_FILLED_SLOT_COUNT, {
124
- valueType: ValueType.INT,
125
- description: 'The number of slots this sequencer has filled',
126
- });
96
+ this.filledSlots = this.meter.createUpDownCounter(Metrics.SEQUENCER_FILLED_SLOT_COUNT);
127
97
 
128
- this.timeToCollectAttestations = this.meter.createGauge(Metrics.SEQUENCER_COLLECT_ATTESTATIONS_DURATION, {
129
- description: 'The time spent collecting attestations from committee members',
130
- unit: 'ms',
131
- valueType: ValueType.INT,
132
- });
98
+ this.timeToCollectAttestations = this.meter.createGauge(Metrics.SEQUENCER_COLLECT_ATTESTATIONS_DURATION);
133
99
 
134
- this.allowanceToCollectAttestations = this.meter.createGauge(
135
- Metrics.SEQUENCER_COLLECT_ATTESTATIONS_TIME_ALLOWANCE,
136
- {
137
- description: 'Maximum amount of time to collect attestations',
138
- unit: 'ms',
139
- valueType: ValueType.INT,
140
- },
141
- );
100
+ this.allowanceToCollectAttestations = this.meter.createGauge(Metrics.SEQUENCER_COLLECT_ATTESTATIONS_TIME_ALLOWANCE);
142
101
 
143
- this.requiredAttestions = this.meter.createGauge(Metrics.SEQUENCER_REQUIRED_ATTESTATIONS_COUNT, {
144
- valueType: ValueType.INT,
145
- description: 'The minimum number of attestations required to publish a block',
146
- });
102
+ this.requiredAttestions = this.meter.createGauge(Metrics.SEQUENCER_REQUIRED_ATTESTATIONS_COUNT);
147
103
 
148
- this.collectedAttestions = this.meter.createGauge(Metrics.SEQUENCER_COLLECTED_ATTESTATIONS_COUNT, {
149
- valueType: ValueType.INT,
150
- description: 'The minimum number of attestations required to publish a block',
151
- });
104
+ this.collectedAttestions = this.meter.createGauge(Metrics.SEQUENCER_COLLECTED_ATTESTATIONS_COUNT);
152
105
 
153
- this.blockProposalFailed = this.meter.createUpDownCounter(Metrics.SEQUENCER_BLOCK_PROPOSAL_FAILED_COUNT, {
154
- valueType: ValueType.INT,
155
- description: 'The number of times block proposal failed (including validation builds)',
156
- });
106
+ this.blockProposalFailed = this.meter.createUpDownCounter(Metrics.SEQUENCER_BLOCK_PROPOSAL_FAILED_COUNT);
157
107
 
158
- this.blockProposalSuccess = this.meter.createUpDownCounter(Metrics.SEQUENCER_BLOCK_PROPOSAL_SUCCESS_COUNT, {
159
- valueType: ValueType.INT,
160
- description: 'The number of times block proposal succeeded (including validation builds)',
161
- });
108
+ this.blockProposalSuccess = this.meter.createUpDownCounter(Metrics.SEQUENCER_BLOCK_PROPOSAL_SUCCESS_COUNT);
162
109
 
163
- this.checkpointSuccess = this.meter.createUpDownCounter(Metrics.SEQUENCER_CHECKPOINT_SUCCESS_COUNT, {
164
- valueType: ValueType.INT,
165
- description: 'The number of times checkpoint publishing succeeded',
166
- });
110
+ this.checkpointSuccess = this.meter.createUpDownCounter(Metrics.SEQUENCER_CHECKPOINT_SUCCESS_COUNT);
167
111
 
168
112
  this.blockProposalPrecheckFailed = this.meter.createUpDownCounter(
169
113
  Metrics.SEQUENCER_BLOCK_PROPOSAL_PRECHECK_FAILED_COUNT,
170
- {
171
- valueType: ValueType.INT,
172
- description: 'The number of times block proposal pre-build checks failed',
173
- },
174
114
  );
175
115
 
176
- this.slashingAttempts = this.meter.createUpDownCounter(Metrics.SEQUENCER_SLASHING_ATTEMPTS_COUNT, {
177
- valueType: ValueType.INT,
178
- description: 'The number of slashing action attempts',
179
- });
116
+ this.slashingAttempts = this.meter.createUpDownCounter(Metrics.SEQUENCER_SLASHING_ATTEMPTS_COUNT);
180
117
 
181
118
  // Fisherman fee analysis metrics
182
- this.fishermanWouldBeIncluded = this.meter.createUpDownCounter(Metrics.FISHERMAN_FEE_ANALYSIS_WOULD_BE_INCLUDED, {
183
- valueType: ValueType.INT,
184
- description: 'Whether the transaction would have been included in the block',
185
- });
119
+ this.fishermanWouldBeIncluded = this.meter.createUpDownCounter(Metrics.FISHERMAN_FEE_ANALYSIS_WOULD_BE_INCLUDED);
186
120
 
187
- this.fishermanTimeBeforeBlock = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_TIME_BEFORE_BLOCK, {
188
- unit: 'ms',
189
- description: 'Time in ms between fee analysis and block being mined',
190
- valueType: ValueType.INT,
191
- });
121
+ this.fishermanTimeBeforeBlock = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_TIME_BEFORE_BLOCK);
192
122
 
193
- this.fishermanPendingBlobTxCount = this.meter.createHistogram(
194
- Metrics.FISHERMAN_FEE_ANALYSIS_PENDING_BLOB_TX_COUNT,
195
- {
196
- description: 'Number of blob transactions seen in the pending block',
197
- valueType: ValueType.INT,
198
- },
199
- );
123
+ this.fishermanPendingBlobTxCount = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_PENDING_BLOB_TX_COUNT);
200
124
 
201
125
  this.fishermanIncludedBlobTxCount = this.meter.createHistogram(
202
126
  Metrics.FISHERMAN_FEE_ANALYSIS_INCLUDED_BLOB_TX_COUNT,
203
- {
204
- description: 'Number of blob transactions that got included in the mined block',
205
- valueType: ValueType.INT,
206
- },
207
127
  );
208
128
 
209
129
  this.fishermanCalculatedPriorityFee = this.meter.createHistogram(
210
130
  Metrics.FISHERMAN_FEE_ANALYSIS_CALCULATED_PRIORITY_FEE,
211
- {
212
- unit: 'gwei',
213
- description: 'Priority fee calculated by each strategy',
214
- valueType: ValueType.DOUBLE,
215
- },
216
131
  );
217
132
 
218
- this.fishermanPriorityFeeDelta = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_PRIORITY_FEE_DELTA, {
219
- unit: 'gwei',
220
- description: 'Difference between our priority fee and minimum included priority fee',
221
- valueType: ValueType.DOUBLE,
222
- });
133
+ this.fishermanPriorityFeeDelta = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_PRIORITY_FEE_DELTA);
223
134
 
224
- this.fishermanEstimatedCost = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_ESTIMATED_COST, {
225
- unit: 'eth',
226
- description: 'Estimated total cost in ETH for the transaction with this strategy',
227
- valueType: ValueType.DOUBLE,
228
- });
135
+ this.fishermanEstimatedCost = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_ESTIMATED_COST);
229
136
 
230
137
  this.fishermanEstimatedOverpayment = this.meter.createHistogram(
231
138
  Metrics.FISHERMAN_FEE_ANALYSIS_ESTIMATED_OVERPAYMENT,
232
- {
233
- unit: 'eth',
234
- description: 'Estimated overpayment in ETH vs minimum required for inclusion',
235
- valueType: ValueType.DOUBLE,
236
- },
237
139
  );
238
140
 
239
141
  this.fishermanMinedBlobTxPriorityFee = this.meter.createHistogram(
240
142
  Metrics.FISHERMAN_FEE_ANALYSIS_MINED_BLOB_TX_PRIORITY_FEE,
241
- {
242
- unit: 'gwei',
243
- description: 'Priority fee per gas for blob transactions in mined blocks',
244
- valueType: ValueType.DOUBLE,
245
- },
246
143
  );
247
144
 
248
145
  this.fishermanMinedBlobTxTotalCost = this.meter.createHistogram(
249
146
  Metrics.FISHERMAN_FEE_ANALYSIS_MINED_BLOB_TX_TOTAL_COST,
250
- {
251
- unit: 'eth',
252
- description: 'Total cost in ETH for blob transactions in mined blocks',
253
- valueType: ValueType.DOUBLE,
254
- },
255
147
  );
256
148
  }
257
149
 
@@ -264,8 +156,8 @@ export class SequencerMetrics {
264
156
  this.timeToCollectAttestations.record(0);
265
157
  }
266
158
 
267
- public recordBlockAttestationDelay(duration: number) {
268
- this.blockAttestationDelay.record(duration);
159
+ public recordCheckpointAttestationDelay(duration: number) {
160
+ this.checkpointAttestationDelay.record(duration);
269
161
  }
270
162
 
271
163
  public recordCollectedAttestations(count: number, durationMs: number) {
@@ -12,7 +12,7 @@ import type { DateProvider } from '@aztec/foundation/timer';
12
12
  import type { TypedEventEmitter } from '@aztec/foundation/types';
13
13
  import type { P2P } from '@aztec/p2p';
14
14
  import type { SlasherClientInterface } from '@aztec/slasher';
15
- import type { L2BlockNew, L2BlockSource, ValidateBlockResult } from '@aztec/stdlib/block';
15
+ import type { L2BlockNew, L2BlockSink, L2BlockSource, ValidateCheckpointResult } from '@aztec/stdlib/block';
16
16
  import type { Checkpoint } from '@aztec/stdlib/checkpoint';
17
17
  import { getSlotAtTimestamp, getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
18
18
  import {
@@ -24,16 +24,15 @@ import {
24
24
  import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
25
25
  import { pickFromSchema } from '@aztec/stdlib/schemas';
26
26
  import { MerkleTreeId } from '@aztec/stdlib/trees';
27
- import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
28
- import type { ValidatorClient } from '@aztec/validator-client';
27
+ import { Attributes, type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
28
+ import { FullNodeCheckpointsBuilder, type ValidatorClient } from '@aztec/validator-client';
29
29
 
30
30
  import EventEmitter from 'node:events';
31
31
 
32
32
  import { DefaultSequencerConfig } from '../config.js';
33
33
  import type { GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
34
34
  import type { SequencerPublisherFactory } from '../publisher/sequencer-publisher-factory.js';
35
- import type { InvalidateBlockRequest, SequencerPublisher } from '../publisher/sequencer-publisher.js';
36
- import { FullNodeCheckpointsBuilder } from './checkpoint_builder.js';
35
+ import type { InvalidateCheckpointRequest, SequencerPublisher } from '../publisher/sequencer-publisher.js';
37
36
  import { CheckpointProposalJob } from './checkpoint_proposal_job.js';
38
37
  import { CheckpointVoter } from './checkpoint_voter.js';
39
38
  import { SequencerInterruptedError, SequencerTooSlowError } from './errors.js';
@@ -58,8 +57,8 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
58
57
  private state = SequencerState.STOPPED;
59
58
  private metrics: SequencerMetrics;
60
59
 
61
- /** The last slot for which we attempted to vote when sync failed, to prevent duplicate attempts. */
62
- private lastSlotForVoteWhenSyncFailed: SlotNumber | undefined;
60
+ /** The last slot for which we attempted to perform our voting duties with degraded block production */
61
+ private lastSlotForFallbackVote: SlotNumber | undefined;
63
62
 
64
63
  /** The last slot for which we triggered a checkpoint proposal job, to prevent duplicate attempts. */
65
64
  private lastSlotForCheckpointProposalJob: SlotNumber | undefined;
@@ -91,7 +90,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
91
90
  protected p2pClient: P2P,
92
91
  protected worldState: WorldStateSynchronizer,
93
92
  protected slasherClient: SlasherClientInterface | undefined,
94
- protected l2BlockSource: L2BlockSource,
93
+ protected l2BlockSource: L2BlockSource & L2BlockSink,
95
94
  protected l1ToL2MessageSource: L1ToL2MessageSource,
96
95
  protected checkpointsBuilder: FullNodeCheckpointsBuilder,
97
96
  protected l1Constants: SequencerRollupConstants,
@@ -160,7 +159,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
160
159
  this.log.info('Stopped sequencer');
161
160
  }
162
161
 
163
- @trackSpan('Sequencer.work')
164
162
  /** Main sequencer loop with a try/catch */
165
163
  protected async safeWork() {
166
164
  try {
@@ -198,12 +196,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
198
196
  * - Collect attestations for the final block
199
197
  * - Submit checkpoint
200
198
  */
199
+ @trackSpan('Sequencer.work')
201
200
  protected async work() {
202
201
  this.setState(SequencerState.SYNCHRONIZING, undefined);
203
202
  const { slot, ts, now, epoch } = this.epochCache.getEpochAndSlotInNextL1Slot();
204
203
 
205
204
  // Check if we are synced and it's our slot, grab a publisher, check previous block invalidation, etc
206
- const checkpointProposalJob = await this.prepareCheckpointProposal(slot, ts, now);
205
+ const checkpointProposalJob = await this.prepareCheckpointProposal(epoch, slot, ts, now);
207
206
  if (!checkpointProposalJob) {
208
207
  return;
209
208
  }
@@ -233,7 +232,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
233
232
  * This is the initial step in the main loop.
234
233
  * @returns CheckpointProposalJob if successful, undefined if we are not yet synced or are not the proposer.
235
234
  */
235
+ @trackSpan('Sequencer.prepareCheckpointProposal')
236
236
  private async prepareCheckpointProposal(
237
+ epoch: EpochNumber,
237
238
  slot: SlotNumber,
238
239
  ts: bigint,
239
240
  now: bigint,
@@ -263,8 +264,27 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
263
264
  return undefined;
264
265
  }
265
266
 
266
- // TODO(palla/mbps): Compute proper checkpoint number
267
- const checkpointNumber = CheckpointNumber.fromBlockNumber(BlockNumber(syncedTo.blockNumber + 1));
267
+ // If escape hatch is open for this epoch, do not start checkpoint proposal work and do not attempt invalidations.
268
+ // Still perform governance/slashing voting (as proposer) once per slot.
269
+ const isEscapeHatchOpen = await this.epochCache.isEscapeHatchOpen(epoch);
270
+
271
+ if (isEscapeHatchOpen) {
272
+ this.setState(SequencerState.PROPOSER_CHECK, slot);
273
+ const [canPropose, proposer] = await this.checkCanPropose(slot);
274
+ if (canPropose) {
275
+ await this.tryVoteWhenEscapeHatchOpen({ slot, proposer });
276
+ } else {
277
+ this.log.trace(`Escape hatch open but we are not proposer, skipping vote-only actions`, {
278
+ slot,
279
+ epoch,
280
+ proposer,
281
+ });
282
+ }
283
+ return undefined;
284
+ }
285
+
286
+ // Next checkpoint follows from the last synced one
287
+ const checkpointNumber = CheckpointNumber(syncedTo.checkpointNumber + 1);
268
288
 
269
289
  const logCtx = {
270
290
  now,
@@ -280,9 +300,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
280
300
  this.setState(SequencerState.PROPOSER_CHECK, slot);
281
301
  const [canPropose, proposer] = await this.checkCanPropose(slot);
282
302
 
283
- // If we are not a proposer check if we should invalidate a invalid block, and bail
303
+ // If we are not a proposer check if we should invalidate an invalid checkpoint, and bail
284
304
  if (!canPropose) {
285
- await this.considerInvalidatingBlock(syncedTo, slot);
305
+ await this.considerInvalidatingCheckpoint(syncedTo, slot);
286
306
  return undefined;
287
307
  }
288
308
 
@@ -312,15 +332,14 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
312
332
  }
313
333
 
314
334
  // Prepare invalidation request if the pending chain is invalid (returns undefined if no need)
315
- // TODO(palla/mbps): We need to invalidate checkpoints, not blocks
316
- const invalidateBlock = await publisher.simulateInvalidateBlock(syncedTo.pendingChainValidationStatus);
335
+ const invalidateCheckpoint = await publisher.simulateInvalidateCheckpoint(syncedTo.pendingChainValidationStatus);
317
336
 
318
337
  // Check with the rollup contract if we can indeed propose at the next L2 slot. This check should not fail
319
338
  // if all the previous checks are good, but we do it just in case.
320
339
  const canProposeCheck = await publisher.canProposeAtNextEthBlock(
321
340
  syncedTo.archive,
322
341
  proposer ?? EthAddress.ZERO,
323
- invalidateBlock,
342
+ invalidateCheckpoint,
324
343
  );
325
344
 
326
345
  if (canProposeCheck === undefined) {
@@ -358,39 +377,44 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
358
377
 
359
378
  // Create and return the checkpoint proposal job
360
379
  return this.createCheckpointProposalJob(
380
+ epoch,
361
381
  slot,
362
382
  checkpointNumber,
363
383
  syncedTo.blockNumber,
364
384
  proposer,
365
385
  publisher,
366
386
  attestorAddress,
367
- invalidateBlock,
387
+ invalidateCheckpoint,
368
388
  );
369
389
  }
370
390
 
371
391
  protected createCheckpointProposalJob(
392
+ epoch: EpochNumber,
372
393
  slot: SlotNumber,
373
394
  checkpointNumber: CheckpointNumber,
374
395
  syncedToBlockNumber: BlockNumber,
375
396
  proposer: EthAddress | undefined,
376
397
  publisher: SequencerPublisher,
377
398
  attestorAddress: EthAddress,
378
- invalidateBlock: InvalidateBlockRequest | undefined,
399
+ invalidateCheckpoint: InvalidateCheckpointRequest | undefined,
379
400
  ): CheckpointProposalJob {
380
401
  return new CheckpointProposalJob(
402
+ epoch,
381
403
  slot,
382
404
  checkpointNumber,
383
405
  syncedToBlockNumber,
384
406
  proposer,
385
407
  publisher,
386
408
  attestorAddress,
387
- invalidateBlock,
409
+ invalidateCheckpoint,
388
410
  this.validatorClient,
389
411
  this.globalsBuilder,
390
412
  this.p2pClient,
391
413
  this.worldState,
392
414
  this.l1ToL2MessageSource,
415
+ this.l2BlockSource,
393
416
  this.checkpointsBuilder,
417
+ this.l2BlockSource,
394
418
  this.l1Constants,
395
419
  this.config,
396
420
  this.timetable,
@@ -401,6 +425,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
401
425
  this,
402
426
  this.setState.bind(this),
403
427
  this.log,
428
+ this.tracer,
404
429
  );
405
430
  }
406
431
 
@@ -469,9 +494,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
469
494
  number: syncSummary.latestBlockNumber,
470
495
  hash: syncSummary.latestBlockHash,
471
496
  })),
472
- this.l2BlockSource.getL2Tips().then(t => t.latest),
497
+ this.l2BlockSource.getL2Tips().then(t => t.proposed),
473
498
  this.p2pClient.getStatus().then(p2p => p2p.syncedToL2Block),
474
- this.l1ToL2MessageSource.getL2Tips().then(t => t.latest),
499
+ this.l1ToL2MessageSource.getL2Tips().then(t => t.proposed),
475
500
  this.l2BlockSource.getPendingChainValidationStatus(),
476
501
  ] as const);
477
502
 
@@ -479,6 +504,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
479
504
 
480
505
  // Handle zero as a special case, since the block hash won't match across services if we're changing the prefilled data for the genesis block,
481
506
  // as the world state can compute the new genesis block hash, but other components use the hardcoded constant.
507
+ // TODO(palla/mbps): Fix the above. All components should be able to handle dynamic genesis block hashes.
482
508
  const result =
483
509
  (l2BlockSource.number === 0 && worldState.number === 0 && p2p.number === 0 && l1ToL2MessageSource.number === 0) ||
484
510
  (worldState.hash === l2BlockSource.hash &&
@@ -494,7 +520,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
494
520
  const blockNumber = worldState.number;
495
521
  if (blockNumber < INITIAL_L2_BLOCK_NUM) {
496
522
  const archive = new Fr((await this.worldState.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root);
497
- return { blockNumber: BlockNumber(INITIAL_L2_BLOCK_NUM - 1), archive, l1Timestamp, pendingChainValidationStatus };
523
+ return {
524
+ checkpointNumber: CheckpointNumber.ZERO,
525
+ blockNumber: BlockNumber.ZERO,
526
+ archive,
527
+ l1Timestamp,
528
+ pendingChainValidationStatus,
529
+ };
498
530
  }
499
531
 
500
532
  const block = await this.l2BlockSource.getL2BlockNew(blockNumber);
@@ -507,6 +539,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
507
539
  return {
508
540
  block,
509
541
  blockNumber: block.number,
542
+ checkpointNumber: block.checkpointNumber,
510
543
  archive: block.archive.root,
511
544
  l1Timestamp,
512
545
  pendingChainValidationStatus,
@@ -555,11 +588,12 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
555
588
  * Tries to vote on slashing actions and governance when the sync check fails but we're past the max time for initializing a proposal.
556
589
  * This allows the sequencer to participate in governance/slashing votes even when it cannot build blocks.
557
590
  */
591
+ @trackSpan('Seqeuencer.tryVoteWhenSyncFails', ({ slot }) => ({ [Attributes.SLOT_NUMBER]: slot }))
558
592
  protected async tryVoteWhenSyncFails(args: { slot: SlotNumber; ts: bigint }): Promise<void> {
559
593
  const { slot } = args;
560
594
 
561
595
  // Prevent duplicate attempts in the same slot
562
- if (this.lastSlotForVoteWhenSyncFailed === slot) {
596
+ if (this.lastSlotForFallbackVote === slot) {
563
597
  this.log.trace(`Already attempted to vote in slot ${slot} (skipping)`);
564
598
  return;
565
599
  }
@@ -591,7 +625,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
591
625
  }
592
626
 
593
627
  // Mark this slot as attempted
594
- this.lastSlotForVoteWhenSyncFailed = slot;
628
+ this.lastSlotForFallbackVote = slot;
595
629
 
596
630
  // Get a publisher for voting
597
631
  const { attestorAddress, publisher } = await this.publisherFactory.create(proposer);
@@ -625,13 +659,61 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
625
659
  await publisher.sendRequests();
626
660
  }
627
661
 
662
+ /**
663
+ * Tries to vote on slashing actions and governance proposals when escape hatch is open.
664
+ * This allows the sequencer to participate in voting without performing checkpoint proposal work.
665
+ */
666
+ @trackSpan('Sequencer.tryVoteWhenEscapeHatchOpen', ({ slot }) => ({ [Attributes.SLOT_NUMBER]: slot }))
667
+ protected async tryVoteWhenEscapeHatchOpen(args: {
668
+ slot: SlotNumber;
669
+ proposer: EthAddress | undefined;
670
+ }): Promise<void> {
671
+ const { slot, proposer } = args;
672
+
673
+ // Prevent duplicate attempts in the same slot
674
+ if (this.lastSlotForFallbackVote === slot) {
675
+ this.log.trace(`Already attempted to vote in slot ${slot} (escape hatch open, skipping)`);
676
+ return;
677
+ }
678
+
679
+ // Mark this slot as attempted
680
+ this.lastSlotForFallbackVote = slot;
681
+
682
+ const { attestorAddress, publisher } = await this.publisherFactory.create(proposer);
683
+
684
+ this.log.debug(`Escape hatch open for slot ${slot}, attempting vote-only actions`, { slot, attestorAddress });
685
+
686
+ const voter = new CheckpointVoter(
687
+ slot,
688
+ publisher,
689
+ attestorAddress,
690
+ this.validatorClient,
691
+ this.slasherClient,
692
+ this.l1Constants,
693
+ this.config,
694
+ this.metrics,
695
+ this.log,
696
+ );
697
+
698
+ const votesPromises = voter.enqueueVotes();
699
+ const votes = await Promise.all(votesPromises);
700
+
701
+ if (votes.every(p => !p)) {
702
+ this.log.debug(`No votes to enqueue for slot ${slot} (escape hatch open)`);
703
+ return;
704
+ }
705
+
706
+ this.log.info(`Voting in slot ${slot} (escape hatch open)`, { slot });
707
+ await publisher.sendRequests();
708
+ }
709
+
628
710
  /**
629
711
  * Considers invalidating a block if the pending chain is invalid. Depends on how long the invalid block
630
712
  * has been there without being invalidated and whether the sequencer is in the committee or not. We always
631
713
  * have the proposer try to invalidate, but if they fail, the sequencers in the committee are expected to try,
632
714
  * and if they fail, any sequencer will try as well.
633
715
  */
634
- protected async considerInvalidatingBlock(
716
+ protected async considerInvalidatingCheckpoint(
635
717
  syncedTo: SequencerSyncCheckResult,
636
718
  currentSlot: SlotNumber,
637
719
  ): Promise<void> {
@@ -640,18 +722,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
640
722
  return;
641
723
  }
642
724
 
643
- const invalidBlockNumber = pendingChainValidationStatus.block.blockNumber;
644
- const invalidBlockTimestamp = pendingChainValidationStatus.block.timestamp;
645
- const timeSinceChainInvalid = this.dateProvider.nowInSeconds() - Number(invalidBlockTimestamp);
725
+ const invalidCheckpointNumber = pendingChainValidationStatus.checkpoint.checkpointNumber;
726
+ const invalidCheckpointTimestamp = pendingChainValidationStatus.checkpoint.timestamp;
727
+ const timeSinceChainInvalid = this.dateProvider.nowInSeconds() - Number(invalidCheckpointTimestamp);
646
728
  const ourValidatorAddresses = this.validatorClient.getValidatorAddresses();
647
729
 
648
730
  const { secondsBeforeInvalidatingBlockAsCommitteeMember, secondsBeforeInvalidatingBlockAsNonCommitteeMember } =
649
731
  this.config;
650
732
 
651
733
  const logData = {
652
- invalidL1Timestamp: invalidBlockTimestamp,
734
+ invalidL1Timestamp: invalidCheckpointTimestamp,
653
735
  l1Timestamp,
654
- invalidBlock: pendingChainValidationStatus.block,
736
+ invalidCheckpoint: pendingChainValidationStatus.checkpoint,
655
737
  secondsBeforeInvalidatingBlockAsCommitteeMember,
656
738
  secondsBeforeInvalidatingBlockAsNonCommitteeMember,
657
739
  ourValidatorAddresses,
@@ -697,25 +779,25 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
697
779
 
698
780
  const { publisher } = await this.publisherFactory.create(validatorToUse);
699
781
 
700
- const invalidateBlock = await publisher.simulateInvalidateBlock(pendingChainValidationStatus);
701
- if (!invalidateBlock) {
702
- this.log.warn(`Failed to simulate invalidate block`, logData);
782
+ const invalidateCheckpoint = await publisher.simulateInvalidateCheckpoint(pendingChainValidationStatus);
783
+ if (!invalidateCheckpoint) {
784
+ this.log.warn(`Failed to simulate invalidate checkpoint`, logData);
703
785
  return;
704
786
  }
705
787
 
706
788
  this.log.info(
707
789
  invalidateAsCommitteeMember
708
- ? `Invalidating block ${invalidBlockNumber} as committee member`
709
- : `Invalidating block ${invalidBlockNumber} as non-committee member`,
790
+ ? `Invalidating checkpoint ${invalidCheckpointNumber} as committee member`
791
+ : `Invalidating checkpoint ${invalidCheckpointNumber} as non-committee member`,
710
792
  logData,
711
793
  );
712
794
 
713
- publisher.enqueueInvalidateBlock(invalidateBlock);
795
+ publisher.enqueueInvalidateCheckpoint(invalidateCheckpoint);
714
796
 
715
797
  if (!this.config.fishermanMode) {
716
798
  await publisher.sendRequests();
717
799
  } else {
718
- this.log.info('Invalidating block in fisherman mode, clearing pending requests');
800
+ this.log.info('Invalidating checkpoint in fisherman mode, clearing pending requests');
719
801
  publisher.clearPendingRequests();
720
802
  }
721
803
  }
@@ -789,8 +871,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
789
871
 
790
872
  type SequencerSyncCheckResult = {
791
873
  block?: L2BlockNew;
874
+ checkpointNumber: CheckpointNumber;
792
875
  blockNumber: BlockNumber;
793
876
  archive: Fr;
794
877
  l1Timestamp: bigint;
795
- pendingChainValidationStatus: ValidateBlockResult;
878
+ pendingChainValidationStatus: ValidateCheckpointResult;
796
879
  };
package/src/test/index.ts CHANGED
@@ -1,12 +1,11 @@
1
1
  import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs';
2
2
  import type { PublisherManager } from '@aztec/ethereum/publisher-manager';
3
3
  import type { PublicProcessorFactory } from '@aztec/simulator/server';
4
- import type { ValidatorClient } from '@aztec/validator-client';
4
+ import type { FullNodeCheckpointsBuilder, ValidatorClient } from '@aztec/validator-client';
5
5
 
6
6
  import { SequencerClient } from '../client/sequencer-client.js';
7
7
  import type { SequencerPublisherFactory } from '../publisher/sequencer-publisher-factory.js';
8
8
  import type { SequencerPublisher } from '../publisher/sequencer-publisher.js';
9
- import type { FullNodeCheckpointsBuilder } from '../sequencer/checkpoint_builder.js';
10
9
  import { Sequencer } from '../sequencer/sequencer.js';
11
10
  import type { SequencerTimetable } from '../sequencer/timetable.js';
12
11