@aztec/sequencer-client 0.0.1-commit.e588bc7e5 → 0.0.1-commit.e5a3663dd
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.d.ts +3 -1
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +3 -4
- package/dest/config.d.ts +2 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +16 -14
- package/dest/global_variable_builder/fee_predictor.d.ts +37 -0
- package/dest/global_variable_builder/fee_predictor.d.ts.map +1 -0
- package/dest/global_variable_builder/fee_predictor.js +128 -0
- package/dest/global_variable_builder/fee_provider.d.ts +21 -0
- package/dest/global_variable_builder/fee_provider.d.ts.map +1 -0
- package/dest/global_variable_builder/fee_provider.js +58 -0
- package/dest/global_variable_builder/global_builder.d.ts +4 -9
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +3 -41
- package/dest/global_variable_builder/index.d.ts +3 -1
- package/dest/global_variable_builder/index.d.ts.map +1 -1
- package/dest/global_variable_builder/index.js +2 -0
- package/dest/publisher/config.d.ts +1 -1
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +2 -2
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +3 -4
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.d.ts +1 -3
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +0 -1
- package/dest/publisher/sequencer-publisher.d.ts +16 -35
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +62 -94
- package/dest/sequencer/chain_state_overrides.d.ts +25 -0
- package/dest/sequencer/chain_state_overrides.d.ts.map +1 -0
- package/dest/sequencer/chain_state_overrides.js +39 -0
- package/dest/sequencer/checkpoint_proposal_job.d.ts +33 -22
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +479 -170
- package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts +34 -0
- package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_proposal_job_metrics.js +72 -0
- package/dest/sequencer/events.d.ts +6 -1
- package/dest/sequencer/events.d.ts.map +1 -1
- package/dest/sequencer/metrics.d.ts +9 -10
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +34 -20
- package/dest/sequencer/sequencer.d.ts +16 -5
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +63 -30
- package/dest/sequencer/timetable.d.ts +14 -1
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +45 -36
- package/dest/test/utils.d.ts +1 -1
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +7 -6
- package/package.json +27 -27
- package/src/client/sequencer-client.ts +5 -7
- package/src/config.ts +15 -11
- package/src/global_variable_builder/README.md +44 -0
- package/src/global_variable_builder/fee_predictor.ts +172 -0
- package/src/global_variable_builder/fee_provider.ts +75 -0
- package/src/global_variable_builder/global_builder.ts +7 -54
- package/src/global_variable_builder/index.ts +2 -0
- package/src/publisher/config.ts +6 -4
- package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +3 -1
- package/src/publisher/sequencer-publisher-factory.ts +0 -3
- package/src/publisher/sequencer-publisher.ts +108 -156
- package/src/sequencer/README.md +82 -13
- package/src/sequencer/chain_state_overrides.ts +87 -0
- package/src/sequencer/checkpoint_proposal_job.ts +572 -218
- package/src/sequencer/checkpoint_proposal_job_metrics.ts +128 -0
- package/src/sequencer/events.ts +5 -0
- package/src/sequencer/metrics.ts +43 -24
- package/src/sequencer/sequencer.ts +81 -35
- package/src/sequencer/timetable.ts +57 -45
- package/src/test/utils.ts +28 -10
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Attributes,
|
|
3
|
+
type Gauge,
|
|
4
|
+
type Histogram,
|
|
5
|
+
type Meter,
|
|
6
|
+
Metrics,
|
|
7
|
+
type TelemetryClient,
|
|
8
|
+
} from '@aztec/telemetry-client';
|
|
9
|
+
|
|
10
|
+
type CheckpointProposalJobInstruments = {
|
|
11
|
+
checkpointAttestationDelay: Histogram;
|
|
12
|
+
checkpointBuildDuration: Histogram;
|
|
13
|
+
checkpointStartToFirstBlockDuration: Histogram;
|
|
14
|
+
checkpointLastBlockToBroadcastDuration: Histogram;
|
|
15
|
+
pipelinedCheckpointBuildStartOffsetFromSlotBoundary: Histogram;
|
|
16
|
+
checkpointBlockCount: Gauge;
|
|
17
|
+
checkpointTxCount: Gauge;
|
|
18
|
+
checkpointTotalMana: Gauge;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Per-job recording surface used by {@link CheckpointProposalJob}.
|
|
23
|
+
*
|
|
24
|
+
* Ownership split:
|
|
25
|
+
* - {@link CheckpointProposalJobMetrics} owns the OpenTelemetry meter and instruments and should live for the
|
|
26
|
+
* lifetime of the sequencer process.
|
|
27
|
+
* - A recorder owns only mutable timing state for one checkpoint proposal job so overlapping jobs cannot
|
|
28
|
+
* overwrite each other's timing markers.
|
|
29
|
+
*/
|
|
30
|
+
export interface CheckpointProposalJobMetricsRecorder {
|
|
31
|
+
recordCheckpointAttestationDelay(durationMs: number): void;
|
|
32
|
+
recordCheckpointBuild(durationMs: number, blockCount: number, txCount: number, totalMana: number): void;
|
|
33
|
+
recordPipelinedCheckpointBuildStartOffsetFromSlotBoundary(offsetMs: number): void;
|
|
34
|
+
startCheckpointTiming(nowMs: number): void;
|
|
35
|
+
noteCheckpointBlockBuilt(nowMs: number, opts: { isFirstBlock: boolean; isLastBlock: boolean }): void;
|
|
36
|
+
noteCheckpointBroadcast(nowMs: number): void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Concrete per-job recorder.
|
|
41
|
+
*
|
|
42
|
+
* This class should be short-lived: create one recorder per checkpoint proposal job and discard it when the job
|
|
43
|
+
* completes. It intentionally does not create instruments; it only holds the job-local timestamps needed to derive
|
|
44
|
+
* timing metrics safely.
|
|
45
|
+
*/
|
|
46
|
+
class CheckpointProposalJobMetricsRecorderImpl implements CheckpointProposalJobMetricsRecorder {
|
|
47
|
+
private checkpointStartedAt?: number;
|
|
48
|
+
private checkpointLastBlockBuiltAt?: number;
|
|
49
|
+
|
|
50
|
+
constructor(private readonly instruments: CheckpointProposalJobInstruments) {}
|
|
51
|
+
|
|
52
|
+
public recordCheckpointAttestationDelay(durationMs: number) {
|
|
53
|
+
this.instruments.checkpointAttestationDelay.record(Math.ceil(durationMs));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public recordCheckpointBuild(durationMs: number, blockCount: number, txCount: number, totalMana: number) {
|
|
57
|
+
this.instruments.checkpointBuildDuration.record(Math.ceil(durationMs));
|
|
58
|
+
this.instruments.checkpointBlockCount.record(blockCount);
|
|
59
|
+
this.instruments.checkpointTxCount.record(txCount);
|
|
60
|
+
this.instruments.checkpointTotalMana.record(totalMana);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public recordPipelinedCheckpointBuildStartOffsetFromSlotBoundary(offsetMs: number) {
|
|
64
|
+
this.instruments.pipelinedCheckpointBuildStartOffsetFromSlotBoundary.record(Math.ceil(Math.abs(offsetMs)), {
|
|
65
|
+
[Attributes.SLOT_BOUNDARY_SIDE]: offsetMs < 0 ? 'before' : 'after',
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public startCheckpointTiming(nowMs: number) {
|
|
70
|
+
this.checkpointStartedAt = nowMs;
|
|
71
|
+
this.checkpointLastBlockBuiltAt = undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public noteCheckpointBlockBuilt(nowMs: number, opts: { isFirstBlock: boolean; isLastBlock: boolean }) {
|
|
75
|
+
if (opts.isFirstBlock && this.checkpointStartedAt !== undefined) {
|
|
76
|
+
this.instruments.checkpointStartToFirstBlockDuration.record(Math.ceil(nowMs - this.checkpointStartedAt));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (opts.isLastBlock) {
|
|
80
|
+
this.checkpointLastBlockBuiltAt = nowMs;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public noteCheckpointBroadcast(nowMs: number) {
|
|
85
|
+
if (this.checkpointLastBlockBuiltAt !== undefined) {
|
|
86
|
+
this.instruments.checkpointLastBlockToBroadcastDuration.record(
|
|
87
|
+
Math.ceil(nowMs - this.checkpointLastBlockBuiltAt),
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
this.checkpointLastBlockBuiltAt = undefined;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Long-lived owner of checkpoint proposal job telemetry instruments.
|
|
97
|
+
*
|
|
98
|
+
* The sequencer should construct this once and reuse it across jobs. Each call to {@link createRecorder} returns a
|
|
99
|
+
* lightweight recorder with isolated mutable state for a single checkpoint proposal job.
|
|
100
|
+
*/
|
|
101
|
+
export class CheckpointProposalJobMetrics {
|
|
102
|
+
private readonly meter: Meter;
|
|
103
|
+
private readonly instruments: CheckpointProposalJobInstruments;
|
|
104
|
+
|
|
105
|
+
constructor(client: TelemetryClient, name = 'CheckpointProposalJob') {
|
|
106
|
+
this.meter = client.getMeter(name);
|
|
107
|
+
this.instruments = {
|
|
108
|
+
checkpointAttestationDelay: this.meter.createHistogram(Metrics.SEQUENCER_CHECKPOINT_ATTESTATION_DELAY),
|
|
109
|
+
checkpointBuildDuration: this.meter.createHistogram(Metrics.SEQUENCER_CHECKPOINT_BUILD_DURATION),
|
|
110
|
+
checkpointStartToFirstBlockDuration: this.meter.createHistogram(
|
|
111
|
+
Metrics.SEQUENCER_CHECKPOINT_START_TO_FIRST_BLOCK_DURATION,
|
|
112
|
+
),
|
|
113
|
+
checkpointLastBlockToBroadcastDuration: this.meter.createHistogram(
|
|
114
|
+
Metrics.SEQUENCER_CHECKPOINT_LAST_BLOCK_TO_BROADCAST_DURATION,
|
|
115
|
+
),
|
|
116
|
+
pipelinedCheckpointBuildStartOffsetFromSlotBoundary: this.meter.createHistogram(
|
|
117
|
+
Metrics.SEQUENCER_PIPELINED_CHECKPOINT_BUILD_START_OFFSET_FROM_SLOT_BOUNDARY,
|
|
118
|
+
),
|
|
119
|
+
checkpointBlockCount: this.meter.createGauge(Metrics.SEQUENCER_CHECKPOINT_BLOCK_COUNT),
|
|
120
|
+
checkpointTxCount: this.meter.createGauge(Metrics.SEQUENCER_CHECKPOINT_TX_COUNT),
|
|
121
|
+
checkpointTotalMana: this.meter.createGauge(Metrics.SEQUENCER_CHECKPOINT_TOTAL_MANA),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
public createRecorder(): CheckpointProposalJobMetricsRecorder {
|
|
126
|
+
return new CheckpointProposalJobMetricsRecorderImpl(this.instruments);
|
|
127
|
+
}
|
|
128
|
+
}
|
package/src/sequencer/events.ts
CHANGED
|
@@ -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
|
};
|
package/src/sequencer/metrics.ts
CHANGED
|
@@ -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;
|
|
@@ -44,13 +45,9 @@ export class SequencerMetrics {
|
|
|
44
45
|
private checkpointProposalFailed: UpDownCounter;
|
|
45
46
|
private checkpointSuccess: UpDownCounter;
|
|
46
47
|
private slashingAttempts: UpDownCounter;
|
|
47
|
-
private checkpointAttestationDelay: Histogram;
|
|
48
|
-
private checkpointBuildDuration: Histogram;
|
|
49
|
-
private checkpointBlockCount: Gauge;
|
|
50
|
-
private checkpointTxCount: Gauge;
|
|
51
|
-
private checkpointTotalMana: Gauge;
|
|
52
48
|
private pipelineDepth: Gauge;
|
|
53
49
|
private pipelineDiscards: UpDownCounter;
|
|
50
|
+
private pipelineParentCheckpointMismatches: UpDownCounter;
|
|
54
51
|
|
|
55
52
|
// Fisherman fee analysis metrics
|
|
56
53
|
private fishermanWouldBeIncluded: UpDownCounter;
|
|
@@ -68,6 +65,10 @@ export class SequencerMetrics {
|
|
|
68
65
|
private fishermanMinedBlobTxPriorityFee: Histogram;
|
|
69
66
|
private fishermanMinedBlobTxTotalCost: Histogram;
|
|
70
67
|
|
|
68
|
+
private blockInterBlockTime: Histogram;
|
|
69
|
+
private lastBlockBuiltTimestamp?: number;
|
|
70
|
+
private lastBlockBuiltSlot?: SlotNumber;
|
|
71
|
+
|
|
71
72
|
private lastSeenSlot?: SlotNumber;
|
|
72
73
|
|
|
73
74
|
constructor(
|
|
@@ -86,9 +87,11 @@ export class SequencerMetrics {
|
|
|
86
87
|
|
|
87
88
|
this.blockBuildManaPerSecond = this.meter.createGauge(Metrics.SEQUENCER_BLOCK_BUILD_MANA_PER_SECOND);
|
|
88
89
|
|
|
90
|
+
this.blockInterBlockTime = this.meter.createHistogram(Metrics.SEQUENCER_BLOCK_INTER_BLOCK_TIME);
|
|
91
|
+
|
|
89
92
|
this.stateTransitionBufferDuration = this.meter.createHistogram(Metrics.SEQUENCER_STATE_TRANSITION_BUFFER_DURATION);
|
|
90
93
|
|
|
91
|
-
this.
|
|
94
|
+
this.stateDuration = this.meter.createHistogram(Metrics.SEQUENCER_STATE_DURATION);
|
|
92
95
|
|
|
93
96
|
this.rewards = this.meter.createGauge(Metrics.SEQUENCER_CURRENT_SLOT_REWARDS);
|
|
94
97
|
|
|
@@ -138,15 +141,23 @@ export class SequencerMetrics {
|
|
|
138
141
|
Metrics.SEQUENCER_CHECKPOINT_PROPOSAL_FAILED_COUNT,
|
|
139
142
|
);
|
|
140
143
|
|
|
141
|
-
this.checkpointBuildDuration = this.meter.createHistogram(Metrics.SEQUENCER_CHECKPOINT_BUILD_DURATION);
|
|
142
|
-
this.checkpointBlockCount = this.meter.createGauge(Metrics.SEQUENCER_CHECKPOINT_BLOCK_COUNT);
|
|
143
|
-
this.checkpointTxCount = this.meter.createGauge(Metrics.SEQUENCER_CHECKPOINT_TX_COUNT);
|
|
144
|
-
this.checkpointTotalMana = this.meter.createGauge(Metrics.SEQUENCER_CHECKPOINT_TOTAL_MANA);
|
|
145
|
-
|
|
146
144
|
this.slashingAttempts = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_SLASHING_ATTEMPTS_COUNT);
|
|
147
145
|
|
|
148
146
|
this.pipelineDepth = this.meter.createGauge(Metrics.SEQUENCER_PIPELINE_DEPTH);
|
|
149
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
|
+
);
|
|
150
161
|
this.pipelineDepth.record(0);
|
|
151
162
|
|
|
152
163
|
// Fisherman fee analysis metrics
|
|
@@ -211,21 +222,25 @@ export class SequencerMetrics {
|
|
|
211
222
|
this.timeToCollectAttestations.record(0);
|
|
212
223
|
}
|
|
213
224
|
|
|
214
|
-
public recordCheckpointAttestationDelay(duration: number) {
|
|
215
|
-
this.checkpointAttestationDelay.record(duration);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
225
|
public recordCollectedAttestations(count: number, durationMs: number) {
|
|
219
226
|
this.collectedAttestions.record(count);
|
|
220
227
|
this.timeToCollectAttestations.record(Math.ceil(durationMs));
|
|
221
228
|
}
|
|
222
229
|
|
|
223
|
-
recordBuiltBlock(buildDurationMs: number, totalMana: number) {
|
|
230
|
+
recordBuiltBlock(buildDurationMs: number, totalMana: number, slot: SlotNumber) {
|
|
224
231
|
this.blockCounter.add(1, {
|
|
225
232
|
[Attributes.STATUS]: 'built',
|
|
226
233
|
});
|
|
227
234
|
this.blockBuildDuration.record(Math.ceil(buildDurationMs));
|
|
228
235
|
this.blockBuildManaPerSecond.record(Math.ceil((totalMana * 1000) / buildDurationMs));
|
|
236
|
+
|
|
237
|
+
// Only record inter-block time between blocks built within the same slot.
|
|
238
|
+
const now = Date.now();
|
|
239
|
+
if (this.lastBlockBuiltTimestamp !== undefined && this.lastBlockBuiltSlot === slot) {
|
|
240
|
+
this.blockInterBlockTime.record(now - this.lastBlockBuiltTimestamp);
|
|
241
|
+
}
|
|
242
|
+
this.lastBlockBuiltTimestamp = now;
|
|
243
|
+
this.lastBlockBuiltSlot = slot;
|
|
229
244
|
}
|
|
230
245
|
|
|
231
246
|
recordFailedBlock() {
|
|
@@ -240,6 +255,12 @@ export class SequencerMetrics {
|
|
|
240
255
|
});
|
|
241
256
|
}
|
|
242
257
|
|
|
258
|
+
recordStateDuration(durationMs: number, state: SequencerState) {
|
|
259
|
+
this.stateDuration.record(Math.ceil(durationMs), {
|
|
260
|
+
[Attributes.SEQUENCER_STATE]: state,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
243
264
|
recordPipelineDepth(depth: number) {
|
|
244
265
|
this.pipelineDepth.record(depth);
|
|
245
266
|
}
|
|
@@ -248,6 +269,12 @@ export class SequencerMetrics {
|
|
|
248
269
|
this.pipelineDiscards.add(count);
|
|
249
270
|
}
|
|
250
271
|
|
|
272
|
+
recordPipelineParentCheckpointMismatch(reason: string) {
|
|
273
|
+
this.pipelineParentCheckpointMismatches.add(1, {
|
|
274
|
+
[Attributes.ERROR_TYPE]: reason,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
251
278
|
incOpenSlot(slot: SlotNumber, proposer: string) {
|
|
252
279
|
// sequencer went through the loop a second time. Noop
|
|
253
280
|
if (slot === this.lastSeenSlot) {
|
|
@@ -306,14 +333,6 @@ export class SequencerMetrics {
|
|
|
306
333
|
});
|
|
307
334
|
}
|
|
308
335
|
|
|
309
|
-
/** Records aggregate metrics for a completed checkpoint build. */
|
|
310
|
-
recordCheckpointBuild(durationMs: number, blockCount: number, txCount: number, totalMana: number) {
|
|
311
|
-
this.checkpointBuildDuration.record(Math.ceil(durationMs));
|
|
312
|
-
this.checkpointBlockCount.record(blockCount);
|
|
313
|
-
this.checkpointTxCount.record(txCount);
|
|
314
|
-
this.checkpointTotalMana.record(totalMana);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
336
|
recordSlashingAttempt(actionCount: number) {
|
|
318
337
|
this.slashingAttempts.add(actionCount);
|
|
319
338
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { getKzg } from '@aztec/blob-lib';
|
|
2
2
|
import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
|
|
3
3
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
4
|
-
import { NoCommitteeError, type RollupContract } from '@aztec/ethereum/contracts';
|
|
4
|
+
import { NoCommitteeError, type RollupContract, SimulationOverridesBuilder } from '@aztec/ethereum/contracts';
|
|
5
5
|
import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
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';
|
|
@@ -34,6 +36,7 @@ import type { GlobalVariableBuilder } from '../global_variable_builder/global_bu
|
|
|
34
36
|
import type { SequencerPublisherFactory } from '../publisher/sequencer-publisher-factory.js';
|
|
35
37
|
import type { InvalidateCheckpointRequest, SequencerPublisher } from '../publisher/sequencer-publisher.js';
|
|
36
38
|
import { CheckpointProposalJob } from './checkpoint_proposal_job.js';
|
|
39
|
+
import { CheckpointProposalJobMetrics } from './checkpoint_proposal_job_metrics.js';
|
|
37
40
|
import { CheckpointVoter } from './checkpoint_voter.js';
|
|
38
41
|
import { SequencerInterruptedError, SequencerTooSlowError } from './errors.js';
|
|
39
42
|
import type { SequencerEvents } from './events.js';
|
|
@@ -55,7 +58,11 @@ export { SequencerState };
|
|
|
55
58
|
export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<SequencerEvents>) {
|
|
56
59
|
private runningPromise?: RunningPromise;
|
|
57
60
|
private state = SequencerState.STOPPED;
|
|
61
|
+
private stateSlotNumber: SlotNumber | undefined;
|
|
62
|
+
private stateEnteredAtMs = performance.now();
|
|
58
63
|
private metrics: SequencerMetrics;
|
|
64
|
+
private checkpointProposalJobMetrics: CheckpointProposalJobMetrics;
|
|
65
|
+
private readonly stateLog: Logger;
|
|
59
66
|
|
|
60
67
|
/** The last slot for which we attempted to perform our voting duties with degraded block production */
|
|
61
68
|
private lastSlotForFallbackVote: SlotNumber | undefined;
|
|
@@ -73,13 +80,14 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
73
80
|
private lastEpochForStrategyComparison: EpochNumber | undefined;
|
|
74
81
|
|
|
75
82
|
/** The last checkpoint proposal job, tracked so we can await its pending L1 submission during shutdown. */
|
|
76
|
-
|
|
83
|
+
protected lastCheckpointProposalJob: CheckpointProposalJob | undefined;
|
|
77
84
|
|
|
78
85
|
/** The maximum number of seconds that the sequencer can be into a slot to transition to a particular state. */
|
|
79
86
|
protected timetable!: SequencerTimetable;
|
|
80
87
|
|
|
81
88
|
/** Config for the sequencer */
|
|
82
89
|
protected config: ResolvedSequencerConfig = DefaultSequencerConfig;
|
|
90
|
+
private readonly signatureContext: CoordinationSignatureContext;
|
|
83
91
|
|
|
84
92
|
constructor(
|
|
85
93
|
protected publisherFactory: SequencerPublisherFactory,
|
|
@@ -95,18 +103,24 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
95
103
|
protected dateProvider: DateProvider,
|
|
96
104
|
protected epochCache: EpochCache,
|
|
97
105
|
protected rollupContract: RollupContract,
|
|
98
|
-
config: SequencerConfig,
|
|
106
|
+
config: SequencerConfig & Pick<ChainConfig, 'l1ChainId' | 'l1Contracts'>,
|
|
99
107
|
protected telemetry: TelemetryClient = getTelemetryClient(),
|
|
100
108
|
protected log = createLogger('sequencer'),
|
|
101
109
|
) {
|
|
102
110
|
super();
|
|
111
|
+
this.stateLog = log.createChild('state');
|
|
103
112
|
|
|
104
113
|
// Add [FISHERMAN] prefix to logger if in fisherman mode
|
|
105
114
|
if (config.fishermanMode) {
|
|
106
115
|
this.log = log.createChild('[FISHERMAN]');
|
|
107
116
|
}
|
|
108
117
|
|
|
118
|
+
this.signatureContext = {
|
|
119
|
+
chainId: config.l1ChainId,
|
|
120
|
+
rollupAddress: config.l1Contracts.rollupAddress,
|
|
121
|
+
};
|
|
109
122
|
this.metrics = new SequencerMetrics(telemetry, this.rollupContract, 'Sequencer');
|
|
123
|
+
this.checkpointProposalJobMetrics = new CheckpointProposalJobMetrics(telemetry);
|
|
110
124
|
this.updateConfig(config);
|
|
111
125
|
}
|
|
112
126
|
|
|
@@ -115,14 +129,16 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
115
129
|
const filteredConfig = pickFromSchema(config, SequencerConfigSchema);
|
|
116
130
|
this.log.info(`Updated sequencer config`, omit(filteredConfig, 'txPublicSetupAllowListExtend'));
|
|
117
131
|
this.config = merge(this.config, filteredConfig);
|
|
132
|
+
const p2pPropagationTime = this.config.attestationPropagationTime;
|
|
118
133
|
this.timetable = new SequencerTimetable(
|
|
119
134
|
{
|
|
120
135
|
ethereumSlotDuration: this.l1Constants.ethereumSlotDuration,
|
|
121
136
|
aztecSlotDuration: this.aztecSlotDuration,
|
|
122
137
|
l1PublishingTime: this.l1PublishingTime,
|
|
123
|
-
p2pPropagationTime
|
|
138
|
+
p2pPropagationTime,
|
|
124
139
|
blockDurationMs: this.config.blockDurationMs,
|
|
125
140
|
enforce: this.config.enforceTimeTable,
|
|
141
|
+
pipelining: this.epochCache.isProposerPipeliningEnabled(),
|
|
126
142
|
},
|
|
127
143
|
this.metrics,
|
|
128
144
|
this.log,
|
|
@@ -146,6 +162,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
146
162
|
this.log.info('Started sequencer');
|
|
147
163
|
}
|
|
148
164
|
|
|
165
|
+
/** Triggers an immediate run of the sequencer, bypassing the polling interval. */
|
|
166
|
+
public trigger() {
|
|
167
|
+
return this.runningPromise?.trigger();
|
|
168
|
+
}
|
|
169
|
+
|
|
149
170
|
/** Stops the sequencer from building blocks and moves to STOPPED state. */
|
|
150
171
|
public async stop(): Promise<void> {
|
|
151
172
|
this.log.info(`Stopping sequencer`);
|
|
@@ -299,16 +320,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
299
320
|
// Next checkpoint follows from the last synced one
|
|
300
321
|
const checkpointNumber = CheckpointNumber(syncedTo.checkpointNumber + 1);
|
|
301
322
|
|
|
302
|
-
// Guard: don't exceed 1-deep pipeline. Without a proposed checkpoint, we can only build
|
|
303
|
-
// confirmed + 1. With a proposed checkpoint, we can build confirmed + 2.
|
|
304
|
-
const confirmedCkpt = syncedTo.checkpointedCheckpointNumber;
|
|
305
|
-
if (checkpointNumber > confirmedCkpt + 2) {
|
|
306
|
-
this.log.warn(
|
|
307
|
-
`Skipping slot ${targetSlot}: checkpoint ${checkpointNumber} exceeds max pipeline depth (confirmed=${confirmedCkpt})`,
|
|
308
|
-
);
|
|
309
|
-
return undefined;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
323
|
const logCtx = {
|
|
313
324
|
nowSeconds,
|
|
314
325
|
syncedToL2Slot: syncedTo.syncedL2Slot,
|
|
@@ -329,6 +340,16 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
329
340
|
return undefined;
|
|
330
341
|
}
|
|
331
342
|
|
|
343
|
+
// Guard: don't exceed 1-deep pipeline. Without a proposed checkpoint, we can only build
|
|
344
|
+
// confirmed + 1. With a proposed checkpoint, we can build confirmed + 2.
|
|
345
|
+
const confirmedCkpt = syncedTo.checkpointedCheckpointNumber;
|
|
346
|
+
if (checkpointNumber > confirmedCkpt + 2) {
|
|
347
|
+
this.log.verbose(
|
|
348
|
+
`Skipping slot ${targetSlot}: checkpoint ${checkpointNumber} exceeds max pipeline depth (confirmed=${confirmedCkpt})`,
|
|
349
|
+
);
|
|
350
|
+
return undefined;
|
|
351
|
+
}
|
|
352
|
+
|
|
332
353
|
// Check that the target slot is not taken by a block already (should never happen, since only us can propose for this slot)
|
|
333
354
|
if (syncedTo.blockData && syncedTo.blockData.header.getSlot() >= targetSlot) {
|
|
334
355
|
this.log.warn(
|
|
@@ -360,18 +381,14 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
360
381
|
// The L1 contract reads archives[proposedCheckpointNumber] and compares it with the provided archive.
|
|
361
382
|
// When invalidating or pipelining, the local archive may differ from L1's, so we adjust accordingly.
|
|
362
383
|
let archiveForCheck = syncedTo.archive;
|
|
363
|
-
const
|
|
364
|
-
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
365
|
-
forceArchive?: { checkpointNumber: CheckpointNumber; archive: Fr };
|
|
366
|
-
} = {};
|
|
384
|
+
const l1SimulationOverridesBuilder = new SimulationOverridesBuilder();
|
|
367
385
|
|
|
368
386
|
if (this.epochCache.isProposerPipeliningEnabled() && syncedTo.hasProposedCheckpoint) {
|
|
369
387
|
// Parent checkpoint hasn't landed on L1 yet. Override both the proposed checkpoint number
|
|
370
388
|
// and the archive at that checkpoint so L1 simulation sees the correct chain tip.
|
|
371
389
|
const parentCheckpointNumber = CheckpointNumber(checkpointNumber - 1);
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
this.metrics.recordPipelineDepth(1);
|
|
390
|
+
l1SimulationOverridesBuilder.forPendingCheckpoint(parentCheckpointNumber).withPendingArchive(syncedTo.archive);
|
|
391
|
+
this.metrics.recordPipelineDepth(syncedTo.checkpointNumber - syncedTo.checkpointedCheckpointNumber);
|
|
375
392
|
|
|
376
393
|
this.log.verbose(
|
|
377
394
|
`Building on top of proposed checkpoint (pending=${syncedTo.proposedCheckpointData?.checkpointNumber})`,
|
|
@@ -382,13 +399,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
382
399
|
// After invalidation, L1 will roll back to checkpoint N-1. The archive at N-1 already
|
|
383
400
|
// exists on L1, so we just pass the matching archive (the lastArchive of the invalid checkpoint).
|
|
384
401
|
archiveForCheck = invalidateCheckpoint.lastArchive;
|
|
385
|
-
|
|
402
|
+
l1SimulationOverridesBuilder.forPendingCheckpoint(invalidateCheckpoint.forcePendingCheckpointNumber);
|
|
386
403
|
this.metrics.recordPipelineDepth(0);
|
|
387
404
|
} else {
|
|
388
405
|
this.metrics.recordPipelineDepth(0);
|
|
389
406
|
}
|
|
390
407
|
|
|
391
|
-
const
|
|
408
|
+
const simulationOverridesPlan = l1SimulationOverridesBuilder.build();
|
|
409
|
+
const canProposeCheck = await publisher.canProposeAt(
|
|
410
|
+
archiveForCheck,
|
|
411
|
+
proposer ?? EthAddress.ZERO,
|
|
412
|
+
simulationOverridesPlan,
|
|
413
|
+
);
|
|
392
414
|
|
|
393
415
|
if (canProposeCheck === undefined) {
|
|
394
416
|
this.log.warn(
|
|
@@ -478,12 +500,14 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
478
500
|
this.checkpointsBuilder,
|
|
479
501
|
this.l2BlockSource,
|
|
480
502
|
this.l1Constants,
|
|
503
|
+
this.signatureContext,
|
|
481
504
|
this.config,
|
|
482
505
|
this.timetable,
|
|
483
506
|
this.slasherClient,
|
|
484
507
|
this.epochCache,
|
|
485
508
|
this.dateProvider,
|
|
486
509
|
this.metrics,
|
|
510
|
+
this.checkpointProposalJobMetrics.createRecorder(),
|
|
487
511
|
this,
|
|
488
512
|
this.setState.bind(this),
|
|
489
513
|
this.tracer,
|
|
@@ -524,19 +548,35 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
524
548
|
this.timetable.assertTimeLeft(proposedState, secondsIntoSlot);
|
|
525
549
|
}
|
|
526
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
|
+
|
|
527
557
|
const boringStates = [SequencerState.IDLE, SequencerState.SYNCHRONIZING];
|
|
528
558
|
const logLevel =
|
|
529
|
-
boringStates.includes(proposedState) && boringStates.includes(
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
+
});
|
|
533
568
|
|
|
534
569
|
this.emit('state-changed', {
|
|
535
|
-
oldState
|
|
570
|
+
oldState,
|
|
536
571
|
newState: proposedState,
|
|
537
572
|
secondsIntoSlot,
|
|
538
573
|
slot: slotNumber,
|
|
539
574
|
});
|
|
575
|
+
if (stateChanged) {
|
|
576
|
+
this.metrics.recordStateDuration(stateDurationMs, oldState);
|
|
577
|
+
this.stateEnteredAtMs = transitionAtMs;
|
|
578
|
+
this.stateSlotNumber = slotNumber;
|
|
579
|
+
}
|
|
540
580
|
this.state = proposedState;
|
|
541
581
|
}
|
|
542
582
|
|
|
@@ -567,12 +607,12 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
567
607
|
.getL2Tips()
|
|
568
608
|
.then(t => ({ proposed: t.proposed, checkpointed: t.checkpointed, proposedCheckpoint: t.proposedCheckpoint })),
|
|
569
609
|
this.p2pClient.getStatus().then(p2p => p2p.syncedToL2Block),
|
|
570
|
-
this.l1ToL2MessageSource.getL2Tips().then(t => t.proposed),
|
|
610
|
+
this.l1ToL2MessageSource.getL2Tips().then(t => ({ proposed: t.proposed, checkpointed: t.checkpointed })),
|
|
571
611
|
this.l2BlockSource.getPendingChainValidationStatus(),
|
|
572
|
-
this.l2BlockSource.
|
|
612
|
+
this.l2BlockSource.getLastProposedCheckpoint(),
|
|
573
613
|
] as const);
|
|
574
614
|
|
|
575
|
-
const [worldState, l2Tips, p2p,
|
|
615
|
+
const [worldState, l2Tips, p2p, l1ToL2MessageSourceTips, pendingChainValidationStatus, proposedCheckpointData] =
|
|
576
616
|
syncedBlocks;
|
|
577
617
|
|
|
578
618
|
// 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,
|
|
@@ -580,19 +620,25 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
580
620
|
// TODO(palla/mbps): Fix the above. All components should be able to handle dynamic genesis block hashes.
|
|
581
621
|
const result =
|
|
582
622
|
(l2Tips.proposed.number === 0 &&
|
|
623
|
+
l2Tips.checkpointed.block.number === 0 &&
|
|
624
|
+
l2Tips.checkpointed.checkpoint.number === 0 &&
|
|
583
625
|
worldState.number === 0 &&
|
|
584
626
|
p2p.number === 0 &&
|
|
585
|
-
|
|
627
|
+
l1ToL2MessageSourceTips.proposed.number === 0 &&
|
|
628
|
+
l1ToL2MessageSourceTips.checkpointed.block.number === 0 &&
|
|
629
|
+
l1ToL2MessageSourceTips.checkpointed.checkpoint.number === 0) ||
|
|
586
630
|
(worldState.hash === l2Tips.proposed.hash &&
|
|
587
631
|
p2p.hash === l2Tips.proposed.hash &&
|
|
588
|
-
|
|
632
|
+
l1ToL2MessageSourceTips.proposed.hash === l2Tips.proposed.hash &&
|
|
633
|
+
l1ToL2MessageSourceTips.checkpointed.block.hash === l2Tips.checkpointed.block.hash &&
|
|
634
|
+
l1ToL2MessageSourceTips.checkpointed.checkpoint.hash === l2Tips.checkpointed.checkpoint.hash);
|
|
589
635
|
|
|
590
636
|
if (!result) {
|
|
591
637
|
this.log.debug(`Sequencer sync check failed`, {
|
|
592
638
|
worldState,
|
|
593
639
|
l2BlockSource: l2Tips.proposed,
|
|
594
640
|
p2p,
|
|
595
|
-
|
|
641
|
+
l1ToL2MessageSourceTips,
|
|
596
642
|
});
|
|
597
643
|
return undefined;
|
|
598
644
|
}
|