@aztec/sequencer-client 0.0.1-commit.54489865 → 0.0.1-commit.5914bae
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 +16 -7
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +56 -23
- package/dest/config.d.ts +25 -6
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +46 -28
- package/dest/global_variable_builder/global_builder.d.ts +14 -10
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +22 -21
- package/dest/global_variable_builder/index.d.ts +2 -2
- package/dest/global_variable_builder/index.d.ts.map +1 -1
- package/dest/publisher/config.d.ts +31 -17
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +101 -42
- package/dest/publisher/sequencer-publisher-factory.d.ts +11 -3
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +13 -2
- package/dest/publisher/sequencer-publisher.d.ts +29 -14
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +144 -74
- package/dest/sequencer/checkpoint_proposal_job.d.ts +30 -7
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +191 -113
- 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 +17 -5
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +86 -15
- package/dest/sequencer/sequencer.d.ts +29 -13
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +47 -43
- package/dest/sequencer/timetable.d.ts +4 -6
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +7 -11
- package/dest/sequencer/types.d.ts +2 -2
- package/dest/sequencer/types.d.ts.map +1 -1
- package/dest/test/index.d.ts +3 -5
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts +12 -12
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +47 -36
- package/dest/test/utils.d.ts +3 -3
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +5 -4
- package/package.json +28 -28
- package/src/client/sequencer-client.ts +78 -21
- package/src/config.ts +61 -38
- package/src/global_variable_builder/global_builder.ts +23 -24
- package/src/global_variable_builder/index.ts +1 -1
- package/src/publisher/config.ts +112 -43
- package/src/publisher/sequencer-publisher-factory.ts +23 -6
- package/src/publisher/sequencer-publisher.ts +155 -87
- package/src/sequencer/checkpoint_proposal_job.ts +285 -159
- package/src/sequencer/checkpoint_voter.ts +1 -12
- package/src/sequencer/metrics.ts +92 -18
- package/src/sequencer/sequencer.ts +60 -49
- package/src/sequencer/timetable.ts +13 -12
- package/src/sequencer/types.ts +1 -1
- package/src/test/index.ts +2 -4
- package/src/test/mock_checkpoint_builder.ts +67 -53
- package/src/test/utils.ts +5 -2
|
@@ -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
|
@@ -18,7 +18,6 @@ import { type Hex, formatUnits } from 'viem';
|
|
|
18
18
|
|
|
19
19
|
import type { SequencerState } from './utils.js';
|
|
20
20
|
|
|
21
|
-
// TODO(palla/mbps): Review all metrics and add any missing ones per checkpoint
|
|
22
21
|
export class SequencerMetrics {
|
|
23
22
|
public readonly tracer: Tracer;
|
|
24
23
|
private meter: Meter;
|
|
@@ -40,17 +39,26 @@ export class SequencerMetrics {
|
|
|
40
39
|
private filledSlots: UpDownCounter;
|
|
41
40
|
|
|
42
41
|
private blockProposalFailed: UpDownCounter;
|
|
43
|
-
private
|
|
44
|
-
private
|
|
42
|
+
private checkpointProposalSuccess: UpDownCounter;
|
|
43
|
+
private checkpointPrecheckFailed: UpDownCounter;
|
|
44
|
+
private checkpointProposalFailed: UpDownCounter;
|
|
45
45
|
private checkpointSuccess: UpDownCounter;
|
|
46
46
|
private slashingAttempts: UpDownCounter;
|
|
47
47
|
private checkpointAttestationDelay: Histogram;
|
|
48
|
+
private checkpointBuildDuration: Histogram;
|
|
49
|
+
private checkpointBlockCount: Gauge;
|
|
50
|
+
private checkpointTxCount: Gauge;
|
|
51
|
+
private checkpointTotalMana: Gauge;
|
|
48
52
|
|
|
49
53
|
// Fisherman fee analysis metrics
|
|
50
54
|
private fishermanWouldBeIncluded: UpDownCounter;
|
|
51
55
|
private fishermanTimeBeforeBlock: Histogram;
|
|
52
56
|
private fishermanPendingBlobTxCount: Histogram;
|
|
53
57
|
private fishermanIncludedBlobTxCount: Histogram;
|
|
58
|
+
private fishermanPendingBlobCount: Histogram;
|
|
59
|
+
private fishermanIncludedBlobCount: Histogram;
|
|
60
|
+
private fishermanBlockBlobsFull: UpDownCounter;
|
|
61
|
+
private fishermanMaxBlobCapacity: Histogram;
|
|
54
62
|
private fishermanCalculatedPriorityFee: Histogram;
|
|
55
63
|
private fishermanPriorityFeeDelta: Histogram;
|
|
56
64
|
private fishermanEstimatedCost: Histogram;
|
|
@@ -80,7 +88,7 @@ export class SequencerMetrics {
|
|
|
80
88
|
|
|
81
89
|
this.checkpointAttestationDelay = this.meter.createHistogram(Metrics.SEQUENCER_CHECKPOINT_ATTESTATION_DELAY);
|
|
82
90
|
|
|
83
|
-
this.rewards = this.meter.createGauge(Metrics.
|
|
91
|
+
this.rewards = this.meter.createGauge(Metrics.SEQUENCER_CURRENT_SLOT_REWARDS);
|
|
84
92
|
|
|
85
93
|
this.slots = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_SLOT_COUNT);
|
|
86
94
|
|
|
@@ -103,16 +111,16 @@ export class SequencerMetrics {
|
|
|
103
111
|
Metrics.SEQUENCER_BLOCK_PROPOSAL_FAILED_COUNT,
|
|
104
112
|
);
|
|
105
113
|
|
|
106
|
-
this.
|
|
114
|
+
this.checkpointProposalSuccess = createUpDownCounterWithDefault(
|
|
107
115
|
this.meter,
|
|
108
|
-
Metrics.
|
|
116
|
+
Metrics.SEQUENCER_CHECKPOINT_PROPOSAL_SUCCESS_COUNT,
|
|
109
117
|
);
|
|
110
118
|
|
|
111
119
|
this.checkpointSuccess = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_CHECKPOINT_SUCCESS_COUNT);
|
|
112
120
|
|
|
113
|
-
this.
|
|
121
|
+
this.checkpointPrecheckFailed = createUpDownCounterWithDefault(
|
|
114
122
|
this.meter,
|
|
115
|
-
Metrics.
|
|
123
|
+
Metrics.SEQUENCER_CHECKPOINT_PRECHECK_FAILED_COUNT,
|
|
116
124
|
{
|
|
117
125
|
[Attributes.ERROR_TYPE]: [
|
|
118
126
|
'slot_already_taken',
|
|
@@ -123,6 +131,16 @@ export class SequencerMetrics {
|
|
|
123
131
|
},
|
|
124
132
|
);
|
|
125
133
|
|
|
134
|
+
this.checkpointProposalFailed = createUpDownCounterWithDefault(
|
|
135
|
+
this.meter,
|
|
136
|
+
Metrics.SEQUENCER_CHECKPOINT_PROPOSAL_FAILED_COUNT,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
this.checkpointBuildDuration = this.meter.createHistogram(Metrics.SEQUENCER_CHECKPOINT_BUILD_DURATION);
|
|
140
|
+
this.checkpointBlockCount = this.meter.createGauge(Metrics.SEQUENCER_CHECKPOINT_BLOCK_COUNT);
|
|
141
|
+
this.checkpointTxCount = this.meter.createGauge(Metrics.SEQUENCER_CHECKPOINT_TX_COUNT);
|
|
142
|
+
this.checkpointTotalMana = this.meter.createGauge(Metrics.SEQUENCER_CHECKPOINT_TOTAL_MANA);
|
|
143
|
+
|
|
126
144
|
this.slashingAttempts = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_SLASHING_ATTEMPTS_COUNT);
|
|
127
145
|
|
|
128
146
|
// Fisherman fee analysis metrics
|
|
@@ -131,6 +149,7 @@ export class SequencerMetrics {
|
|
|
131
149
|
Metrics.FISHERMAN_FEE_ANALYSIS_WOULD_BE_INCLUDED,
|
|
132
150
|
{
|
|
133
151
|
[Attributes.OK]: [true, false],
|
|
152
|
+
[Attributes.BLOCK_FULL]: ['true', 'false'],
|
|
134
153
|
},
|
|
135
154
|
);
|
|
136
155
|
|
|
@@ -161,6 +180,20 @@ export class SequencerMetrics {
|
|
|
161
180
|
this.fishermanMinedBlobTxTotalCost = this.meter.createHistogram(
|
|
162
181
|
Metrics.FISHERMAN_FEE_ANALYSIS_MINED_BLOB_TX_TOTAL_COST,
|
|
163
182
|
);
|
|
183
|
+
|
|
184
|
+
this.fishermanPendingBlobCount = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_PENDING_BLOB_COUNT);
|
|
185
|
+
|
|
186
|
+
this.fishermanIncludedBlobCount = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_INCLUDED_BLOB_COUNT);
|
|
187
|
+
|
|
188
|
+
this.fishermanBlockBlobsFull = createUpDownCounterWithDefault(
|
|
189
|
+
this.meter,
|
|
190
|
+
Metrics.FISHERMAN_FEE_ANALYSIS_BLOCK_BLOBS_FULL,
|
|
191
|
+
{
|
|
192
|
+
[Attributes.OK]: [true, false],
|
|
193
|
+
},
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
this.fishermanMaxBlobCapacity = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_MAX_BLOB_CAPACITY);
|
|
164
197
|
}
|
|
165
198
|
|
|
166
199
|
public recordRequiredAttestations(requiredAttestationsCount: number, allowanceMs: number) {
|
|
@@ -243,18 +276,30 @@ export class SequencerMetrics {
|
|
|
243
276
|
});
|
|
244
277
|
}
|
|
245
278
|
|
|
246
|
-
|
|
247
|
-
this.
|
|
279
|
+
recordCheckpointProposalSuccess() {
|
|
280
|
+
this.checkpointProposalSuccess.add(1);
|
|
248
281
|
}
|
|
249
282
|
|
|
250
|
-
|
|
283
|
+
recordCheckpointPrecheckFailed(
|
|
251
284
|
checkType: 'slot_already_taken' | 'rollup_contract_check_failed' | 'slot_mismatch' | 'block_number_mismatch',
|
|
252
285
|
) {
|
|
253
|
-
this.
|
|
254
|
-
|
|
286
|
+
this.checkpointPrecheckFailed.add(1, { [Attributes.ERROR_TYPE]: checkType });
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
recordCheckpointProposalFailed(reason?: string) {
|
|
290
|
+
this.checkpointProposalFailed.add(1, {
|
|
291
|
+
...(reason && { [Attributes.ERROR_TYPE]: reason }),
|
|
255
292
|
});
|
|
256
293
|
}
|
|
257
294
|
|
|
295
|
+
/** Records aggregate metrics for a completed checkpoint build. */
|
|
296
|
+
recordCheckpointBuild(durationMs: number, blockCount: number, txCount: number, totalMana: number) {
|
|
297
|
+
this.checkpointBuildDuration.record(Math.ceil(durationMs));
|
|
298
|
+
this.checkpointBlockCount.record(blockCount);
|
|
299
|
+
this.checkpointTxCount.record(txCount);
|
|
300
|
+
this.checkpointTotalMana.record(totalMana);
|
|
301
|
+
}
|
|
302
|
+
|
|
258
303
|
recordSlashingAttempt(actionCount: number) {
|
|
259
304
|
this.slashingAttempts.add(actionCount);
|
|
260
305
|
}
|
|
@@ -281,10 +326,12 @@ export class SequencerMetrics {
|
|
|
281
326
|
|
|
282
327
|
// Record pending block snapshot data (once per strategy for comparison)
|
|
283
328
|
this.fishermanPendingBlobTxCount.record(analysis.pendingSnapshot.pendingBlobTxCount, strategyAttributes);
|
|
329
|
+
this.fishermanPendingBlobCount.record(analysis.pendingSnapshot.pendingBlobCount, strategyAttributes);
|
|
284
330
|
|
|
285
331
|
// Record mined block data if available
|
|
286
332
|
if (analysis.minedBlock) {
|
|
287
333
|
this.fishermanIncludedBlobTxCount.record(analysis.minedBlock.includedBlobTxCount, strategyAttributes);
|
|
334
|
+
this.fishermanIncludedBlobCount.record(analysis.minedBlock.includedBlobCount, strategyAttributes);
|
|
288
335
|
|
|
289
336
|
// Record actual fees from blob transactions in the mined block
|
|
290
337
|
for (const blobTx of analysis.minedBlock.includedBlobTxs) {
|
|
@@ -318,13 +365,28 @@ export class SequencerMetrics {
|
|
|
318
365
|
if (analysis.analysis) {
|
|
319
366
|
this.fishermanTimeBeforeBlock.record(Math.ceil(analysis.analysis.timeBeforeBlockMs), strategyAttributes);
|
|
320
367
|
|
|
368
|
+
// Record whether the block reached 100% blob capacity
|
|
369
|
+
if (analysis.analysis.blockBlobsFull) {
|
|
370
|
+
this.fishermanBlockBlobsFull.add(1, { ...strategyAttributes, [Attributes.OK]: true });
|
|
371
|
+
} else {
|
|
372
|
+
this.fishermanBlockBlobsFull.add(1, { ...strategyAttributes, [Attributes.OK]: false });
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Record the max blob capacity for this block
|
|
376
|
+
this.fishermanMaxBlobCapacity.record(analysis.analysis.maxBlobCapacity, strategyAttributes);
|
|
377
|
+
|
|
321
378
|
// Record strategy-specific inclusion result
|
|
322
379
|
if (strategyResult.wouldBeIncluded !== undefined) {
|
|
380
|
+
const inclusionAttributes = {
|
|
381
|
+
...strategyAttributes,
|
|
382
|
+
[Attributes.BLOCK_FULL]: analysis.analysis.blockBlobsFull ? 'true' : 'false',
|
|
383
|
+
};
|
|
384
|
+
|
|
323
385
|
if (strategyResult.wouldBeIncluded) {
|
|
324
|
-
this.fishermanWouldBeIncluded.add(1, { ...
|
|
386
|
+
this.fishermanWouldBeIncluded.add(1, { ...inclusionAttributes, [Attributes.OK]: true });
|
|
325
387
|
} else {
|
|
326
388
|
this.fishermanWouldBeIncluded.add(1, {
|
|
327
|
-
...
|
|
389
|
+
...inclusionAttributes,
|
|
328
390
|
[Attributes.OK]: false,
|
|
329
391
|
...(strategyResult.exclusionReason && { [Attributes.ERROR_TYPE]: strategyResult.exclusionReason }),
|
|
330
392
|
});
|
|
@@ -334,17 +396,29 @@ export class SequencerMetrics {
|
|
|
334
396
|
// Record strategy-specific priority fee delta
|
|
335
397
|
if (strategyResult.priorityFeeDelta !== undefined) {
|
|
336
398
|
const priorityFeeDeltaGwei = Number(strategyResult.priorityFeeDelta) / 1e9;
|
|
337
|
-
|
|
399
|
+
const deltaAttributes = {
|
|
400
|
+
...strategyAttributes,
|
|
401
|
+
[Attributes.BLOCK_FULL]: analysis.analysis.blockBlobsFull ? 'true' : 'false',
|
|
402
|
+
};
|
|
403
|
+
this.fishermanPriorityFeeDelta.record(priorityFeeDeltaGwei, deltaAttributes);
|
|
338
404
|
}
|
|
339
405
|
|
|
340
406
|
// Record estimated cost if available
|
|
341
407
|
if (strategyResult.estimatedCostEth !== undefined) {
|
|
342
|
-
|
|
408
|
+
const costAttributes = {
|
|
409
|
+
...strategyAttributes,
|
|
410
|
+
[Attributes.BLOCK_FULL]: analysis.analysis.blockBlobsFull ? 'true' : 'false',
|
|
411
|
+
};
|
|
412
|
+
this.fishermanEstimatedCost.record(strategyResult.estimatedCostEth, costAttributes);
|
|
343
413
|
}
|
|
344
414
|
|
|
345
415
|
// Record estimated overpayment if available
|
|
346
416
|
if (strategyResult.estimatedOverpaymentEth !== undefined) {
|
|
347
|
-
|
|
417
|
+
const overpaymentAttributes = {
|
|
418
|
+
...strategyAttributes,
|
|
419
|
+
[Attributes.BLOCK_FULL]: analysis.analysis.blockBlobsFull ? 'true' : 'false',
|
|
420
|
+
};
|
|
421
|
+
this.fishermanEstimatedOverpayment.record(strategyResult.estimatedOverpaymentEth, overpaymentAttributes);
|
|
348
422
|
}
|
|
349
423
|
}
|
|
350
424
|
}
|
|
@@ -12,9 +12,9 @@ import type { DateProvider } from '@aztec/foundation/timer';
|
|
|
12
12
|
import type { TypedEventEmitter } from '@aztec/foundation/types';
|
|
13
13
|
import type { P2P } from '@aztec/p2p';
|
|
14
14
|
import type { SlasherClientInterface } from '@aztec/slasher';
|
|
15
|
-
import type {
|
|
15
|
+
import type { BlockData, L2BlockSink, L2BlockSource, ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
16
16
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
17
|
-
import {
|
|
17
|
+
import { getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
18
18
|
import {
|
|
19
19
|
type ResolvedSequencerConfig,
|
|
20
20
|
type SequencerConfig,
|
|
@@ -25,7 +25,7 @@ import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
|
25
25
|
import { pickFromSchema } from '@aztec/stdlib/schemas';
|
|
26
26
|
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
27
27
|
import { Attributes, type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
28
|
-
import { FullNodeCheckpointsBuilder, type ValidatorClient } from '@aztec/validator-client';
|
|
28
|
+
import { FullNodeCheckpointsBuilder, NodeKeystoreAdapter, type ValidatorClient } from '@aztec/validator-client';
|
|
29
29
|
|
|
30
30
|
import EventEmitter from 'node:events';
|
|
31
31
|
|
|
@@ -60,6 +60,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
60
60
|
/** The last slot for which we attempted to perform our voting duties with degraded block production */
|
|
61
61
|
private lastSlotForFallbackVote: SlotNumber | undefined;
|
|
62
62
|
|
|
63
|
+
/** The last slot for which we logged "no committee" warning, to avoid spam */
|
|
64
|
+
private lastSlotForNoCommitteeWarning: SlotNumber | undefined;
|
|
65
|
+
|
|
63
66
|
/** The last slot for which we triggered a checkpoint proposal job, to prevent duplicate attempts. */
|
|
64
67
|
private lastSlotForCheckpointProposalJob: SlotNumber | undefined;
|
|
65
68
|
|
|
@@ -72,14 +75,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
72
75
|
/** The maximum number of seconds that the sequencer can be into a slot to transition to a particular state. */
|
|
73
76
|
protected timetable!: SequencerTimetable;
|
|
74
77
|
|
|
75
|
-
// This shouldn't be here as this gets re-created each time we build/propose a block.
|
|
76
|
-
// But we have a number of tests that abuse/rely on this class having a permanent publisher.
|
|
77
|
-
// As long as those tests only configure a single publisher they will continue to work.
|
|
78
|
-
// This will get re-assigned every time the sequencer goes to build a new block to a publisher that is valid
|
|
79
|
-
// for the block proposer.
|
|
80
|
-
// TODO(palla/mbps): Remove this field and fix tests
|
|
81
|
-
protected publisher: SequencerPublisher | undefined;
|
|
82
|
-
|
|
83
78
|
/** Config for the sequencer */
|
|
84
79
|
protected config: ResolvedSequencerConfig = DefaultSequencerConfig;
|
|
85
80
|
|
|
@@ -115,7 +110,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
115
110
|
/** Updates sequencer config by the defined values and updates the timetable */
|
|
116
111
|
public updateConfig(config: Partial<SequencerConfig>) {
|
|
117
112
|
const filteredConfig = pickFromSchema(config, SequencerConfigSchema);
|
|
118
|
-
this.log.info(`Updated sequencer config`, omit(filteredConfig, '
|
|
113
|
+
this.log.info(`Updated sequencer config`, omit(filteredConfig, 'txPublicSetupAllowListExtend'));
|
|
119
114
|
this.config = merge(this.config, filteredConfig);
|
|
120
115
|
this.timetable = new SequencerTimetable(
|
|
121
116
|
{
|
|
@@ -131,10 +126,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
131
126
|
);
|
|
132
127
|
}
|
|
133
128
|
|
|
134
|
-
/** Initializes the sequencer (precomputes tables
|
|
135
|
-
public
|
|
129
|
+
/** Initializes the sequencer (precomputes tables). Takes about 3s. */
|
|
130
|
+
public init() {
|
|
136
131
|
getKzg();
|
|
137
|
-
this.publisher = (await this.publisherFactory.create(undefined)).publisher;
|
|
138
132
|
}
|
|
139
133
|
|
|
140
134
|
/** Starts the sequencer and moves to IDLE state. */
|
|
@@ -149,11 +143,16 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
149
143
|
this.log.info('Started sequencer');
|
|
150
144
|
}
|
|
151
145
|
|
|
146
|
+
/** Triggers an immediate run of the sequencer, bypassing the polling interval. */
|
|
147
|
+
public trigger() {
|
|
148
|
+
return this.runningPromise?.trigger();
|
|
149
|
+
}
|
|
150
|
+
|
|
152
151
|
/** Stops the sequencer from building blocks and moves to STOPPED state. */
|
|
153
152
|
public async stop(): Promise<void> {
|
|
154
153
|
this.log.info(`Stopping sequencer`);
|
|
155
154
|
this.setState(SequencerState.STOPPING, undefined, { force: true });
|
|
156
|
-
this.
|
|
155
|
+
this.publisherFactory.interruptAll();
|
|
157
156
|
await this.runningPromise?.stop();
|
|
158
157
|
this.setState(SequencerState.STOPPED, undefined, { force: true });
|
|
159
158
|
this.log.info('Stopped sequencer');
|
|
@@ -166,7 +165,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
166
165
|
} catch (err) {
|
|
167
166
|
this.emit('checkpoint-error', { error: err as Error });
|
|
168
167
|
if (err instanceof SequencerTooSlowError) {
|
|
169
|
-
// TODO(palla/mbps): Add missing states
|
|
170
168
|
// Log as warn only if we had to abort halfway through the block proposal
|
|
171
169
|
const logLvl = [SequencerState.INITIALIZING_CHECKPOINT, SequencerState.PROPOSER_CHECK].includes(
|
|
172
170
|
err.proposedState,
|
|
@@ -288,8 +286,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
288
286
|
|
|
289
287
|
const logCtx = {
|
|
290
288
|
now,
|
|
291
|
-
|
|
292
|
-
syncedToL2Slot: getSlotAtTimestamp(syncedTo.l1Timestamp, this.l1Constants),
|
|
289
|
+
syncedToL2Slot: syncedTo.syncedL2Slot,
|
|
293
290
|
slot,
|
|
294
291
|
slotTs: ts,
|
|
295
292
|
checkpointNumber,
|
|
@@ -307,12 +304,12 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
307
304
|
}
|
|
308
305
|
|
|
309
306
|
// Check that the slot is not taken by a block already (should never happen, since only us can propose for this slot)
|
|
310
|
-
if (syncedTo.
|
|
307
|
+
if (syncedTo.blockData && syncedTo.blockData.header.getSlot() >= slot) {
|
|
311
308
|
this.log.warn(
|
|
312
309
|
`Cannot propose block at next L2 slot ${slot} since that slot was taken by block ${syncedTo.blockNumber}`,
|
|
313
|
-
{ ...logCtx, block: syncedTo.
|
|
310
|
+
{ ...logCtx, block: syncedTo.blockData.header.toInspect() },
|
|
314
311
|
);
|
|
315
|
-
this.metrics.
|
|
312
|
+
this.metrics.recordCheckpointPrecheckFailed('slot_already_taken');
|
|
316
313
|
return undefined;
|
|
317
314
|
}
|
|
318
315
|
|
|
@@ -323,7 +320,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
323
320
|
const proposerForPublisher = this.config.fishermanMode ? undefined : proposer;
|
|
324
321
|
const { attestorAddress, publisher } = await this.publisherFactory.create(proposerForPublisher);
|
|
325
322
|
this.log.verbose(`Created publisher at address ${publisher.getSenderAddress()} for attestor ${attestorAddress}`);
|
|
326
|
-
this.publisher = publisher;
|
|
327
323
|
|
|
328
324
|
// In fisherman mode, set the actual proposer's address for simulations
|
|
329
325
|
if (this.config.fishermanMode && proposer) {
|
|
@@ -336,7 +332,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
336
332
|
|
|
337
333
|
// Check with the rollup contract if we can indeed propose at the next L2 slot. This check should not fail
|
|
338
334
|
// if all the previous checks are good, but we do it just in case.
|
|
339
|
-
const canProposeCheck = await publisher.
|
|
335
|
+
const canProposeCheck = await publisher.canProposeAt(
|
|
340
336
|
syncedTo.archive,
|
|
341
337
|
proposer ?? EthAddress.ZERO,
|
|
342
338
|
invalidateCheckpoint,
|
|
@@ -348,7 +344,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
348
344
|
logCtx,
|
|
349
345
|
);
|
|
350
346
|
this.emit('proposer-rollup-check-failed', { reason: 'Rollup contract check failed', slot });
|
|
351
|
-
this.metrics.
|
|
347
|
+
this.metrics.recordCheckpointPrecheckFailed('rollup_contract_check_failed');
|
|
352
348
|
return undefined;
|
|
353
349
|
}
|
|
354
350
|
|
|
@@ -358,7 +354,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
358
354
|
{ ...logCtx, rollup: canProposeCheck, expectedSlot: slot },
|
|
359
355
|
);
|
|
360
356
|
this.emit('proposer-rollup-check-failed', { reason: 'Slot mismatch', slot });
|
|
361
|
-
this.metrics.
|
|
357
|
+
this.metrics.recordCheckpointPrecheckFailed('slot_mismatch');
|
|
362
358
|
return undefined;
|
|
363
359
|
}
|
|
364
360
|
|
|
@@ -368,11 +364,12 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
368
364
|
{ ...logCtx, rollup: canProposeCheck, expectedSlot: slot },
|
|
369
365
|
);
|
|
370
366
|
this.emit('proposer-rollup-check-failed', { reason: 'Block mismatch', slot });
|
|
371
|
-
this.metrics.
|
|
367
|
+
this.metrics.recordCheckpointPrecheckFailed('block_number_mismatch');
|
|
372
368
|
return undefined;
|
|
373
369
|
}
|
|
374
370
|
|
|
375
371
|
this.lastSlotForCheckpointProposalJob = slot;
|
|
372
|
+
await this.p2pClient.prepareForSlot(slot);
|
|
376
373
|
this.log.info(`Preparing checkpoint proposal ${checkpointNumber} at slot ${slot}`, { ...logCtx, proposer });
|
|
377
374
|
|
|
378
375
|
// Create and return the checkpoint proposal job
|
|
@@ -429,6 +426,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
429
426
|
);
|
|
430
427
|
}
|
|
431
428
|
|
|
429
|
+
/**
|
|
430
|
+
* Returns the current sequencer state.
|
|
431
|
+
*/
|
|
432
|
+
public getState(): SequencerState {
|
|
433
|
+
return this.state;
|
|
434
|
+
}
|
|
435
|
+
|
|
432
436
|
/**
|
|
433
437
|
* Internal helper for setting the sequencer state and checks if we have enough time left in the slot to transition to the new state.
|
|
434
438
|
* @param proposedState - The new state to transition to.
|
|
@@ -475,16 +479,15 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
475
479
|
* We don't check against the previous block submitted since it may have been reorg'd out.
|
|
476
480
|
*/
|
|
477
481
|
protected async checkSync(args: { ts: bigint; slot: SlotNumber }): Promise<SequencerSyncCheckResult | undefined> {
|
|
478
|
-
// Check that the archiver
|
|
479
|
-
//
|
|
480
|
-
//
|
|
481
|
-
const
|
|
482
|
-
const { slot
|
|
483
|
-
if (
|
|
482
|
+
// Check that the archiver has fully synced the L2 slot before the one we want to propose in.
|
|
483
|
+
// The archiver reports sync progress via L1 block timestamps and synced checkpoint slots.
|
|
484
|
+
// See getSyncedL2SlotNumber for how missed L1 blocks are handled.
|
|
485
|
+
const syncedL2Slot = await this.l2BlockSource.getSyncedL2SlotNumber();
|
|
486
|
+
const { slot } = args;
|
|
487
|
+
if (syncedL2Slot === undefined || syncedL2Slot + 1 < slot) {
|
|
484
488
|
this.log.debug(`Cannot propose block at next L2 slot ${slot} due to pending sync from L1`, {
|
|
485
489
|
slot,
|
|
486
|
-
|
|
487
|
-
l1Timestamp,
|
|
490
|
+
syncedL2Slot,
|
|
488
491
|
});
|
|
489
492
|
return undefined;
|
|
490
493
|
}
|
|
@@ -524,24 +527,24 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
524
527
|
checkpointNumber: CheckpointNumber.ZERO,
|
|
525
528
|
blockNumber: BlockNumber.ZERO,
|
|
526
529
|
archive,
|
|
527
|
-
|
|
530
|
+
syncedL2Slot,
|
|
528
531
|
pendingChainValidationStatus,
|
|
529
532
|
};
|
|
530
533
|
}
|
|
531
534
|
|
|
532
|
-
const
|
|
533
|
-
if (!
|
|
535
|
+
const blockData = await this.l2BlockSource.getBlockData(blockNumber);
|
|
536
|
+
if (!blockData) {
|
|
534
537
|
// this shouldn't really happen because a moment ago we checked that all components were in sync
|
|
535
|
-
this.log.error(`Failed to get L2 block ${blockNumber} from the archiver with all components in sync`);
|
|
538
|
+
this.log.error(`Failed to get L2 block data ${blockNumber} from the archiver with all components in sync`);
|
|
536
539
|
return undefined;
|
|
537
540
|
}
|
|
538
541
|
|
|
539
542
|
return {
|
|
540
|
-
|
|
541
|
-
blockNumber:
|
|
542
|
-
checkpointNumber:
|
|
543
|
-
archive:
|
|
544
|
-
|
|
543
|
+
blockData,
|
|
544
|
+
blockNumber: blockData.header.getBlockNumber(),
|
|
545
|
+
checkpointNumber: blockData.checkpointNumber,
|
|
546
|
+
archive: blockData.archive.root,
|
|
547
|
+
syncedL2Slot,
|
|
545
548
|
pendingChainValidationStatus,
|
|
546
549
|
};
|
|
547
550
|
}
|
|
@@ -557,7 +560,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
557
560
|
proposer = await this.epochCache.getProposerAttesterAddressInSlot(slot);
|
|
558
561
|
} catch (e) {
|
|
559
562
|
if (e instanceof NoCommitteeError) {
|
|
560
|
-
this.
|
|
563
|
+
if (this.lastSlotForNoCommitteeWarning !== slot) {
|
|
564
|
+
this.lastSlotForNoCommitteeWarning = slot;
|
|
565
|
+
this.log.warn(`Cannot propose at next L2 slot ${slot} since the committee does not exist on L1`);
|
|
566
|
+
}
|
|
561
567
|
return [false, undefined];
|
|
562
568
|
}
|
|
563
569
|
this.log.error(`Error getting proposer for slot ${slot}`, e);
|
|
@@ -717,7 +723,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
717
723
|
syncedTo: SequencerSyncCheckResult,
|
|
718
724
|
currentSlot: SlotNumber,
|
|
719
725
|
): Promise<void> {
|
|
720
|
-
const { pendingChainValidationStatus,
|
|
726
|
+
const { pendingChainValidationStatus, syncedL2Slot } = syncedTo;
|
|
721
727
|
if (pendingChainValidationStatus.valid) {
|
|
722
728
|
return;
|
|
723
729
|
}
|
|
@@ -732,7 +738,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
732
738
|
|
|
733
739
|
const logData = {
|
|
734
740
|
invalidL1Timestamp: invalidCheckpointTimestamp,
|
|
735
|
-
|
|
741
|
+
syncedL2Slot,
|
|
736
742
|
invalidCheckpoint: pendingChainValidationStatus.checkpoint,
|
|
737
743
|
secondsBeforeInvalidatingBlockAsCommitteeMember,
|
|
738
744
|
secondsBeforeInvalidatingBlockAsNonCommitteeMember,
|
|
@@ -860,6 +866,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
860
866
|
return this.validatorClient?.getValidatorAddresses();
|
|
861
867
|
}
|
|
862
868
|
|
|
869
|
+
/** Updates the publisher factory's node keystore adapter after a keystore reload. */
|
|
870
|
+
public updatePublisherNodeKeyStore(adapter: NodeKeystoreAdapter): void {
|
|
871
|
+
this.publisherFactory.updateNodeKeyStore(adapter);
|
|
872
|
+
}
|
|
873
|
+
|
|
863
874
|
public getConfig() {
|
|
864
875
|
return this.config;
|
|
865
876
|
}
|
|
@@ -870,10 +881,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
870
881
|
}
|
|
871
882
|
|
|
872
883
|
type SequencerSyncCheckResult = {
|
|
873
|
-
|
|
884
|
+
blockData?: BlockData;
|
|
874
885
|
checkpointNumber: CheckpointNumber;
|
|
875
886
|
blockNumber: BlockNumber;
|
|
876
887
|
archive: Fr;
|
|
877
|
-
|
|
888
|
+
syncedL2Slot: SlotNumber;
|
|
878
889
|
pendingChainValidationStatus: ValidateCheckpointResult;
|
|
879
890
|
};
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Logger } from '@aztec/foundation/log';
|
|
2
|
+
import {
|
|
3
|
+
CHECKPOINT_ASSEMBLE_TIME,
|
|
4
|
+
CHECKPOINT_INITIALIZATION_TIME,
|
|
5
|
+
DEFAULT_P2P_PROPAGATION_TIME,
|
|
6
|
+
MIN_EXECUTION_TIME,
|
|
7
|
+
} from '@aztec/stdlib/timetable';
|
|
2
8
|
|
|
3
|
-
import { DEFAULT_ATTESTATION_PROPAGATION_TIME as DEFAULT_P2P_PROPAGATION_TIME } from '../config.js';
|
|
4
9
|
import { SequencerTooSlowError } from './errors.js';
|
|
5
10
|
import type { SequencerMetrics } from './metrics.js';
|
|
6
11
|
import { SequencerState } from './utils.js';
|
|
7
12
|
|
|
8
|
-
export const MIN_EXECUTION_TIME = 2;
|
|
9
|
-
export const CHECKPOINT_INITIALIZATION_TIME = 1;
|
|
10
|
-
export const CHECKPOINT_ASSEMBLE_TIME = 1;
|
|
11
|
-
|
|
12
13
|
export class SequencerTimetable {
|
|
13
14
|
/**
|
|
14
15
|
* How late into the slot can we be to start working. Computed as the total time needed for assembling and publishing a block,
|
|
@@ -79,7 +80,7 @@ export class SequencerTimetable {
|
|
|
79
80
|
enforce: boolean;
|
|
80
81
|
},
|
|
81
82
|
private readonly metrics?: SequencerMetrics,
|
|
82
|
-
private readonly log
|
|
83
|
+
private readonly log?: Logger,
|
|
83
84
|
) {
|
|
84
85
|
this.ethereumSlotDuration = opts.ethereumSlotDuration;
|
|
85
86
|
this.aztecSlotDuration = opts.aztecSlotDuration;
|
|
@@ -131,7 +132,7 @@ export class SequencerTimetable {
|
|
|
131
132
|
const initializeDeadline = this.aztecSlotDuration - minWorkToDo;
|
|
132
133
|
this.initializeDeadline = initializeDeadline;
|
|
133
134
|
|
|
134
|
-
this.log
|
|
135
|
+
this.log?.info(
|
|
135
136
|
`Sequencer timetable initialized with ${this.maxNumberOfBlocks} blocks per slot (${this.enforce ? 'enforced' : 'not enforced'})`,
|
|
136
137
|
{
|
|
137
138
|
ethereumSlotDuration: this.ethereumSlotDuration,
|
|
@@ -205,7 +206,7 @@ export class SequencerTimetable {
|
|
|
205
206
|
}
|
|
206
207
|
|
|
207
208
|
this.metrics?.recordStateTransitionBufferMs(Math.floor(bufferSeconds * 1000), newState);
|
|
208
|
-
this.log
|
|
209
|
+
this.log?.trace(`Enough time to transition to ${newState}`, { maxAllowedTime, secondsIntoSlot });
|
|
209
210
|
}
|
|
210
211
|
|
|
211
212
|
/**
|
|
@@ -241,7 +242,7 @@ export class SequencerTimetable {
|
|
|
241
242
|
const canStart = available >= this.minExecutionTime;
|
|
242
243
|
const deadline = secondsIntoSlot + available;
|
|
243
244
|
|
|
244
|
-
this.log
|
|
245
|
+
this.log?.verbose(
|
|
245
246
|
`${canStart ? 'Can' : 'Cannot'} start single-block checkpoint at ${secondsIntoSlot}s into slot`,
|
|
246
247
|
{ secondsIntoSlot, maxAllowed, available, deadline },
|
|
247
248
|
);
|
|
@@ -261,7 +262,7 @@ export class SequencerTimetable {
|
|
|
261
262
|
// Found an available sub-slot! Is this the last one?
|
|
262
263
|
const isLastBlock = subSlot === this.maxNumberOfBlocks;
|
|
263
264
|
|
|
264
|
-
this.log
|
|
265
|
+
this.log?.verbose(
|
|
265
266
|
`Can start ${isLastBlock ? 'last block' : 'block'} in sub-slot ${subSlot} with deadline ${deadline}s`,
|
|
266
267
|
{ secondsIntoSlot, deadline, timeUntilDeadline, subSlot, maxBlocks: this.maxNumberOfBlocks },
|
|
267
268
|
);
|
|
@@ -271,7 +272,7 @@ export class SequencerTimetable {
|
|
|
271
272
|
}
|
|
272
273
|
|
|
273
274
|
// No sub-slots available with enough time
|
|
274
|
-
this.log
|
|
275
|
+
this.log?.verbose(`No time left to start any more blocks`, {
|
|
275
276
|
secondsIntoSlot,
|
|
276
277
|
maxBlocks: this.maxNumberOfBlocks,
|
|
277
278
|
initializationOffset: this.initializationOffset,
|
package/src/sequencer/types.ts
CHANGED
|
@@ -2,5 +2,5 @@ import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
|
|
|
2
2
|
|
|
3
3
|
export type SequencerRollupConstants = Pick<
|
|
4
4
|
L1RollupConstants,
|
|
5
|
-
'ethereumSlotDuration' | 'l1GenesisTime' | 'slotDuration'
|
|
5
|
+
'ethereumSlotDuration' | 'l1GenesisTime' | 'slotDuration' | 'rollupManaLimit'
|
|
6
6
|
>;
|