@aztec/sequencer-client 0.0.1-commit.3f296a7d2 → 0.0.1-commit.3f5453c7b
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 +0 -5
- 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 +3 -8
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +4 -39
- 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/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 +47 -44
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +112 -112
- 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 +21 -10
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +197 -131
- 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/checkpoint_voter.d.ts +1 -2
- package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_voter.js +2 -5
- package/dest/sequencer/metrics.d.ts +5 -10
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +12 -20
- package/dest/sequencer/sequencer.d.ts +17 -4
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +73 -20
- 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 +0 -5
- 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 +8 -43
- package/src/global_variable_builder/index.ts +2 -0
- package/src/publisher/sequencer-publisher-factory.ts +0 -3
- package/src/publisher/sequencer-publisher.ts +174 -158
- package/src/sequencer/README.md +83 -13
- package/src/sequencer/chain_state_overrides.ts +87 -0
- package/src/sequencer/checkpoint_proposal_job.ts +238 -153
- package/src/sequencer/checkpoint_proposal_job_metrics.ts +128 -0
- package/src/sequencer/checkpoint_voter.ts +1 -12
- package/src/sequencer/metrics.ts +15 -25
- package/src/sequencer/sequencer.ts +110 -22
- 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
|
+
}
|
|
@@ -2,7 +2,6 @@ import type { SlotNumber } from '@aztec/foundation/branded-types';
|
|
|
2
2
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
3
3
|
import type { Logger } from '@aztec/foundation/log';
|
|
4
4
|
import type { SlasherClientInterface } from '@aztec/slasher';
|
|
5
|
-
import { getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
6
5
|
import type { ResolvedSequencerConfig } from '@aztec/stdlib/interfaces/server';
|
|
7
6
|
import type { ValidatorClient } from '@aztec/validator-client';
|
|
8
7
|
import { DutyAlreadySignedError } from '@aztec/validator-ha-signer/errors';
|
|
@@ -18,7 +17,6 @@ import type { SequencerRollupConstants } from './types.js';
|
|
|
18
17
|
* Handles governance and slashing voting for a given slot.
|
|
19
18
|
*/
|
|
20
19
|
export class CheckpointVoter {
|
|
21
|
-
private slotTimestamp: bigint;
|
|
22
20
|
private governanceSigner: (msg: TypedDataDefinition) => Promise<`0x${string}`>;
|
|
23
21
|
private slashingSigner: (msg: TypedDataDefinition) => Promise<`0x${string}`>;
|
|
24
22
|
|
|
@@ -33,8 +31,6 @@ export class CheckpointVoter {
|
|
|
33
31
|
private readonly metrics: SequencerMetrics,
|
|
34
32
|
private readonly log: Logger,
|
|
35
33
|
) {
|
|
36
|
-
this.slotTimestamp = getTimestampForSlot(this.slot, this.l1Constants);
|
|
37
|
-
|
|
38
34
|
// Create separate signers with appropriate duty contexts for governance and slashing votes
|
|
39
35
|
// These use HA protection to ensure only one node signs per slot/duty
|
|
40
36
|
const governanceContext: SigningContext = { slot: this.slot, dutyType: DutyType.GOVERNANCE_VOTE };
|
|
@@ -77,7 +73,6 @@ export class CheckpointVoter {
|
|
|
77
73
|
return await this.publisher.enqueueGovernanceCastSignal(
|
|
78
74
|
governanceProposerPayload,
|
|
79
75
|
this.slot,
|
|
80
|
-
this.slotTimestamp,
|
|
81
76
|
this.attestorAddress,
|
|
82
77
|
this.governanceSigner,
|
|
83
78
|
);
|
|
@@ -108,13 +103,7 @@ export class CheckpointVoter {
|
|
|
108
103
|
|
|
109
104
|
this.metrics.recordSlashingAttempt(actions.length);
|
|
110
105
|
|
|
111
|
-
return await this.publisher.enqueueSlashingActions(
|
|
112
|
-
actions,
|
|
113
|
-
this.slot,
|
|
114
|
-
this.slotTimestamp,
|
|
115
|
-
this.attestorAddress,
|
|
116
|
-
this.slashingSigner,
|
|
117
|
-
);
|
|
106
|
+
return await this.publisher.enqueueSlashingActions(actions, this.slot, this.attestorAddress, this.slashingSigner);
|
|
118
107
|
} catch (err) {
|
|
119
108
|
if (err instanceof DutyAlreadySignedError) {
|
|
120
109
|
this.log.info(`Slashing vote already signed by another node`, {
|
package/src/sequencer/metrics.ts
CHANGED
|
@@ -44,11 +44,6 @@ 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;
|
|
54
49
|
|
|
@@ -68,6 +63,10 @@ export class SequencerMetrics {
|
|
|
68
63
|
private fishermanMinedBlobTxPriorityFee: Histogram;
|
|
69
64
|
private fishermanMinedBlobTxTotalCost: Histogram;
|
|
70
65
|
|
|
66
|
+
private blockInterBlockTime: Histogram;
|
|
67
|
+
private lastBlockBuiltTimestamp?: number;
|
|
68
|
+
private lastBlockBuiltSlot?: SlotNumber;
|
|
69
|
+
|
|
71
70
|
private lastSeenSlot?: SlotNumber;
|
|
72
71
|
|
|
73
72
|
constructor(
|
|
@@ -86,9 +85,9 @@ export class SequencerMetrics {
|
|
|
86
85
|
|
|
87
86
|
this.blockBuildManaPerSecond = this.meter.createGauge(Metrics.SEQUENCER_BLOCK_BUILD_MANA_PER_SECOND);
|
|
88
87
|
|
|
89
|
-
this.
|
|
88
|
+
this.blockInterBlockTime = this.meter.createHistogram(Metrics.SEQUENCER_BLOCK_INTER_BLOCK_TIME);
|
|
90
89
|
|
|
91
|
-
this.
|
|
90
|
+
this.stateTransitionBufferDuration = this.meter.createHistogram(Metrics.SEQUENCER_STATE_TRANSITION_BUFFER_DURATION);
|
|
92
91
|
|
|
93
92
|
this.rewards = this.meter.createGauge(Metrics.SEQUENCER_CURRENT_SLOT_REWARDS);
|
|
94
93
|
|
|
@@ -138,11 +137,6 @@ export class SequencerMetrics {
|
|
|
138
137
|
Metrics.SEQUENCER_CHECKPOINT_PROPOSAL_FAILED_COUNT,
|
|
139
138
|
);
|
|
140
139
|
|
|
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
140
|
this.slashingAttempts = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_SLASHING_ATTEMPTS_COUNT);
|
|
147
141
|
|
|
148
142
|
this.pipelineDepth = this.meter.createGauge(Metrics.SEQUENCER_PIPELINE_DEPTH);
|
|
@@ -211,21 +205,25 @@ export class SequencerMetrics {
|
|
|
211
205
|
this.timeToCollectAttestations.record(0);
|
|
212
206
|
}
|
|
213
207
|
|
|
214
|
-
public recordCheckpointAttestationDelay(duration: number) {
|
|
215
|
-
this.checkpointAttestationDelay.record(duration);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
208
|
public recordCollectedAttestations(count: number, durationMs: number) {
|
|
219
209
|
this.collectedAttestions.record(count);
|
|
220
210
|
this.timeToCollectAttestations.record(Math.ceil(durationMs));
|
|
221
211
|
}
|
|
222
212
|
|
|
223
|
-
recordBuiltBlock(buildDurationMs: number, totalMana: number) {
|
|
213
|
+
recordBuiltBlock(buildDurationMs: number, totalMana: number, slot: SlotNumber) {
|
|
224
214
|
this.blockCounter.add(1, {
|
|
225
215
|
[Attributes.STATUS]: 'built',
|
|
226
216
|
});
|
|
227
217
|
this.blockBuildDuration.record(Math.ceil(buildDurationMs));
|
|
228
218
|
this.blockBuildManaPerSecond.record(Math.ceil((totalMana * 1000) / buildDurationMs));
|
|
219
|
+
|
|
220
|
+
// Only record inter-block time between blocks built within the same slot.
|
|
221
|
+
const now = Date.now();
|
|
222
|
+
if (this.lastBlockBuiltTimestamp !== undefined && this.lastBlockBuiltSlot === slot) {
|
|
223
|
+
this.blockInterBlockTime.record(now - this.lastBlockBuiltTimestamp);
|
|
224
|
+
}
|
|
225
|
+
this.lastBlockBuiltTimestamp = now;
|
|
226
|
+
this.lastBlockBuiltSlot = slot;
|
|
229
227
|
}
|
|
230
228
|
|
|
231
229
|
recordFailedBlock() {
|
|
@@ -306,14 +304,6 @@ export class SequencerMetrics {
|
|
|
306
304
|
});
|
|
307
305
|
}
|
|
308
306
|
|
|
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
307
|
recordSlashingAttempt(actionCount: number) {
|
|
318
308
|
this.slashingAttempts.add(actionCount);
|
|
319
309
|
}
|
|
@@ -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';
|
|
@@ -13,7 +13,7 @@ import type { TypedEventEmitter } from '@aztec/foundation/types';
|
|
|
13
13
|
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
|
-
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
16
|
+
import type { Checkpoint, ProposedCheckpointData } from '@aztec/stdlib/checkpoint';
|
|
17
17
|
import { getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
18
18
|
import {
|
|
19
19
|
type ResolvedSequencerConfig,
|
|
@@ -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;
|
|
@@ -72,6 +74,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
72
74
|
/** The last epoch for which we logged strategy comparison in fisherman mode. */
|
|
73
75
|
private lastEpochForStrategyComparison: EpochNumber | undefined;
|
|
74
76
|
|
|
77
|
+
/** The last checkpoint proposal job, tracked so we can await its pending L1 submission during shutdown. */
|
|
78
|
+
protected lastCheckpointProposalJob: CheckpointProposalJob | undefined;
|
|
79
|
+
|
|
75
80
|
/** The maximum number of seconds that the sequencer can be into a slot to transition to a particular state. */
|
|
76
81
|
protected timetable!: SequencerTimetable;
|
|
77
82
|
|
|
@@ -104,6 +109,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
104
109
|
}
|
|
105
110
|
|
|
106
111
|
this.metrics = new SequencerMetrics(telemetry, this.rollupContract, 'Sequencer');
|
|
112
|
+
this.checkpointProposalJobMetrics = new CheckpointProposalJobMetrics(telemetry);
|
|
107
113
|
this.updateConfig(config);
|
|
108
114
|
}
|
|
109
115
|
|
|
@@ -112,14 +118,16 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
112
118
|
const filteredConfig = pickFromSchema(config, SequencerConfigSchema);
|
|
113
119
|
this.log.info(`Updated sequencer config`, omit(filteredConfig, 'txPublicSetupAllowListExtend'));
|
|
114
120
|
this.config = merge(this.config, filteredConfig);
|
|
121
|
+
const p2pPropagationTime = this.config.attestationPropagationTime;
|
|
115
122
|
this.timetable = new SequencerTimetable(
|
|
116
123
|
{
|
|
117
124
|
ethereumSlotDuration: this.l1Constants.ethereumSlotDuration,
|
|
118
125
|
aztecSlotDuration: this.aztecSlotDuration,
|
|
119
126
|
l1PublishingTime: this.l1PublishingTime,
|
|
120
|
-
p2pPropagationTime
|
|
127
|
+
p2pPropagationTime,
|
|
121
128
|
blockDurationMs: this.config.blockDurationMs,
|
|
122
129
|
enforce: this.config.enforceTimeTable,
|
|
130
|
+
pipelining: this.epochCache.isProposerPipeliningEnabled(),
|
|
123
131
|
},
|
|
124
132
|
this.metrics,
|
|
125
133
|
this.log,
|
|
@@ -143,12 +151,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
143
151
|
this.log.info('Started sequencer');
|
|
144
152
|
}
|
|
145
153
|
|
|
154
|
+
/** Triggers an immediate run of the sequencer, bypassing the polling interval. */
|
|
155
|
+
public trigger() {
|
|
156
|
+
return this.runningPromise?.trigger();
|
|
157
|
+
}
|
|
158
|
+
|
|
146
159
|
/** Stops the sequencer from building blocks and moves to STOPPED state. */
|
|
147
160
|
public async stop(): Promise<void> {
|
|
148
161
|
this.log.info(`Stopping sequencer`);
|
|
149
162
|
this.setState(SequencerState.STOPPING, undefined, { force: true });
|
|
150
163
|
await this.publisherFactory.stopAll();
|
|
151
164
|
await this.runningPromise?.stop();
|
|
165
|
+
await this.lastCheckpointProposalJob?.awaitPendingSubmission();
|
|
152
166
|
this.setState(SequencerState.STOPPED, undefined, { force: true });
|
|
153
167
|
this.log.info('Stopped sequencer');
|
|
154
168
|
}
|
|
@@ -208,6 +222,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
208
222
|
return;
|
|
209
223
|
}
|
|
210
224
|
|
|
225
|
+
// Track the job so we can await its pending L1 submission during shutdown
|
|
226
|
+
this.lastCheckpointProposalJob = checkpointProposalJob;
|
|
227
|
+
|
|
211
228
|
// Execute the checkpoint proposal job
|
|
212
229
|
const checkpoint = await checkpointProposalJob.execute();
|
|
213
230
|
|
|
@@ -234,7 +251,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
234
251
|
* @returns CheckpointProposalJob if successful, undefined if we are not yet synced or are not the proposer.
|
|
235
252
|
*/
|
|
236
253
|
@trackSpan('Sequencer.prepareCheckpointProposal')
|
|
237
|
-
|
|
254
|
+
protected async prepareCheckpointProposal(
|
|
238
255
|
slot: SlotNumber,
|
|
239
256
|
targetSlot: SlotNumber,
|
|
240
257
|
epoch: EpochNumber,
|
|
@@ -312,6 +329,16 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
312
329
|
return undefined;
|
|
313
330
|
}
|
|
314
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
|
+
|
|
315
342
|
// Check that the target slot is not taken by a block already (should never happen, since only us can propose for this slot)
|
|
316
343
|
if (syncedTo.blockData && syncedTo.blockData.header.getSlot() >= targetSlot) {
|
|
317
344
|
this.log.warn(
|
|
@@ -337,13 +364,42 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
337
364
|
}
|
|
338
365
|
|
|
339
366
|
// Prepare invalidation request if the pending chain is invalid (returns undefined if no need)
|
|
340
|
-
|
|
367
|
+
let invalidateCheckpoint = await publisher.simulateInvalidateCheckpoint(syncedTo.pendingChainValidationStatus);
|
|
368
|
+
|
|
369
|
+
// Determine the correct archive and L1 state overrides for the canProposeAt check.
|
|
370
|
+
// The L1 contract reads archives[proposedCheckpointNumber] and compares it with the provided archive.
|
|
371
|
+
// When invalidating or pipelining, the local archive may differ from L1's, so we adjust accordingly.
|
|
372
|
+
let archiveForCheck = syncedTo.archive;
|
|
373
|
+
const l1SimulationOverridesBuilder = new SimulationOverridesBuilder();
|
|
374
|
+
|
|
375
|
+
if (this.epochCache.isProposerPipeliningEnabled() && syncedTo.hasProposedCheckpoint) {
|
|
376
|
+
// Parent checkpoint hasn't landed on L1 yet. Override both the proposed checkpoint number
|
|
377
|
+
// and the archive at that checkpoint so L1 simulation sees the correct chain tip.
|
|
378
|
+
const parentCheckpointNumber = CheckpointNumber(checkpointNumber - 1);
|
|
379
|
+
l1SimulationOverridesBuilder.forPendingCheckpoint(parentCheckpointNumber).withPendingArchive(syncedTo.archive);
|
|
380
|
+
this.metrics.recordPipelineDepth(1);
|
|
381
|
+
|
|
382
|
+
this.log.verbose(
|
|
383
|
+
`Building on top of proposed checkpoint (pending=${syncedTo.proposedCheckpointData?.checkpointNumber})`,
|
|
384
|
+
);
|
|
385
|
+
// Clear the invalidation - the proposed checkpoint should handle it.
|
|
386
|
+
invalidateCheckpoint = undefined;
|
|
387
|
+
} else if (invalidateCheckpoint) {
|
|
388
|
+
// After invalidation, L1 will roll back to checkpoint N-1. The archive at N-1 already
|
|
389
|
+
// exists on L1, so we just pass the matching archive (the lastArchive of the invalid checkpoint).
|
|
390
|
+
archiveForCheck = invalidateCheckpoint.lastArchive;
|
|
391
|
+
l1SimulationOverridesBuilder.forPendingCheckpoint(invalidateCheckpoint.forcePendingCheckpointNumber);
|
|
392
|
+
this.metrics.recordPipelineDepth(0);
|
|
393
|
+
} else {
|
|
394
|
+
this.metrics.recordPipelineDepth(0);
|
|
395
|
+
}
|
|
341
396
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
397
|
+
const simulationOverridesPlan = l1SimulationOverridesBuilder.build();
|
|
398
|
+
const canProposeCheck = await publisher.canProposeAt(
|
|
399
|
+
archiveForCheck,
|
|
400
|
+
proposer ?? EthAddress.ZERO,
|
|
401
|
+
simulationOverridesPlan,
|
|
402
|
+
);
|
|
347
403
|
|
|
348
404
|
if (canProposeCheck === undefined) {
|
|
349
405
|
this.log.warn(
|
|
@@ -391,7 +447,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
391
447
|
return this.createCheckpointProposalJob(
|
|
392
448
|
slot,
|
|
393
449
|
targetSlot,
|
|
394
|
-
epoch,
|
|
395
450
|
targetEpoch,
|
|
396
451
|
checkpointNumber,
|
|
397
452
|
syncedTo.blockNumber,
|
|
@@ -399,13 +454,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
399
454
|
publisher,
|
|
400
455
|
attestorAddress,
|
|
401
456
|
invalidateCheckpoint,
|
|
457
|
+
syncedTo.proposedCheckpointData,
|
|
402
458
|
);
|
|
403
459
|
}
|
|
404
460
|
|
|
405
461
|
protected createCheckpointProposalJob(
|
|
406
462
|
slot: SlotNumber,
|
|
407
463
|
targetSlot: SlotNumber,
|
|
408
|
-
epoch: EpochNumber,
|
|
409
464
|
targetEpoch: EpochNumber,
|
|
410
465
|
checkpointNumber: CheckpointNumber,
|
|
411
466
|
syncedToBlockNumber: BlockNumber,
|
|
@@ -413,11 +468,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
413
468
|
publisher: SequencerPublisher,
|
|
414
469
|
attestorAddress: EthAddress,
|
|
415
470
|
invalidateCheckpoint: InvalidateCheckpointRequest | undefined,
|
|
471
|
+
proposedCheckpointData?: ProposedCheckpointData,
|
|
416
472
|
): CheckpointProposalJob {
|
|
417
473
|
return new CheckpointProposalJob(
|
|
418
474
|
slot,
|
|
419
475
|
targetSlot,
|
|
420
|
-
epoch,
|
|
421
476
|
targetEpoch,
|
|
422
477
|
checkpointNumber,
|
|
423
478
|
syncedToBlockNumber,
|
|
@@ -440,10 +495,12 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
440
495
|
this.epochCache,
|
|
441
496
|
this.dateProvider,
|
|
442
497
|
this.metrics,
|
|
498
|
+
this.checkpointProposalJobMetrics.createRecorder(),
|
|
443
499
|
this,
|
|
444
500
|
this.setState.bind(this),
|
|
445
501
|
this.tracer,
|
|
446
502
|
this.log.getBindings(),
|
|
503
|
+
proposedCheckpointData,
|
|
447
504
|
);
|
|
448
505
|
}
|
|
449
506
|
|
|
@@ -518,25 +575,43 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
518
575
|
number: syncSummary.latestBlockNumber,
|
|
519
576
|
hash: syncSummary.latestBlockHash,
|
|
520
577
|
})),
|
|
521
|
-
this.l2BlockSource
|
|
578
|
+
this.l2BlockSource
|
|
579
|
+
.getL2Tips()
|
|
580
|
+
.then(t => ({ proposed: t.proposed, checkpointed: t.checkpointed, proposedCheckpoint: t.proposedCheckpoint })),
|
|
522
581
|
this.p2pClient.getStatus().then(p2p => p2p.syncedToL2Block),
|
|
523
|
-
this.l1ToL2MessageSource.getL2Tips().then(t => t.proposed),
|
|
582
|
+
this.l1ToL2MessageSource.getL2Tips().then(t => ({ proposed: t.proposed, checkpointed: t.checkpointed })),
|
|
524
583
|
this.l2BlockSource.getPendingChainValidationStatus(),
|
|
584
|
+
this.l2BlockSource.getProposedCheckpointOnly(),
|
|
525
585
|
] as const);
|
|
526
586
|
|
|
527
|
-
const [worldState,
|
|
587
|
+
const [worldState, l2Tips, p2p, l1ToL2MessageSourceTips, pendingChainValidationStatus, proposedCheckpointData] =
|
|
588
|
+
syncedBlocks;
|
|
528
589
|
|
|
529
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,
|
|
530
591
|
// as the world state can compute the new genesis block hash, but other components use the hardcoded constant.
|
|
531
592
|
// TODO(palla/mbps): Fix the above. All components should be able to handle dynamic genesis block hashes.
|
|
532
593
|
const result =
|
|
533
|
-
(
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
594
|
+
(l2Tips.proposed.number === 0 &&
|
|
595
|
+
l2Tips.checkpointed.block.number === 0 &&
|
|
596
|
+
l2Tips.checkpointed.checkpoint.number === 0 &&
|
|
597
|
+
worldState.number === 0 &&
|
|
598
|
+
p2p.number === 0 &&
|
|
599
|
+
l1ToL2MessageSourceTips.proposed.number === 0 &&
|
|
600
|
+
l1ToL2MessageSourceTips.checkpointed.block.number === 0 &&
|
|
601
|
+
l1ToL2MessageSourceTips.checkpointed.checkpoint.number === 0) ||
|
|
602
|
+
(worldState.hash === l2Tips.proposed.hash &&
|
|
603
|
+
p2p.hash === l2Tips.proposed.hash &&
|
|
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);
|
|
537
607
|
|
|
538
608
|
if (!result) {
|
|
539
|
-
this.log.debug(`Sequencer sync check failed`, {
|
|
609
|
+
this.log.debug(`Sequencer sync check failed`, {
|
|
610
|
+
worldState,
|
|
611
|
+
l2BlockSource: l2Tips.proposed,
|
|
612
|
+
p2p,
|
|
613
|
+
l1ToL2MessageSourceTips,
|
|
614
|
+
});
|
|
540
615
|
return undefined;
|
|
541
616
|
}
|
|
542
617
|
|
|
@@ -546,8 +621,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
546
621
|
const archive = new Fr((await this.worldState.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root);
|
|
547
622
|
return {
|
|
548
623
|
checkpointNumber: CheckpointNumber.ZERO,
|
|
624
|
+
checkpointedCheckpointNumber: CheckpointNumber.ZERO,
|
|
549
625
|
blockNumber: BlockNumber.ZERO,
|
|
550
626
|
archive,
|
|
627
|
+
hasProposedCheckpoint: false,
|
|
551
628
|
syncedL2Slot,
|
|
552
629
|
pendingChainValidationStatus,
|
|
553
630
|
};
|
|
@@ -560,11 +637,16 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
560
637
|
return undefined;
|
|
561
638
|
}
|
|
562
639
|
|
|
640
|
+
const hasProposedCheckpoint = l2Tips.proposedCheckpoint.checkpoint.number > l2Tips.checkpointed.checkpoint.number;
|
|
641
|
+
|
|
563
642
|
return {
|
|
564
643
|
blockData,
|
|
565
644
|
blockNumber: blockData.header.getBlockNumber(),
|
|
566
645
|
checkpointNumber: blockData.checkpointNumber,
|
|
646
|
+
checkpointedCheckpointNumber: l2Tips.checkpointed.checkpoint.number,
|
|
567
647
|
archive: blockData.archive.root,
|
|
648
|
+
hasProposedCheckpoint,
|
|
649
|
+
proposedCheckpointData,
|
|
568
650
|
syncedL2Slot,
|
|
569
651
|
pendingChainValidationStatus,
|
|
570
652
|
};
|
|
@@ -612,7 +694,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
612
694
|
return [false, proposer];
|
|
613
695
|
}
|
|
614
696
|
|
|
615
|
-
this.log.
|
|
697
|
+
this.log.info(`We are the proposer for pipeline slot ${targetSlot}`, {
|
|
698
|
+
targetSlot,
|
|
699
|
+
proposer,
|
|
700
|
+
});
|
|
616
701
|
return [true, proposer];
|
|
617
702
|
}
|
|
618
703
|
|
|
@@ -909,8 +994,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
909
994
|
type SequencerSyncCheckResult = {
|
|
910
995
|
blockData?: BlockData;
|
|
911
996
|
checkpointNumber: CheckpointNumber;
|
|
997
|
+
checkpointedCheckpointNumber: CheckpointNumber;
|
|
912
998
|
blockNumber: BlockNumber;
|
|
913
999
|
archive: Fr;
|
|
1000
|
+
hasProposedCheckpoint: boolean;
|
|
1001
|
+
proposedCheckpointData?: ProposedCheckpointData;
|
|
914
1002
|
syncedL2Slot: SlotNumber;
|
|
915
1003
|
pendingChainValidationStatus: ValidateCheckpointResult;
|
|
916
1004
|
};
|