@aztec/sequencer-client 0.0.1-commit.f5d02921e → 0.0.1-commit.f7ea82942
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 +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +8 -11
- 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 +60 -92
- 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 +27 -22
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +294 -155
- 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 +7 -10
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +27 -20
- package/dest/sequencer/sequencer.d.ts +5 -2
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +31 -23
- 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/package.json +27 -27
- package/src/client/sequencer-client.ts +5 -7
- package/src/config.ts +8 -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 +106 -152
- package/src/sequencer/README.md +83 -13
- package/src/sequencer/chain_state_overrides.ts +87 -0
- package/src/sequencer/checkpoint_proposal_job.ts +365 -205
- package/src/sequencer/checkpoint_proposal_job_metrics.ts +128 -0
- package/src/sequencer/events.ts +5 -0
- package/src/sequencer/metrics.ts +35 -25
- package/src/sequencer/sequencer.ts +44 -26
- package/src/sequencer/timetable.ts +57 -45
|
@@ -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
|
@@ -44,13 +44,9 @@ export class SequencerMetrics {
|
|
|
44
44
|
private checkpointProposalFailed: UpDownCounter;
|
|
45
45
|
private checkpointSuccess: UpDownCounter;
|
|
46
46
|
private slashingAttempts: UpDownCounter;
|
|
47
|
-
private checkpointAttestationDelay: Histogram;
|
|
48
|
-
private checkpointBuildDuration: Histogram;
|
|
49
|
-
private checkpointBlockCount: Gauge;
|
|
50
|
-
private checkpointTxCount: Gauge;
|
|
51
|
-
private checkpointTotalMana: Gauge;
|
|
52
47
|
private pipelineDepth: Gauge;
|
|
53
48
|
private pipelineDiscards: UpDownCounter;
|
|
49
|
+
private pipelineParentCheckpointMismatches: UpDownCounter;
|
|
54
50
|
|
|
55
51
|
// Fisherman fee analysis metrics
|
|
56
52
|
private fishermanWouldBeIncluded: UpDownCounter;
|
|
@@ -68,6 +64,10 @@ export class SequencerMetrics {
|
|
|
68
64
|
private fishermanMinedBlobTxPriorityFee: Histogram;
|
|
69
65
|
private fishermanMinedBlobTxTotalCost: Histogram;
|
|
70
66
|
|
|
67
|
+
private blockInterBlockTime: Histogram;
|
|
68
|
+
private lastBlockBuiltTimestamp?: number;
|
|
69
|
+
private lastBlockBuiltSlot?: SlotNumber;
|
|
70
|
+
|
|
71
71
|
private lastSeenSlot?: SlotNumber;
|
|
72
72
|
|
|
73
73
|
constructor(
|
|
@@ -86,9 +86,9 @@ export class SequencerMetrics {
|
|
|
86
86
|
|
|
87
87
|
this.blockBuildManaPerSecond = this.meter.createGauge(Metrics.SEQUENCER_BLOCK_BUILD_MANA_PER_SECOND);
|
|
88
88
|
|
|
89
|
-
this.
|
|
89
|
+
this.blockInterBlockTime = this.meter.createHistogram(Metrics.SEQUENCER_BLOCK_INTER_BLOCK_TIME);
|
|
90
90
|
|
|
91
|
-
this.
|
|
91
|
+
this.stateTransitionBufferDuration = this.meter.createHistogram(Metrics.SEQUENCER_STATE_TRANSITION_BUFFER_DURATION);
|
|
92
92
|
|
|
93
93
|
this.rewards = this.meter.createGauge(Metrics.SEQUENCER_CURRENT_SLOT_REWARDS);
|
|
94
94
|
|
|
@@ -138,15 +138,23 @@ export class SequencerMetrics {
|
|
|
138
138
|
Metrics.SEQUENCER_CHECKPOINT_PROPOSAL_FAILED_COUNT,
|
|
139
139
|
);
|
|
140
140
|
|
|
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
141
|
this.slashingAttempts = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_SLASHING_ATTEMPTS_COUNT);
|
|
147
142
|
|
|
148
143
|
this.pipelineDepth = this.meter.createGauge(Metrics.SEQUENCER_PIPELINE_DEPTH);
|
|
149
144
|
this.pipelineDiscards = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_PIPELINE_DISCARDS_COUNT);
|
|
145
|
+
this.pipelineParentCheckpointMismatches = createUpDownCounterWithDefault(
|
|
146
|
+
this.meter,
|
|
147
|
+
Metrics.SEQUENCER_PIPELINE_PARENT_CHECKPOINT_MISMATCH_COUNT,
|
|
148
|
+
{
|
|
149
|
+
[Attributes.ERROR_TYPE]: [
|
|
150
|
+
'archiver-sync-timeout',
|
|
151
|
+
'parent-not-on-l1',
|
|
152
|
+
'parent-hash-mismatch',
|
|
153
|
+
'parent-invalid-attestations',
|
|
154
|
+
'unexpected-parent-appeared',
|
|
155
|
+
],
|
|
156
|
+
},
|
|
157
|
+
);
|
|
150
158
|
this.pipelineDepth.record(0);
|
|
151
159
|
|
|
152
160
|
// Fisherman fee analysis metrics
|
|
@@ -211,21 +219,25 @@ export class SequencerMetrics {
|
|
|
211
219
|
this.timeToCollectAttestations.record(0);
|
|
212
220
|
}
|
|
213
221
|
|
|
214
|
-
public recordCheckpointAttestationDelay(duration: number) {
|
|
215
|
-
this.checkpointAttestationDelay.record(duration);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
222
|
public recordCollectedAttestations(count: number, durationMs: number) {
|
|
219
223
|
this.collectedAttestions.record(count);
|
|
220
224
|
this.timeToCollectAttestations.record(Math.ceil(durationMs));
|
|
221
225
|
}
|
|
222
226
|
|
|
223
|
-
recordBuiltBlock(buildDurationMs: number, totalMana: number) {
|
|
227
|
+
recordBuiltBlock(buildDurationMs: number, totalMana: number, slot: SlotNumber) {
|
|
224
228
|
this.blockCounter.add(1, {
|
|
225
229
|
[Attributes.STATUS]: 'built',
|
|
226
230
|
});
|
|
227
231
|
this.blockBuildDuration.record(Math.ceil(buildDurationMs));
|
|
228
232
|
this.blockBuildManaPerSecond.record(Math.ceil((totalMana * 1000) / buildDurationMs));
|
|
233
|
+
|
|
234
|
+
// Only record inter-block time between blocks built within the same slot.
|
|
235
|
+
const now = Date.now();
|
|
236
|
+
if (this.lastBlockBuiltTimestamp !== undefined && this.lastBlockBuiltSlot === slot) {
|
|
237
|
+
this.blockInterBlockTime.record(now - this.lastBlockBuiltTimestamp);
|
|
238
|
+
}
|
|
239
|
+
this.lastBlockBuiltTimestamp = now;
|
|
240
|
+
this.lastBlockBuiltSlot = slot;
|
|
229
241
|
}
|
|
230
242
|
|
|
231
243
|
recordFailedBlock() {
|
|
@@ -248,6 +260,12 @@ export class SequencerMetrics {
|
|
|
248
260
|
this.pipelineDiscards.add(count);
|
|
249
261
|
}
|
|
250
262
|
|
|
263
|
+
recordPipelineParentCheckpointMismatch(reason: string) {
|
|
264
|
+
this.pipelineParentCheckpointMismatches.add(1, {
|
|
265
|
+
[Attributes.ERROR_TYPE]: reason,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
251
269
|
incOpenSlot(slot: SlotNumber, proposer: string) {
|
|
252
270
|
// sequencer went through the loop a second time. Noop
|
|
253
271
|
if (slot === this.lastSeenSlot) {
|
|
@@ -306,14 +324,6 @@ export class SequencerMetrics {
|
|
|
306
324
|
});
|
|
307
325
|
}
|
|
308
326
|
|
|
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
327
|
recordSlashingAttempt(actionCount: number) {
|
|
318
328
|
this.slashingAttempts.add(actionCount);
|
|
319
329
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
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';
|
|
@@ -34,6 +34,7 @@ import type { GlobalVariableBuilder } from '../global_variable_builder/global_bu
|
|
|
34
34
|
import type { SequencerPublisherFactory } from '../publisher/sequencer-publisher-factory.js';
|
|
35
35
|
import type { InvalidateCheckpointRequest, SequencerPublisher } from '../publisher/sequencer-publisher.js';
|
|
36
36
|
import { CheckpointProposalJob } from './checkpoint_proposal_job.js';
|
|
37
|
+
import { CheckpointProposalJobMetrics } from './checkpoint_proposal_job_metrics.js';
|
|
37
38
|
import { CheckpointVoter } from './checkpoint_voter.js';
|
|
38
39
|
import { SequencerInterruptedError, SequencerTooSlowError } from './errors.js';
|
|
39
40
|
import type { SequencerEvents } from './events.js';
|
|
@@ -56,6 +57,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
56
57
|
private runningPromise?: RunningPromise;
|
|
57
58
|
private state = SequencerState.STOPPED;
|
|
58
59
|
private metrics: SequencerMetrics;
|
|
60
|
+
private checkpointProposalJobMetrics: CheckpointProposalJobMetrics;
|
|
59
61
|
|
|
60
62
|
/** The last slot for which we attempted to perform our voting duties with degraded block production */
|
|
61
63
|
private lastSlotForFallbackVote: SlotNumber | undefined;
|
|
@@ -73,7 +75,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
73
75
|
private lastEpochForStrategyComparison: EpochNumber | undefined;
|
|
74
76
|
|
|
75
77
|
/** The last checkpoint proposal job, tracked so we can await its pending L1 submission during shutdown. */
|
|
76
|
-
|
|
78
|
+
protected lastCheckpointProposalJob: CheckpointProposalJob | undefined;
|
|
77
79
|
|
|
78
80
|
/** The maximum number of seconds that the sequencer can be into a slot to transition to a particular state. */
|
|
79
81
|
protected timetable!: SequencerTimetable;
|
|
@@ -107,6 +109,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
this.metrics = new SequencerMetrics(telemetry, this.rollupContract, 'Sequencer');
|
|
112
|
+
this.checkpointProposalJobMetrics = new CheckpointProposalJobMetrics(telemetry);
|
|
110
113
|
this.updateConfig(config);
|
|
111
114
|
}
|
|
112
115
|
|
|
@@ -115,14 +118,16 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
115
118
|
const filteredConfig = pickFromSchema(config, SequencerConfigSchema);
|
|
116
119
|
this.log.info(`Updated sequencer config`, omit(filteredConfig, 'txPublicSetupAllowListExtend'));
|
|
117
120
|
this.config = merge(this.config, filteredConfig);
|
|
121
|
+
const p2pPropagationTime = this.config.attestationPropagationTime;
|
|
118
122
|
this.timetable = new SequencerTimetable(
|
|
119
123
|
{
|
|
120
124
|
ethereumSlotDuration: this.l1Constants.ethereumSlotDuration,
|
|
121
125
|
aztecSlotDuration: this.aztecSlotDuration,
|
|
122
126
|
l1PublishingTime: this.l1PublishingTime,
|
|
123
|
-
p2pPropagationTime
|
|
127
|
+
p2pPropagationTime,
|
|
124
128
|
blockDurationMs: this.config.blockDurationMs,
|
|
125
129
|
enforce: this.config.enforceTimeTable,
|
|
130
|
+
pipelining: this.epochCache.isProposerPipeliningEnabled(),
|
|
126
131
|
},
|
|
127
132
|
this.metrics,
|
|
128
133
|
this.log,
|
|
@@ -146,6 +151,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
146
151
|
this.log.info('Started sequencer');
|
|
147
152
|
}
|
|
148
153
|
|
|
154
|
+
/** Triggers an immediate run of the sequencer, bypassing the polling interval. */
|
|
155
|
+
public trigger() {
|
|
156
|
+
return this.runningPromise?.trigger();
|
|
157
|
+
}
|
|
158
|
+
|
|
149
159
|
/** Stops the sequencer from building blocks and moves to STOPPED state. */
|
|
150
160
|
public async stop(): Promise<void> {
|
|
151
161
|
this.log.info(`Stopping sequencer`);
|
|
@@ -299,16 +309,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
299
309
|
// Next checkpoint follows from the last synced one
|
|
300
310
|
const checkpointNumber = CheckpointNumber(syncedTo.checkpointNumber + 1);
|
|
301
311
|
|
|
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
312
|
const logCtx = {
|
|
313
313
|
nowSeconds,
|
|
314
314
|
syncedToL2Slot: syncedTo.syncedL2Slot,
|
|
@@ -329,6 +329,16 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
329
329
|
return undefined;
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
+
// Guard: don't exceed 1-deep pipeline. Without a proposed checkpoint, we can only build
|
|
333
|
+
// confirmed + 1. With a proposed checkpoint, we can build confirmed + 2.
|
|
334
|
+
const confirmedCkpt = syncedTo.checkpointedCheckpointNumber;
|
|
335
|
+
if (checkpointNumber > confirmedCkpt + 2) {
|
|
336
|
+
this.log.verbose(
|
|
337
|
+
`Skipping slot ${targetSlot}: checkpoint ${checkpointNumber} exceeds max pipeline depth (confirmed=${confirmedCkpt})`,
|
|
338
|
+
);
|
|
339
|
+
return undefined;
|
|
340
|
+
}
|
|
341
|
+
|
|
332
342
|
// Check that the target slot is not taken by a block already (should never happen, since only us can propose for this slot)
|
|
333
343
|
if (syncedTo.blockData && syncedTo.blockData.header.getSlot() >= targetSlot) {
|
|
334
344
|
this.log.warn(
|
|
@@ -360,17 +370,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
360
370
|
// The L1 contract reads archives[proposedCheckpointNumber] and compares it with the provided archive.
|
|
361
371
|
// When invalidating or pipelining, the local archive may differ from L1's, so we adjust accordingly.
|
|
362
372
|
let archiveForCheck = syncedTo.archive;
|
|
363
|
-
const
|
|
364
|
-
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
365
|
-
forceArchive?: { checkpointNumber: CheckpointNumber; archive: Fr };
|
|
366
|
-
} = {};
|
|
373
|
+
const l1SimulationOverridesBuilder = new SimulationOverridesBuilder();
|
|
367
374
|
|
|
368
375
|
if (this.epochCache.isProposerPipeliningEnabled() && syncedTo.hasProposedCheckpoint) {
|
|
369
376
|
// Parent checkpoint hasn't landed on L1 yet. Override both the proposed checkpoint number
|
|
370
377
|
// and the archive at that checkpoint so L1 simulation sees the correct chain tip.
|
|
371
378
|
const parentCheckpointNumber = CheckpointNumber(checkpointNumber - 1);
|
|
372
|
-
|
|
373
|
-
l1Overrides.forceArchive = { checkpointNumber: parentCheckpointNumber, archive: syncedTo.archive };
|
|
379
|
+
l1SimulationOverridesBuilder.forPendingCheckpoint(parentCheckpointNumber).withPendingArchive(syncedTo.archive);
|
|
374
380
|
this.metrics.recordPipelineDepth(1);
|
|
375
381
|
|
|
376
382
|
this.log.verbose(
|
|
@@ -382,13 +388,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
382
388
|
// After invalidation, L1 will roll back to checkpoint N-1. The archive at N-1 already
|
|
383
389
|
// exists on L1, so we just pass the matching archive (the lastArchive of the invalid checkpoint).
|
|
384
390
|
archiveForCheck = invalidateCheckpoint.lastArchive;
|
|
385
|
-
|
|
391
|
+
l1SimulationOverridesBuilder.forPendingCheckpoint(invalidateCheckpoint.forcePendingCheckpointNumber);
|
|
386
392
|
this.metrics.recordPipelineDepth(0);
|
|
387
393
|
} else {
|
|
388
394
|
this.metrics.recordPipelineDepth(0);
|
|
389
395
|
}
|
|
390
396
|
|
|
391
|
-
const
|
|
397
|
+
const simulationOverridesPlan = l1SimulationOverridesBuilder.build();
|
|
398
|
+
const canProposeCheck = await publisher.canProposeAt(
|
|
399
|
+
archiveForCheck,
|
|
400
|
+
proposer ?? EthAddress.ZERO,
|
|
401
|
+
simulationOverridesPlan,
|
|
402
|
+
);
|
|
392
403
|
|
|
393
404
|
if (canProposeCheck === undefined) {
|
|
394
405
|
this.log.warn(
|
|
@@ -484,6 +495,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
484
495
|
this.epochCache,
|
|
485
496
|
this.dateProvider,
|
|
486
497
|
this.metrics,
|
|
498
|
+
this.checkpointProposalJobMetrics.createRecorder(),
|
|
487
499
|
this,
|
|
488
500
|
this.setState.bind(this),
|
|
489
501
|
this.tracer,
|
|
@@ -567,12 +579,12 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
567
579
|
.getL2Tips()
|
|
568
580
|
.then(t => ({ proposed: t.proposed, checkpointed: t.checkpointed, proposedCheckpoint: t.proposedCheckpoint })),
|
|
569
581
|
this.p2pClient.getStatus().then(p2p => p2p.syncedToL2Block),
|
|
570
|
-
this.l1ToL2MessageSource.getL2Tips().then(t => t.proposed),
|
|
582
|
+
this.l1ToL2MessageSource.getL2Tips().then(t => ({ proposed: t.proposed, checkpointed: t.checkpointed })),
|
|
571
583
|
this.l2BlockSource.getPendingChainValidationStatus(),
|
|
572
584
|
this.l2BlockSource.getProposedCheckpointOnly(),
|
|
573
585
|
] as const);
|
|
574
586
|
|
|
575
|
-
const [worldState, l2Tips, p2p,
|
|
587
|
+
const [worldState, l2Tips, p2p, l1ToL2MessageSourceTips, pendingChainValidationStatus, proposedCheckpointData] =
|
|
576
588
|
syncedBlocks;
|
|
577
589
|
|
|
578
590
|
// 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 +592,25 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
580
592
|
// TODO(palla/mbps): Fix the above. All components should be able to handle dynamic genesis block hashes.
|
|
581
593
|
const result =
|
|
582
594
|
(l2Tips.proposed.number === 0 &&
|
|
595
|
+
l2Tips.checkpointed.block.number === 0 &&
|
|
596
|
+
l2Tips.checkpointed.checkpoint.number === 0 &&
|
|
583
597
|
worldState.number === 0 &&
|
|
584
598
|
p2p.number === 0 &&
|
|
585
|
-
|
|
599
|
+
l1ToL2MessageSourceTips.proposed.number === 0 &&
|
|
600
|
+
l1ToL2MessageSourceTips.checkpointed.block.number === 0 &&
|
|
601
|
+
l1ToL2MessageSourceTips.checkpointed.checkpoint.number === 0) ||
|
|
586
602
|
(worldState.hash === l2Tips.proposed.hash &&
|
|
587
603
|
p2p.hash === l2Tips.proposed.hash &&
|
|
588
|
-
|
|
604
|
+
l1ToL2MessageSourceTips.proposed.hash === l2Tips.proposed.hash &&
|
|
605
|
+
l1ToL2MessageSourceTips.checkpointed.block.hash === l2Tips.checkpointed.block.hash &&
|
|
606
|
+
l1ToL2MessageSourceTips.checkpointed.checkpoint.hash === l2Tips.checkpointed.checkpoint.hash);
|
|
589
607
|
|
|
590
608
|
if (!result) {
|
|
591
609
|
this.log.debug(`Sequencer sync check failed`, {
|
|
592
610
|
worldState,
|
|
593
611
|
l2BlockSource: l2Tips.proposed,
|
|
594
612
|
p2p,
|
|
595
|
-
|
|
613
|
+
l1ToL2MessageSourceTips,
|
|
596
614
|
});
|
|
597
615
|
return undefined;
|
|
598
616
|
}
|
|
@@ -2,8 +2,10 @@ import type { Logger } from '@aztec/foundation/log';
|
|
|
2
2
|
import {
|
|
3
3
|
CHECKPOINT_ASSEMBLE_TIME,
|
|
4
4
|
CHECKPOINT_INITIALIZATION_TIME,
|
|
5
|
+
type CheckpointTiming,
|
|
5
6
|
DEFAULT_P2P_PROPAGATION_TIME,
|
|
6
7
|
MIN_EXECUTION_TIME,
|
|
8
|
+
createCheckpointTimingModel,
|
|
7
9
|
} from '@aztec/stdlib/timetable';
|
|
8
10
|
|
|
9
11
|
import { SequencerTooSlowError } from './errors.js';
|
|
@@ -11,6 +13,8 @@ import type { SequencerMetrics } from './metrics.js';
|
|
|
11
13
|
import { SequencerState } from './utils.js';
|
|
12
14
|
|
|
13
15
|
export class SequencerTimetable {
|
|
16
|
+
private readonly checkpointTiming: CheckpointTiming;
|
|
17
|
+
|
|
14
18
|
/**
|
|
15
19
|
* How late into the slot can we be to start working. Computed as the total time needed for assembling and publishing a block,
|
|
16
20
|
* assuming an execution time equal to `minExecutionTime`, subtracted from the slot duration. This means that, if the proposer
|
|
@@ -70,6 +74,15 @@ export class SequencerTimetable {
|
|
|
70
74
|
/** Maximum number of blocks that can be built in this slot configuration */
|
|
71
75
|
public readonly maxNumberOfBlocks: number;
|
|
72
76
|
|
|
77
|
+
/** Whether pipelining is enabled (checkpoint finalization deferred to next slot). */
|
|
78
|
+
public readonly pipelining: boolean;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* How far into the target slot attestation collection can extend when pipelining.
|
|
82
|
+
* Covers validator re-execution (one block duration) plus one-way attestation return.
|
|
83
|
+
*/
|
|
84
|
+
public readonly pipeliningAttestationGracePeriod: number;
|
|
85
|
+
|
|
73
86
|
constructor(
|
|
74
87
|
opts: {
|
|
75
88
|
ethereumSlotDuration: number;
|
|
@@ -78,6 +91,7 @@ export class SequencerTimetable {
|
|
|
78
91
|
p2pPropagationTime?: number;
|
|
79
92
|
blockDurationMs?: number;
|
|
80
93
|
enforce: boolean;
|
|
94
|
+
pipelining?: boolean;
|
|
81
95
|
},
|
|
82
96
|
private readonly metrics?: SequencerMetrics,
|
|
83
97
|
private readonly log?: Logger,
|
|
@@ -85,52 +99,31 @@ export class SequencerTimetable {
|
|
|
85
99
|
this.ethereumSlotDuration = opts.ethereumSlotDuration;
|
|
86
100
|
this.aztecSlotDuration = opts.aztecSlotDuration;
|
|
87
101
|
this.l1PublishingTime = opts.l1PublishingTime;
|
|
88
|
-
this.p2pPropagationTime = opts.p2pPropagationTime ?? DEFAULT_P2P_PROPAGATION_TIME;
|
|
89
102
|
this.blockDuration = opts.blockDurationMs ? opts.blockDurationMs / 1000 : undefined;
|
|
90
103
|
this.enforce = opts.enforce;
|
|
104
|
+
this.pipelining = opts.pipelining ?? false;
|
|
105
|
+
|
|
106
|
+
this.checkpointTiming = createCheckpointTimingModel({
|
|
107
|
+
aztecSlotDuration: this.aztecSlotDuration,
|
|
108
|
+
ethereumSlotDuration: this.ethereumSlotDuration,
|
|
109
|
+
blockDuration: this.blockDuration,
|
|
110
|
+
l1PublishingTime: this.l1PublishingTime,
|
|
111
|
+
p2pPropagationTime: opts.p2pPropagationTime ?? DEFAULT_P2P_PROPAGATION_TIME,
|
|
112
|
+
pipelining: this.pipelining,
|
|
113
|
+
});
|
|
91
114
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
this.checkpointInitializationTime = 0.5;
|
|
97
|
-
this.minExecutionTime = 1;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Min execution time cannot be less than the block duration if set
|
|
101
|
-
if (this.blockDuration !== undefined && this.minExecutionTime > this.blockDuration) {
|
|
102
|
-
this.minExecutionTime = this.blockDuration;
|
|
103
|
-
}
|
|
115
|
+
this.p2pPropagationTime = this.checkpointTiming.p2pPropagationTime;
|
|
116
|
+
this.checkpointAssembleTime = this.checkpointTiming.checkpointAssembleTime;
|
|
117
|
+
this.checkpointInitializationTime = this.checkpointTiming.checkpointInitializationTime;
|
|
118
|
+
this.minExecutionTime = this.checkpointTiming.minExecutionTime;
|
|
104
119
|
|
|
105
120
|
// Calculate initialization offset - estimate of time needed for sync + proposer check
|
|
106
121
|
// This is the baseline for all sub-slot deadlines
|
|
107
|
-
this.initializationOffset = this.checkpointInitializationTime;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
this.
|
|
111
|
-
|
|
112
|
-
this.p2pPropagationTime * 2 + // Round-trip propagation
|
|
113
|
-
this.l1PublishingTime; // L1 publishing
|
|
114
|
-
|
|
115
|
-
// Calculate maximum number of blocks that fit in this slot
|
|
116
|
-
if (!this.blockDuration) {
|
|
117
|
-
this.maxNumberOfBlocks = 1; // Single block per slot
|
|
118
|
-
} else {
|
|
119
|
-
const timeReservedAtEnd =
|
|
120
|
-
this.blockDuration + // Last sub-slot for validator re-execution
|
|
121
|
-
this.checkpointFinalizationTime; // Checkpoint finalization
|
|
122
|
-
const timeAvailableForBlocks = this.aztecSlotDuration - this.initializationOffset - timeReservedAtEnd;
|
|
123
|
-
this.maxNumberOfBlocks = Math.floor(timeAvailableForBlocks / this.blockDuration);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Minimum work to do within a slot for building a block with the minimum time for execution and publishing its checkpoint
|
|
127
|
-
const minWorkToDo =
|
|
128
|
-
this.initializationOffset +
|
|
129
|
-
this.minExecutionTime * 2 + // Execution and reexecution
|
|
130
|
-
this.checkpointFinalizationTime;
|
|
131
|
-
|
|
132
|
-
const initializeDeadline = this.aztecSlotDuration - minWorkToDo;
|
|
133
|
-
this.initializeDeadline = initializeDeadline;
|
|
122
|
+
this.initializationOffset = this.checkpointTiming.checkpointInitializationTime;
|
|
123
|
+
this.checkpointFinalizationTime = this.checkpointTiming.checkpointFinalizationTime;
|
|
124
|
+
this.pipeliningAttestationGracePeriod = this.checkpointTiming.pipeliningAttestationGracePeriod;
|
|
125
|
+
this.maxNumberOfBlocks = this.checkpointTiming.calculateMaxBlocksPerSlot();
|
|
126
|
+
this.initializeDeadline = this.checkpointTiming.initializeDeadline;
|
|
134
127
|
|
|
135
128
|
this.log?.info(
|
|
136
129
|
`Sequencer timetable initialized with ${this.maxNumberOfBlocks} blocks per slot (${this.enforce ? 'enforced' : 'not enforced'})`,
|
|
@@ -144,19 +137,37 @@ export class SequencerTimetable {
|
|
|
144
137
|
blockAssembleTime: this.checkpointAssembleTime,
|
|
145
138
|
initializeDeadline: this.initializeDeadline,
|
|
146
139
|
enforce: this.enforce,
|
|
147
|
-
|
|
140
|
+
pipelining: this.pipelining,
|
|
141
|
+
pipeliningAttestationGracePeriod: this.pipeliningAttestationGracePeriod,
|
|
142
|
+
minWorkToDo: this.checkpointTiming.minimumBuildSlotWork,
|
|
148
143
|
blockDuration: this.blockDuration,
|
|
149
144
|
maxNumberOfBlocks: this.maxNumberOfBlocks,
|
|
150
145
|
},
|
|
151
146
|
);
|
|
152
147
|
|
|
153
|
-
if (initializeDeadline <= 0) {
|
|
148
|
+
if (this.initializeDeadline <= 0) {
|
|
154
149
|
throw new Error(
|
|
155
|
-
`Block proposal initialize deadline cannot be negative (got ${initializeDeadline} from total time needed ${
|
|
150
|
+
`Block proposal initialize deadline cannot be negative (got ${this.initializeDeadline} from total time needed ${this.checkpointTiming.minimumBuildSlotWork} and a slot duration of ${this.aztecSlotDuration}).`,
|
|
156
151
|
);
|
|
157
152
|
}
|
|
158
153
|
}
|
|
159
154
|
|
|
155
|
+
public getCheckpointAssemblyDeadline(): number {
|
|
156
|
+
return this.checkpointTiming.checkpointAssemblyDeadline;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
public getCheckpointAttestationDeadline(): number {
|
|
160
|
+
return this.checkpointTiming.checkpointAttestationDeadline;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
public getCheckpointAttestationStartDeadline(): number {
|
|
164
|
+
return this.checkpointTiming.checkpointAttestationStartDeadline;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
public getCheckpointPublishingDeadline(): number {
|
|
168
|
+
return this.checkpointTiming.checkpointPublishingDeadline;
|
|
169
|
+
}
|
|
170
|
+
|
|
160
171
|
public getMaxAllowedTime(
|
|
161
172
|
state: Extract<SequencerState, SequencerState.STOPPED | SequencerState.IDLE | SequencerState.SYNCHRONIZING>,
|
|
162
173
|
): undefined;
|
|
@@ -179,10 +190,11 @@ export class SequencerTimetable {
|
|
|
179
190
|
case SequencerState.WAITING_UNTIL_NEXT_BLOCK:
|
|
180
191
|
return this.initializeDeadline + this.checkpointInitializationTime;
|
|
181
192
|
case SequencerState.ASSEMBLING_CHECKPOINT:
|
|
193
|
+
return this.getCheckpointAssemblyDeadline();
|
|
182
194
|
case SequencerState.COLLECTING_ATTESTATIONS:
|
|
183
|
-
return this.
|
|
195
|
+
return this.getCheckpointAttestationStartDeadline();
|
|
184
196
|
case SequencerState.PUBLISHING_CHECKPOINT:
|
|
185
|
-
return this.
|
|
197
|
+
return this.getCheckpointPublishingDeadline();
|
|
186
198
|
default: {
|
|
187
199
|
const _exhaustiveCheck: never = state;
|
|
188
200
|
throw new Error(`Unexpected state: ${state}`);
|