@aztec/sequencer-client 0.0.1-commit.6d3c34e → 0.0.1-commit.7cf39cb55
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.
- package/dest/client/sequencer-client.js +1 -1
- package/dest/config.d.ts +1 -2
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +5 -11
- package/dest/global_variable_builder/global_builder.js +2 -2
- package/dest/index.d.ts +2 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -1
- package/dest/publisher/sequencer-publisher-metrics.d.ts +1 -1
- package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-metrics.js +12 -4
- package/dest/publisher/sequencer-publisher.d.ts +1 -2
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +39 -18
- package/dest/sequencer/checkpoint_proposal_job.d.ts +34 -9
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +136 -34
- package/dest/sequencer/checkpoint_voter.d.ts +3 -2
- package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_voter.js +34 -10
- package/dest/sequencer/index.d.ts +1 -2
- package/dest/sequencer/index.d.ts.map +1 -1
- package/dest/sequencer/index.js +0 -1
- package/dest/sequencer/metrics.d.ts +5 -2
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +52 -17
- package/dest/sequencer/sequencer.d.ts +19 -9
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +73 -12
- package/dest/sequencer/timetable.d.ts +1 -4
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +1 -4
- package/dest/test/mock_checkpoint_builder.d.ts +17 -13
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +28 -10
- package/dest/test/utils.d.ts +8 -8
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +8 -7
- package/package.json +30 -28
- package/src/client/sequencer-client.ts +1 -1
- package/src/config.ts +10 -14
- package/src/global_variable_builder/global_builder.ts +2 -2
- package/src/index.ts +1 -6
- package/src/publisher/sequencer-publisher-metrics.ts +7 -3
- package/src/publisher/sequencer-publisher.ts +34 -18
- package/src/sequencer/checkpoint_proposal_job.ts +189 -54
- package/src/sequencer/checkpoint_voter.ts +32 -7
- package/src/sequencer/index.ts +0 -1
- package/src/sequencer/metrics.ts +60 -18
- package/src/sequencer/sequencer.ts +90 -11
- package/src/sequencer/timetable.ts +6 -5
- package/src/test/mock_checkpoint_builder.ts +64 -34
- package/src/test/utils.ts +20 -12
- package/dest/sequencer/block_builder.d.ts +0 -26
- package/dest/sequencer/block_builder.d.ts.map +0 -1
- package/dest/sequencer/block_builder.js +0 -129
- package/src/sequencer/block_builder.ts +0 -216
package/src/sequencer/metrics.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type TelemetryClient,
|
|
12
12
|
type Tracer,
|
|
13
13
|
type UpDownCounter,
|
|
14
|
+
createUpDownCounterWithDefault,
|
|
14
15
|
} from '@aztec/telemetry-client';
|
|
15
16
|
|
|
16
17
|
import { type Hex, formatUnits } from 'viem';
|
|
@@ -50,6 +51,9 @@ export class SequencerMetrics {
|
|
|
50
51
|
private fishermanTimeBeforeBlock: Histogram;
|
|
51
52
|
private fishermanPendingBlobTxCount: Histogram;
|
|
52
53
|
private fishermanIncludedBlobTxCount: Histogram;
|
|
54
|
+
private fishermanPendingBlobCount: Histogram;
|
|
55
|
+
private fishermanIncludedBlobCount: Histogram;
|
|
56
|
+
private fishermanBlockBlobsFull: UpDownCounter;
|
|
53
57
|
private fishermanCalculatedPriorityFee: Histogram;
|
|
54
58
|
private fishermanPriorityFeeDelta: Histogram;
|
|
55
59
|
private fishermanEstimatedCost: Histogram;
|
|
@@ -67,7 +71,9 @@ export class SequencerMetrics {
|
|
|
67
71
|
this.meter = client.getMeter(name);
|
|
68
72
|
this.tracer = client.getTracer(name);
|
|
69
73
|
|
|
70
|
-
this.blockCounter = this.meter
|
|
74
|
+
this.blockCounter = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_BLOCK_COUNT, {
|
|
75
|
+
[Attributes.STATUS]: ['failed', 'built'],
|
|
76
|
+
});
|
|
71
77
|
|
|
72
78
|
this.blockBuildDuration = this.meter.createHistogram(Metrics.SEQUENCER_BLOCK_BUILD_DURATION);
|
|
73
79
|
|
|
@@ -77,23 +83,15 @@ export class SequencerMetrics {
|
|
|
77
83
|
|
|
78
84
|
this.checkpointAttestationDelay = this.meter.createHistogram(Metrics.SEQUENCER_CHECKPOINT_ATTESTATION_DELAY);
|
|
79
85
|
|
|
80
|
-
// Init gauges and counters
|
|
81
|
-
this.blockCounter.add(0, {
|
|
82
|
-
[Attributes.STATUS]: 'failed',
|
|
83
|
-
});
|
|
84
|
-
this.blockCounter.add(0, {
|
|
85
|
-
[Attributes.STATUS]: 'built',
|
|
86
|
-
});
|
|
87
|
-
|
|
88
86
|
this.rewards = this.meter.createGauge(Metrics.SEQUENCER_CURRENT_BLOCK_REWARDS);
|
|
89
87
|
|
|
90
|
-
this.slots = this.meter
|
|
88
|
+
this.slots = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_SLOT_COUNT);
|
|
91
89
|
|
|
92
90
|
/**
|
|
93
91
|
* NOTE: we do not track missed slots as a separate metric. That would be difficult to determine
|
|
94
92
|
* Instead, use a computed metric, `slots - filledSlots` to get the number of slots a sequencer has missed.
|
|
95
93
|
*/
|
|
96
|
-
this.filledSlots = this.meter
|
|
94
|
+
this.filledSlots = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_FILLED_SLOT_COUNT);
|
|
97
95
|
|
|
98
96
|
this.timeToCollectAttestations = this.meter.createGauge(Metrics.SEQUENCER_COLLECT_ATTESTATIONS_DURATION);
|
|
99
97
|
|
|
@@ -103,20 +101,41 @@ export class SequencerMetrics {
|
|
|
103
101
|
|
|
104
102
|
this.collectedAttestions = this.meter.createGauge(Metrics.SEQUENCER_COLLECTED_ATTESTATIONS_COUNT);
|
|
105
103
|
|
|
106
|
-
this.blockProposalFailed =
|
|
104
|
+
this.blockProposalFailed = createUpDownCounterWithDefault(
|
|
105
|
+
this.meter,
|
|
106
|
+
Metrics.SEQUENCER_BLOCK_PROPOSAL_FAILED_COUNT,
|
|
107
|
+
);
|
|
107
108
|
|
|
108
|
-
this.blockProposalSuccess =
|
|
109
|
+
this.blockProposalSuccess = createUpDownCounterWithDefault(
|
|
110
|
+
this.meter,
|
|
111
|
+
Metrics.SEQUENCER_BLOCK_PROPOSAL_SUCCESS_COUNT,
|
|
112
|
+
);
|
|
109
113
|
|
|
110
|
-
this.checkpointSuccess = this.meter
|
|
114
|
+
this.checkpointSuccess = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_CHECKPOINT_SUCCESS_COUNT);
|
|
111
115
|
|
|
112
|
-
this.blockProposalPrecheckFailed =
|
|
116
|
+
this.blockProposalPrecheckFailed = createUpDownCounterWithDefault(
|
|
117
|
+
this.meter,
|
|
113
118
|
Metrics.SEQUENCER_BLOCK_PROPOSAL_PRECHECK_FAILED_COUNT,
|
|
119
|
+
{
|
|
120
|
+
[Attributes.ERROR_TYPE]: [
|
|
121
|
+
'slot_already_taken',
|
|
122
|
+
'rollup_contract_check_failed',
|
|
123
|
+
'slot_mismatch',
|
|
124
|
+
'block_number_mismatch',
|
|
125
|
+
],
|
|
126
|
+
},
|
|
114
127
|
);
|
|
115
128
|
|
|
116
|
-
this.slashingAttempts = this.meter
|
|
129
|
+
this.slashingAttempts = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_SLASHING_ATTEMPTS_COUNT);
|
|
117
130
|
|
|
118
131
|
// Fisherman fee analysis metrics
|
|
119
|
-
this.fishermanWouldBeIncluded =
|
|
132
|
+
this.fishermanWouldBeIncluded = createUpDownCounterWithDefault(
|
|
133
|
+
this.meter,
|
|
134
|
+
Metrics.FISHERMAN_FEE_ANALYSIS_WOULD_BE_INCLUDED,
|
|
135
|
+
{
|
|
136
|
+
[Attributes.OK]: [true, false],
|
|
137
|
+
},
|
|
138
|
+
);
|
|
120
139
|
|
|
121
140
|
this.fishermanTimeBeforeBlock = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_TIME_BEFORE_BLOCK);
|
|
122
141
|
|
|
@@ -145,6 +164,18 @@ export class SequencerMetrics {
|
|
|
145
164
|
this.fishermanMinedBlobTxTotalCost = this.meter.createHistogram(
|
|
146
165
|
Metrics.FISHERMAN_FEE_ANALYSIS_MINED_BLOB_TX_TOTAL_COST,
|
|
147
166
|
);
|
|
167
|
+
|
|
168
|
+
this.fishermanPendingBlobCount = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_PENDING_BLOB_COUNT);
|
|
169
|
+
|
|
170
|
+
this.fishermanIncludedBlobCount = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_INCLUDED_BLOB_COUNT);
|
|
171
|
+
|
|
172
|
+
this.fishermanBlockBlobsFull = createUpDownCounterWithDefault(
|
|
173
|
+
this.meter,
|
|
174
|
+
Metrics.FISHERMAN_FEE_ANALYSIS_BLOCK_BLOBS_FULL,
|
|
175
|
+
{
|
|
176
|
+
[Attributes.OK]: [true, false],
|
|
177
|
+
},
|
|
178
|
+
);
|
|
148
179
|
}
|
|
149
180
|
|
|
150
181
|
public recordRequiredAttestations(requiredAttestationsCount: number, allowanceMs: number) {
|
|
@@ -231,7 +262,9 @@ export class SequencerMetrics {
|
|
|
231
262
|
this.blockProposalSuccess.add(1);
|
|
232
263
|
}
|
|
233
264
|
|
|
234
|
-
recordBlockProposalPrecheckFailed(
|
|
265
|
+
recordBlockProposalPrecheckFailed(
|
|
266
|
+
checkType: 'slot_already_taken' | 'rollup_contract_check_failed' | 'slot_mismatch' | 'block_number_mismatch',
|
|
267
|
+
) {
|
|
235
268
|
this.blockProposalPrecheckFailed.add(1, {
|
|
236
269
|
[Attributes.ERROR_TYPE]: checkType,
|
|
237
270
|
});
|
|
@@ -263,10 +296,12 @@ export class SequencerMetrics {
|
|
|
263
296
|
|
|
264
297
|
// Record pending block snapshot data (once per strategy for comparison)
|
|
265
298
|
this.fishermanPendingBlobTxCount.record(analysis.pendingSnapshot.pendingBlobTxCount, strategyAttributes);
|
|
299
|
+
this.fishermanPendingBlobCount.record(analysis.pendingSnapshot.pendingBlobCount, strategyAttributes);
|
|
266
300
|
|
|
267
301
|
// Record mined block data if available
|
|
268
302
|
if (analysis.minedBlock) {
|
|
269
303
|
this.fishermanIncludedBlobTxCount.record(analysis.minedBlock.includedBlobTxCount, strategyAttributes);
|
|
304
|
+
this.fishermanIncludedBlobCount.record(analysis.minedBlock.includedBlobCount, strategyAttributes);
|
|
270
305
|
|
|
271
306
|
// Record actual fees from blob transactions in the mined block
|
|
272
307
|
for (const blobTx of analysis.minedBlock.includedBlobTxs) {
|
|
@@ -300,6 +335,13 @@ export class SequencerMetrics {
|
|
|
300
335
|
if (analysis.analysis) {
|
|
301
336
|
this.fishermanTimeBeforeBlock.record(Math.ceil(analysis.analysis.timeBeforeBlockMs), strategyAttributes);
|
|
302
337
|
|
|
338
|
+
// Record whether the block reached 100% blob capacity
|
|
339
|
+
if (analysis.analysis.blockBlobsFull) {
|
|
340
|
+
this.fishermanBlockBlobsFull.add(1, { ...strategyAttributes, [Attributes.OK]: true });
|
|
341
|
+
} else {
|
|
342
|
+
this.fishermanBlockBlobsFull.add(1, { ...strategyAttributes, [Attributes.OK]: false });
|
|
343
|
+
}
|
|
344
|
+
|
|
303
345
|
// Record strategy-specific inclusion result
|
|
304
346
|
if (strategyResult.wouldBeIncluded !== undefined) {
|
|
305
347
|
if (strategyResult.wouldBeIncluded) {
|
|
@@ -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 {
|
|
15
|
+
import type { L2Block, 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 {
|
|
@@ -57,8 +57,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
57
57
|
private state = SequencerState.STOPPED;
|
|
58
58
|
private metrics: SequencerMetrics;
|
|
59
59
|
|
|
60
|
-
/** The last slot for which we attempted to
|
|
61
|
-
private
|
|
60
|
+
/** The last slot for which we attempted to perform our voting duties with degraded block production */
|
|
61
|
+
private lastSlotForFallbackVote: SlotNumber | undefined;
|
|
62
|
+
|
|
63
|
+
/** The last slot for which we logged "no committee" warning, to avoid spam */
|
|
64
|
+
private lastSlotForNoCommitteeWarning: SlotNumber | undefined;
|
|
62
65
|
|
|
63
66
|
/** The last slot for which we triggered a checkpoint proposal job, to prevent duplicate attempts. */
|
|
64
67
|
private lastSlotForCheckpointProposalJob: SlotNumber | undefined;
|
|
@@ -202,7 +205,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
202
205
|
const { slot, ts, now, epoch } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
203
206
|
|
|
204
207
|
// Check if we are synced and it's our slot, grab a publisher, check previous block invalidation, etc
|
|
205
|
-
const checkpointProposalJob = await this.prepareCheckpointProposal(slot, ts, now);
|
|
208
|
+
const checkpointProposalJob = await this.prepareCheckpointProposal(epoch, slot, ts, now);
|
|
206
209
|
if (!checkpointProposalJob) {
|
|
207
210
|
return;
|
|
208
211
|
}
|
|
@@ -234,6 +237,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
234
237
|
*/
|
|
235
238
|
@trackSpan('Sequencer.prepareCheckpointProposal')
|
|
236
239
|
private async prepareCheckpointProposal(
|
|
240
|
+
epoch: EpochNumber,
|
|
237
241
|
slot: SlotNumber,
|
|
238
242
|
ts: bigint,
|
|
239
243
|
now: bigint,
|
|
@@ -263,6 +267,25 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
263
267
|
return undefined;
|
|
264
268
|
}
|
|
265
269
|
|
|
270
|
+
// If escape hatch is open for this epoch, do not start checkpoint proposal work and do not attempt invalidations.
|
|
271
|
+
// Still perform governance/slashing voting (as proposer) once per slot.
|
|
272
|
+
const isEscapeHatchOpen = await this.epochCache.isEscapeHatchOpen(epoch);
|
|
273
|
+
|
|
274
|
+
if (isEscapeHatchOpen) {
|
|
275
|
+
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
276
|
+
const [canPropose, proposer] = await this.checkCanPropose(slot);
|
|
277
|
+
if (canPropose) {
|
|
278
|
+
await this.tryVoteWhenEscapeHatchOpen({ slot, proposer });
|
|
279
|
+
} else {
|
|
280
|
+
this.log.trace(`Escape hatch open but we are not proposer, skipping vote-only actions`, {
|
|
281
|
+
slot,
|
|
282
|
+
epoch,
|
|
283
|
+
proposer,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
return undefined;
|
|
287
|
+
}
|
|
288
|
+
|
|
266
289
|
// Next checkpoint follows from the last synced one
|
|
267
290
|
const checkpointNumber = CheckpointNumber(syncedTo.checkpointNumber + 1);
|
|
268
291
|
|
|
@@ -353,10 +376,12 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
353
376
|
}
|
|
354
377
|
|
|
355
378
|
this.lastSlotForCheckpointProposalJob = slot;
|
|
379
|
+
await this.p2pClient.prepareForSlot(slot);
|
|
356
380
|
this.log.info(`Preparing checkpoint proposal ${checkpointNumber} at slot ${slot}`, { ...logCtx, proposer });
|
|
357
381
|
|
|
358
382
|
// Create and return the checkpoint proposal job
|
|
359
383
|
return this.createCheckpointProposalJob(
|
|
384
|
+
epoch,
|
|
360
385
|
slot,
|
|
361
386
|
checkpointNumber,
|
|
362
387
|
syncedTo.blockNumber,
|
|
@@ -368,6 +393,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
368
393
|
}
|
|
369
394
|
|
|
370
395
|
protected createCheckpointProposalJob(
|
|
396
|
+
epoch: EpochNumber,
|
|
371
397
|
slot: SlotNumber,
|
|
372
398
|
checkpointNumber: CheckpointNumber,
|
|
373
399
|
syncedToBlockNumber: BlockNumber,
|
|
@@ -377,6 +403,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
377
403
|
invalidateCheckpoint: InvalidateCheckpointRequest | undefined,
|
|
378
404
|
): CheckpointProposalJob {
|
|
379
405
|
return new CheckpointProposalJob(
|
|
406
|
+
epoch,
|
|
380
407
|
slot,
|
|
381
408
|
checkpointNumber,
|
|
382
409
|
syncedToBlockNumber,
|
|
@@ -389,6 +416,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
389
416
|
this.p2pClient,
|
|
390
417
|
this.worldState,
|
|
391
418
|
this.l1ToL2MessageSource,
|
|
419
|
+
this.l2BlockSource,
|
|
392
420
|
this.checkpointsBuilder,
|
|
393
421
|
this.l2BlockSource,
|
|
394
422
|
this.l1Constants,
|
|
@@ -400,8 +428,8 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
400
428
|
this.metrics,
|
|
401
429
|
this,
|
|
402
430
|
this.setState.bind(this),
|
|
403
|
-
this.log,
|
|
404
431
|
this.tracer,
|
|
432
|
+
this.log.getBindings(),
|
|
405
433
|
);
|
|
406
434
|
}
|
|
407
435
|
|
|
@@ -505,7 +533,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
505
533
|
};
|
|
506
534
|
}
|
|
507
535
|
|
|
508
|
-
const block = await this.l2BlockSource.
|
|
536
|
+
const block = await this.l2BlockSource.getL2Block(blockNumber);
|
|
509
537
|
if (!block) {
|
|
510
538
|
// this shouldn't really happen because a moment ago we checked that all components were in sync
|
|
511
539
|
this.log.error(`Failed to get L2 block ${blockNumber} from the archiver with all components in sync`);
|
|
@@ -533,7 +561,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
533
561
|
proposer = await this.epochCache.getProposerAttesterAddressInSlot(slot);
|
|
534
562
|
} catch (e) {
|
|
535
563
|
if (e instanceof NoCommitteeError) {
|
|
536
|
-
this.
|
|
564
|
+
if (this.lastSlotForNoCommitteeWarning !== slot) {
|
|
565
|
+
this.lastSlotForNoCommitteeWarning = slot;
|
|
566
|
+
this.log.warn(`Cannot propose at next L2 slot ${slot} since the committee does not exist on L1`);
|
|
567
|
+
}
|
|
537
568
|
return [false, undefined];
|
|
538
569
|
}
|
|
539
570
|
this.log.error(`Error getting proposer for slot ${slot}`, e);
|
|
@@ -569,7 +600,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
569
600
|
const { slot } = args;
|
|
570
601
|
|
|
571
602
|
// Prevent duplicate attempts in the same slot
|
|
572
|
-
if (this.
|
|
603
|
+
if (this.lastSlotForFallbackVote === slot) {
|
|
573
604
|
this.log.trace(`Already attempted to vote in slot ${slot} (skipping)`);
|
|
574
605
|
return;
|
|
575
606
|
}
|
|
@@ -601,7 +632,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
601
632
|
}
|
|
602
633
|
|
|
603
634
|
// Mark this slot as attempted
|
|
604
|
-
this.
|
|
635
|
+
this.lastSlotForFallbackVote = slot;
|
|
605
636
|
|
|
606
637
|
// Get a publisher for voting
|
|
607
638
|
const { attestorAddress, publisher } = await this.publisherFactory.create(proposer);
|
|
@@ -636,7 +667,55 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
636
667
|
}
|
|
637
668
|
|
|
638
669
|
/**
|
|
639
|
-
*
|
|
670
|
+
* Tries to vote on slashing actions and governance proposals when escape hatch is open.
|
|
671
|
+
* This allows the sequencer to participate in voting without performing checkpoint proposal work.
|
|
672
|
+
*/
|
|
673
|
+
@trackSpan('Sequencer.tryVoteWhenEscapeHatchOpen', ({ slot }) => ({ [Attributes.SLOT_NUMBER]: slot }))
|
|
674
|
+
protected async tryVoteWhenEscapeHatchOpen(args: {
|
|
675
|
+
slot: SlotNumber;
|
|
676
|
+
proposer: EthAddress | undefined;
|
|
677
|
+
}): Promise<void> {
|
|
678
|
+
const { slot, proposer } = args;
|
|
679
|
+
|
|
680
|
+
// Prevent duplicate attempts in the same slot
|
|
681
|
+
if (this.lastSlotForFallbackVote === slot) {
|
|
682
|
+
this.log.trace(`Already attempted to vote in slot ${slot} (escape hatch open, skipping)`);
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Mark this slot as attempted
|
|
687
|
+
this.lastSlotForFallbackVote = slot;
|
|
688
|
+
|
|
689
|
+
const { attestorAddress, publisher } = await this.publisherFactory.create(proposer);
|
|
690
|
+
|
|
691
|
+
this.log.debug(`Escape hatch open for slot ${slot}, attempting vote-only actions`, { slot, attestorAddress });
|
|
692
|
+
|
|
693
|
+
const voter = new CheckpointVoter(
|
|
694
|
+
slot,
|
|
695
|
+
publisher,
|
|
696
|
+
attestorAddress,
|
|
697
|
+
this.validatorClient,
|
|
698
|
+
this.slasherClient,
|
|
699
|
+
this.l1Constants,
|
|
700
|
+
this.config,
|
|
701
|
+
this.metrics,
|
|
702
|
+
this.log,
|
|
703
|
+
);
|
|
704
|
+
|
|
705
|
+
const votesPromises = voter.enqueueVotes();
|
|
706
|
+
const votes = await Promise.all(votesPromises);
|
|
707
|
+
|
|
708
|
+
if (votes.every(p => !p)) {
|
|
709
|
+
this.log.debug(`No votes to enqueue for slot ${slot} (escape hatch open)`);
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
this.log.info(`Voting in slot ${slot} (escape hatch open)`, { slot });
|
|
714
|
+
await publisher.sendRequests();
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* Considers invalidating a block if the pending chain is invalid. Depends on how long the invalid block
|
|
640
719
|
* has been there without being invalidated and whether the sequencer is in the committee or not. We always
|
|
641
720
|
* have the proposer try to invalidate, but if they fail, the sequencers in the committee are expected to try,
|
|
642
721
|
* and if they fail, any sequencer will try as well.
|
|
@@ -798,7 +877,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
798
877
|
}
|
|
799
878
|
|
|
800
879
|
type SequencerSyncCheckResult = {
|
|
801
|
-
block?:
|
|
880
|
+
block?: L2Block;
|
|
802
881
|
checkpointNumber: CheckpointNumber;
|
|
803
882
|
blockNumber: BlockNumber;
|
|
804
883
|
archive: Fr;
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { createLogger } from '@aztec/aztec.js/log';
|
|
2
|
+
import {
|
|
3
|
+
CHECKPOINT_ASSEMBLE_TIME,
|
|
4
|
+
CHECKPOINT_INITIALIZATION_TIME,
|
|
5
|
+
DEFAULT_P2P_PROPAGATION_TIME,
|
|
6
|
+
MIN_EXECUTION_TIME,
|
|
7
|
+
} from '@aztec/stdlib/timetable';
|
|
2
8
|
|
|
3
|
-
import { DEFAULT_ATTESTATION_PROPAGATION_TIME as DEFAULT_P2P_PROPAGATION_TIME } from '../config.js';
|
|
4
9
|
import { SequencerTooSlowError } from './errors.js';
|
|
5
10
|
import type { SequencerMetrics } from './metrics.js';
|
|
6
11
|
import { SequencerState } from './utils.js';
|
|
7
12
|
|
|
8
|
-
export const MIN_EXECUTION_TIME = 2;
|
|
9
|
-
export const CHECKPOINT_INITIALIZATION_TIME = 1;
|
|
10
|
-
export const CHECKPOINT_ASSEMBLE_TIME = 1;
|
|
11
|
-
|
|
12
13
|
export class SequencerTimetable {
|
|
13
14
|
/**
|
|
14
15
|
* How late into the slot can we be to start working. Computed as the total time needed for assembling and publishing a block,
|
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
import { type BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
2
2
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
3
|
-
import {
|
|
4
|
-
import type { FunctionsOf } from '@aztec/foundation/types';
|
|
5
|
-
import { L2BlockNew } from '@aztec/stdlib/block';
|
|
3
|
+
import { L2Block } from '@aztec/stdlib/block';
|
|
6
4
|
import { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
7
5
|
import { Gas } from '@aztec/stdlib/gas';
|
|
8
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
FullNodeBlockBuilderConfig,
|
|
8
|
+
ICheckpointBlockBuilder,
|
|
9
|
+
ICheckpointsBuilder,
|
|
10
|
+
MerkleTreeWriteOperations,
|
|
11
|
+
PublicProcessorLimits,
|
|
12
|
+
} from '@aztec/stdlib/interfaces/server';
|
|
9
13
|
import { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
10
14
|
import { makeAppendOnlyTreeSnapshot } from '@aztec/stdlib/testing';
|
|
11
15
|
import type { CheckpointGlobalVariables, Tx } from '@aztec/stdlib/tx';
|
|
12
|
-
import type {
|
|
13
|
-
BuildBlockInCheckpointResult,
|
|
14
|
-
CheckpointBuilder,
|
|
15
|
-
FullNodeCheckpointsBuilder,
|
|
16
|
-
} from '@aztec/validator-client';
|
|
16
|
+
import type { BuildBlockInCheckpointResult } from '@aztec/validator-client';
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* A fake CheckpointBuilder for testing that implements the same interface as the real one.
|
|
20
20
|
* Can be seeded with blocks to return sequentially on each `buildBlock` call.
|
|
21
21
|
*/
|
|
22
|
-
export class MockCheckpointBuilder implements
|
|
23
|
-
private blocks:
|
|
24
|
-
private builtBlocks:
|
|
22
|
+
export class MockCheckpointBuilder implements ICheckpointBlockBuilder {
|
|
23
|
+
private blocks: L2Block[] = [];
|
|
24
|
+
private builtBlocks: L2Block[] = [];
|
|
25
25
|
private usedTxsPerBlock: Tx[][] = [];
|
|
26
26
|
private blockIndex = 0;
|
|
27
27
|
|
|
28
28
|
/** Optional function to dynamically provide the block (alternative to seedBlocks) */
|
|
29
|
-
private blockProvider: (() =>
|
|
29
|
+
private blockProvider: (() => L2Block) | undefined = undefined;
|
|
30
30
|
|
|
31
31
|
/** Track calls for assertions */
|
|
32
32
|
public buildBlockCalls: Array<{
|
|
@@ -34,6 +34,8 @@ export class MockCheckpointBuilder implements FunctionsOf<CheckpointBuilder> {
|
|
|
34
34
|
timestamp: bigint;
|
|
35
35
|
opts: PublicProcessorLimits;
|
|
36
36
|
}> = [];
|
|
37
|
+
/** Track all consumed transaction hashes across buildBlock calls */
|
|
38
|
+
public consumedTxHashes: Set<string> = new Set();
|
|
37
39
|
public completeCheckpointCalled = false;
|
|
38
40
|
public getCheckpointCalled = false;
|
|
39
41
|
|
|
@@ -46,7 +48,7 @@ export class MockCheckpointBuilder implements FunctionsOf<CheckpointBuilder> {
|
|
|
46
48
|
) {}
|
|
47
49
|
|
|
48
50
|
/** Seed the builder with blocks to return on successive buildBlock calls */
|
|
49
|
-
seedBlocks(blocks:
|
|
51
|
+
seedBlocks(blocks: L2Block[], usedTxsPerBlock?: Tx[][]): this {
|
|
50
52
|
this.blocks = blocks;
|
|
51
53
|
this.usedTxsPerBlock = usedTxsPerBlock ?? blocks.map(() => []);
|
|
52
54
|
this.blockIndex = 0;
|
|
@@ -58,7 +60,7 @@ export class MockCheckpointBuilder implements FunctionsOf<CheckpointBuilder> {
|
|
|
58
60
|
* Set a function that provides blocks dynamically.
|
|
59
61
|
* Useful for tests where the block is determined at call time (e.g., sequencer tests).
|
|
60
62
|
*/
|
|
61
|
-
setBlockProvider(provider: () =>
|
|
63
|
+
setBlockProvider(provider: () => L2Block): this {
|
|
62
64
|
this.blockProvider = provider;
|
|
63
65
|
this.blocks = [];
|
|
64
66
|
return this;
|
|
@@ -68,8 +70,8 @@ export class MockCheckpointBuilder implements FunctionsOf<CheckpointBuilder> {
|
|
|
68
70
|
return this.constants;
|
|
69
71
|
}
|
|
70
72
|
|
|
71
|
-
buildBlock(
|
|
72
|
-
|
|
73
|
+
async buildBlock(
|
|
74
|
+
pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
|
|
73
75
|
blockNumber: BlockNumber,
|
|
74
76
|
timestamp: bigint,
|
|
75
77
|
opts: PublicProcessorLimits,
|
|
@@ -77,10 +79,10 @@ export class MockCheckpointBuilder implements FunctionsOf<CheckpointBuilder> {
|
|
|
77
79
|
this.buildBlockCalls.push({ blockNumber, timestamp, opts });
|
|
78
80
|
|
|
79
81
|
if (this.errorOnBuild) {
|
|
80
|
-
|
|
82
|
+
throw this.errorOnBuild;
|
|
81
83
|
}
|
|
82
84
|
|
|
83
|
-
let block:
|
|
85
|
+
let block: L2Block;
|
|
84
86
|
let usedTxs: Tx[];
|
|
85
87
|
|
|
86
88
|
if (this.blockProvider) {
|
|
@@ -96,15 +98,28 @@ export class MockCheckpointBuilder implements FunctionsOf<CheckpointBuilder> {
|
|
|
96
98
|
this.builtBlocks.push(block);
|
|
97
99
|
}
|
|
98
100
|
|
|
99
|
-
|
|
101
|
+
// Check that no pending tx has already been consumed
|
|
102
|
+
for await (const tx of pendingTxs) {
|
|
103
|
+
const hash = tx.getTxHash().toString();
|
|
104
|
+
if (this.consumedTxHashes.has(hash)) {
|
|
105
|
+
throw new Error(`Transaction ${hash} was already consumed in a previous block`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Add used txs to consumed set
|
|
110
|
+
for (const tx of usedTxs) {
|
|
111
|
+
this.consumedTxHashes.add(tx.getTxHash().toString());
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
100
115
|
block,
|
|
101
116
|
publicGas: Gas.empty(),
|
|
102
117
|
publicProcessorDuration: 0,
|
|
103
118
|
numTxs: block?.body?.txEffects?.length ?? usedTxs.length,
|
|
104
|
-
blockBuildingTimer: new Timer(),
|
|
105
119
|
usedTxs,
|
|
106
120
|
failedTxs: [],
|
|
107
|
-
|
|
121
|
+
usedTxBlobFields: block?.body?.txEffects?.reduce((sum, tx) => sum + tx.getNumBlobFields(), 0) ?? 0,
|
|
122
|
+
};
|
|
108
123
|
}
|
|
109
124
|
|
|
110
125
|
completeCheckpoint(): Promise<Checkpoint> {
|
|
@@ -146,7 +161,7 @@ export class MockCheckpointBuilder implements FunctionsOf<CheckpointBuilder> {
|
|
|
146
161
|
* Creates a CheckpointHeader from a block's header for testing.
|
|
147
162
|
* This is a simplified version that creates a minimal CheckpointHeader.
|
|
148
163
|
*/
|
|
149
|
-
private createCheckpointHeader(block:
|
|
164
|
+
private createCheckpointHeader(block: L2Block): CheckpointHeader {
|
|
150
165
|
const header = block.header;
|
|
151
166
|
const gv = header.globalVariables;
|
|
152
167
|
return CheckpointHeader.empty({
|
|
@@ -168,6 +183,7 @@ export class MockCheckpointBuilder implements FunctionsOf<CheckpointBuilder> {
|
|
|
168
183
|
this.usedTxsPerBlock = [];
|
|
169
184
|
this.blockIndex = 0;
|
|
170
185
|
this.buildBlockCalls = [];
|
|
186
|
+
this.consumedTxHashes.clear();
|
|
171
187
|
this.completeCheckpointCalled = false;
|
|
172
188
|
this.getCheckpointCalled = false;
|
|
173
189
|
this.errorOnBuild = undefined;
|
|
@@ -180,7 +196,7 @@ export class MockCheckpointBuilder implements FunctionsOf<CheckpointBuilder> {
|
|
|
180
196
|
* as FullNodeCheckpointsBuilder. Returns MockCheckpointBuilder instances.
|
|
181
197
|
* Does NOT use jest mocks - this is a proper test double.
|
|
182
198
|
*/
|
|
183
|
-
export class MockCheckpointsBuilder implements
|
|
199
|
+
export class MockCheckpointsBuilder implements ICheckpointsBuilder {
|
|
184
200
|
private checkpointBuilder: MockCheckpointBuilder | undefined;
|
|
185
201
|
|
|
186
202
|
/** Track calls for assertions */
|
|
@@ -188,12 +204,14 @@ export class MockCheckpointsBuilder implements FunctionsOf<FullNodeCheckpointsBu
|
|
|
188
204
|
checkpointNumber: CheckpointNumber;
|
|
189
205
|
constants: CheckpointGlobalVariables;
|
|
190
206
|
l1ToL2Messages: Fr[];
|
|
207
|
+
previousCheckpointOutHashes: Fr[];
|
|
191
208
|
}> = [];
|
|
192
209
|
public openCheckpointCalls: Array<{
|
|
193
210
|
checkpointNumber: CheckpointNumber;
|
|
194
211
|
constants: CheckpointGlobalVariables;
|
|
195
212
|
l1ToL2Messages: Fr[];
|
|
196
|
-
|
|
213
|
+
previousCheckpointOutHashes: Fr[];
|
|
214
|
+
existingBlocks: L2Block[];
|
|
197
215
|
}> = [];
|
|
198
216
|
public updateConfigCalls: Array<Partial<FullNodeBlockBuilderConfig>> = [];
|
|
199
217
|
|
|
@@ -240,33 +258,45 @@ export class MockCheckpointsBuilder implements FunctionsOf<FullNodeCheckpointsBu
|
|
|
240
258
|
checkpointNumber: CheckpointNumber,
|
|
241
259
|
constants: CheckpointGlobalVariables,
|
|
242
260
|
l1ToL2Messages: Fr[],
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
261
|
+
previousCheckpointOutHashes: Fr[],
|
|
262
|
+
_fork: MerkleTreeWriteOperations,
|
|
263
|
+
): Promise<ICheckpointBlockBuilder> {
|
|
264
|
+
this.startCheckpointCalls.push({ checkpointNumber, constants, l1ToL2Messages, previousCheckpointOutHashes });
|
|
246
265
|
|
|
247
266
|
if (!this.checkpointBuilder) {
|
|
248
267
|
// Auto-create a builder if none was set
|
|
249
268
|
this.checkpointBuilder = new MockCheckpointBuilder(constants, checkpointNumber);
|
|
250
269
|
}
|
|
251
270
|
|
|
252
|
-
return Promise.resolve(this.checkpointBuilder
|
|
271
|
+
return Promise.resolve(this.checkpointBuilder);
|
|
253
272
|
}
|
|
254
273
|
|
|
255
274
|
openCheckpoint(
|
|
256
275
|
checkpointNumber: CheckpointNumber,
|
|
257
276
|
constants: CheckpointGlobalVariables,
|
|
258
277
|
l1ToL2Messages: Fr[],
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
278
|
+
previousCheckpointOutHashes: Fr[],
|
|
279
|
+
_fork: MerkleTreeWriteOperations,
|
|
280
|
+
existingBlocks: L2Block[] = [],
|
|
281
|
+
): Promise<ICheckpointBlockBuilder> {
|
|
282
|
+
this.openCheckpointCalls.push({
|
|
283
|
+
checkpointNumber,
|
|
284
|
+
constants,
|
|
285
|
+
l1ToL2Messages,
|
|
286
|
+
previousCheckpointOutHashes,
|
|
287
|
+
existingBlocks,
|
|
288
|
+
});
|
|
263
289
|
|
|
264
290
|
if (!this.checkpointBuilder) {
|
|
265
291
|
// Auto-create a builder if none was set
|
|
266
292
|
this.checkpointBuilder = new MockCheckpointBuilder(constants, checkpointNumber);
|
|
267
293
|
}
|
|
268
294
|
|
|
269
|
-
return Promise.resolve(this.checkpointBuilder
|
|
295
|
+
return Promise.resolve(this.checkpointBuilder);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
getFork(_blockNumber: BlockNumber): Promise<MerkleTreeWriteOperations> {
|
|
299
|
+
throw new Error('MockCheckpointsBuilder.getFork not implemented');
|
|
270
300
|
}
|
|
271
301
|
|
|
272
302
|
/** Reset for reuse in another test */
|