@aztec/sequencer-client 0.0.1-commit.88e6f9396 → 0.0.1-commit.8c0b8ff
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 +5 -1
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +6 -12
- package/dest/config.d.ts +3 -3
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +1 -2
- package/dest/global_variable_builder/global_builder.d.ts +13 -7
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +22 -22
- 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 +1 -5
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +1 -6
- package/dest/publisher/index.d.ts +1 -2
- package/dest/publisher/index.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.d.ts +1 -1
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +0 -14
- package/dest/publisher/sequencer-publisher.d.ts +11 -16
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +53 -289
- package/dest/sequencer/checkpoint_proposal_job.d.ts +4 -10
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +75 -117
- 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/events.d.ts +1 -2
- package/dest/sequencer/events.d.ts.map +1 -1
- package/dest/sequencer/metrics.d.ts +1 -5
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +0 -11
- package/dest/sequencer/sequencer.d.ts +5 -4
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +50 -59
- package/dest/test/mock_checkpoint_builder.d.ts +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +2 -0
- package/package.json +28 -27
- package/src/client/sequencer-client.ts +9 -11
- package/src/config.ts +0 -4
- package/src/global_variable_builder/global_builder.ts +22 -24
- package/src/global_variable_builder/index.ts +1 -1
- package/src/publisher/config.ts +0 -9
- package/src/publisher/index.ts +0 -3
- package/src/publisher/sequencer-publisher-factory.ts +0 -15
- package/src/publisher/sequencer-publisher.ts +51 -274
- package/src/sequencer/checkpoint_proposal_job.ts +66 -139
- package/src/sequencer/checkpoint_voter.ts +1 -12
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/metrics.ts +0 -14
- package/src/sequencer/sequencer.ts +58 -84
- package/src/test/mock_checkpoint_builder.ts +2 -0
- package/dest/publisher/l1_tx_failed_store/factory.d.ts +0 -11
- package/dest/publisher/l1_tx_failed_store/factory.d.ts.map +0 -1
- package/dest/publisher/l1_tx_failed_store/factory.js +0 -22
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +0 -59
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +0 -1
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +0 -1
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +0 -15
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +0 -1
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +0 -34
- package/dest/publisher/l1_tx_failed_store/index.d.ts +0 -4
- package/dest/publisher/l1_tx_failed_store/index.d.ts.map +0 -1
- package/dest/publisher/l1_tx_failed_store/index.js +0 -2
- package/src/publisher/l1_tx_failed_store/factory.ts +0 -32
- package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +0 -55
- package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +0 -46
- package/src/publisher/l1_tx_failed_store/index.ts +0 -3
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
MaliciousCommitteeAttestationsAndSigners,
|
|
32
32
|
} from '@aztec/stdlib/block';
|
|
33
33
|
import { type Checkpoint, validateCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
34
|
-
import { getSlotStartBuildTimestamp
|
|
34
|
+
import { getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
35
35
|
import { Gas } from '@aztec/stdlib/gas';
|
|
36
36
|
import {
|
|
37
37
|
type BlockBuilderOptions,
|
|
@@ -77,10 +77,8 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
77
77
|
protected readonly log: Logger;
|
|
78
78
|
|
|
79
79
|
constructor(
|
|
80
|
-
private readonly
|
|
81
|
-
private readonly
|
|
82
|
-
private readonly epochNow: EpochNumber,
|
|
83
|
-
private readonly targetEpoch: EpochNumber,
|
|
80
|
+
private readonly epoch: EpochNumber,
|
|
81
|
+
private readonly slot: SlotNumber,
|
|
84
82
|
private readonly checkpointNumber: CheckpointNumber,
|
|
85
83
|
private readonly syncedToBlockNumber: BlockNumber,
|
|
86
84
|
// TODO(palla/mbps): Can we remove the proposer in favor of attestorAddress? Need to check fisherman-node flows.
|
|
@@ -108,20 +106,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
108
106
|
public readonly tracer: Tracer,
|
|
109
107
|
bindings?: LoggerBindings,
|
|
110
108
|
) {
|
|
111
|
-
this.log = createLogger('sequencer:checkpoint-proposal', {
|
|
112
|
-
...bindings,
|
|
113
|
-
instanceId: `slot-${this.slotNow}`,
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/** The wall-clock slot during which the proposer builds. */
|
|
118
|
-
private get slot(): SlotNumber {
|
|
119
|
-
return this.slotNow;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/** The wall-clock epoch. */
|
|
123
|
-
private get epoch(): EpochNumber {
|
|
124
|
-
return this.epochNow;
|
|
109
|
+
this.log = createLogger('sequencer:checkpoint-proposal', { ...bindings, instanceId: `slot-${slot}` });
|
|
125
110
|
}
|
|
126
111
|
|
|
127
112
|
/**
|
|
@@ -134,7 +119,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
134
119
|
// In fisherman mode, we simulate slashing but don't actually publish to L1
|
|
135
120
|
// These are constant for the whole slot, so we only enqueue them once
|
|
136
121
|
const votesPromises = new CheckpointVoter(
|
|
137
|
-
this.
|
|
122
|
+
this.slot,
|
|
138
123
|
this.publisher,
|
|
139
124
|
this.attestorAddress,
|
|
140
125
|
this.validatorClient,
|
|
@@ -161,29 +146,6 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
161
146
|
return;
|
|
162
147
|
}
|
|
163
148
|
|
|
164
|
-
// If pipelining, wait until the submission slot so L1 recognizes the pipelined proposer
|
|
165
|
-
if (this.epochCache.isProposerPipeliningEnabled()) {
|
|
166
|
-
const submissionSlotTimestamp =
|
|
167
|
-
getTimestampForSlot(this.targetSlot, this.l1Constants) - BigInt(this.l1Constants.ethereumSlotDuration);
|
|
168
|
-
this.log.info(`Waiting until submission slot ${this.targetSlot} for L1 submission`, {
|
|
169
|
-
slot: this.slot,
|
|
170
|
-
submissionSlot: this.targetSlot,
|
|
171
|
-
submissionSlotTimestamp,
|
|
172
|
-
});
|
|
173
|
-
await sleepUntil(new Date(Number(submissionSlotTimestamp) * 1000), this.dateProvider.nowAsDate());
|
|
174
|
-
|
|
175
|
-
// After waking, verify the parent checkpoint wasn't pruned during the sleep.
|
|
176
|
-
// We check L1's pending tip directly instead of canProposeAt, which also validates the proposer
|
|
177
|
-
// identity and would fail because the timestamp resolves to a different slot's proposer.
|
|
178
|
-
const l1Tips = await this.publisher.rollupContract.getTips();
|
|
179
|
-
if (l1Tips.pending < this.checkpointNumber - 1) {
|
|
180
|
-
this.log.warn(
|
|
181
|
-
`Parent checkpoint was pruned during pipelining sleep (L1 pending=${l1Tips.pending}, expected>=${this.checkpointNumber - 1}), skipping L1 submission for checkpoint ${this.checkpointNumber}`,
|
|
182
|
-
);
|
|
183
|
-
return undefined;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
149
|
// Then send everything to L1
|
|
188
150
|
const l1Response = await this.publisher.sendRequests();
|
|
189
151
|
const proposedAction = l1Response?.successfulActions.find(a => a === 'propose');
|
|
@@ -202,7 +164,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
202
164
|
return {
|
|
203
165
|
// nullish operator needed for tests
|
|
204
166
|
[Attributes.COINBASE]: this.validatorClient.getCoinbaseForAttestor(this.attestorAddress)?.toString(),
|
|
205
|
-
[Attributes.SLOT_NUMBER]: this.
|
|
167
|
+
[Attributes.SLOT_NUMBER]: this.slot,
|
|
206
168
|
};
|
|
207
169
|
})
|
|
208
170
|
private async proposeCheckpoint(): Promise<Checkpoint | undefined> {
|
|
@@ -212,15 +174,8 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
212
174
|
const feeRecipient = this.validatorClient.getFeeRecipientForAttestor(this.attestorAddress);
|
|
213
175
|
|
|
214
176
|
// Start the checkpoint
|
|
215
|
-
this.setStateFn(SequencerState.INITIALIZING_CHECKPOINT, this.
|
|
216
|
-
this.
|
|
217
|
-
buildSlot: this.slot,
|
|
218
|
-
submissionSlot: this.targetSlot,
|
|
219
|
-
pipelining: this.epochCache.isProposerPipeliningEnabled(),
|
|
220
|
-
proposer: this.proposer?.toString(),
|
|
221
|
-
coinbase: coinbase.toString(),
|
|
222
|
-
});
|
|
223
|
-
this.metrics.incOpenSlot(this.targetSlot, this.proposer?.toString() ?? 'unknown');
|
|
177
|
+
this.setStateFn(SequencerState.INITIALIZING_CHECKPOINT, this.slot);
|
|
178
|
+
this.metrics.incOpenSlot(this.slot, this.proposer?.toString() ?? 'unknown');
|
|
224
179
|
|
|
225
180
|
// Enqueues checkpoint invalidation (constant for the whole slot)
|
|
226
181
|
if (this.invalidateCheckpoint && !this.config.skipInvalidateBlockAsProposer) {
|
|
@@ -231,7 +186,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
231
186
|
const checkpointGlobalVariables = await this.globalsBuilder.buildCheckpointGlobalVariables(
|
|
232
187
|
coinbase,
|
|
233
188
|
feeRecipient,
|
|
234
|
-
this.
|
|
189
|
+
this.slot,
|
|
235
190
|
);
|
|
236
191
|
|
|
237
192
|
// Collect L1 to L2 messages for the checkpoint and compute their hash
|
|
@@ -239,7 +194,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
239
194
|
const inHash = computeInHashFromL1ToL2Messages(l1ToL2Messages);
|
|
240
195
|
|
|
241
196
|
// Collect the out hashes of all the checkpoints before this one in the same epoch
|
|
242
|
-
const previousCheckpointOutHashes = (await this.l2BlockSource.getCheckpointsDataForEpoch(this.
|
|
197
|
+
const previousCheckpointOutHashes = (await this.l2BlockSource.getCheckpointsDataForEpoch(this.epoch))
|
|
243
198
|
.filter(c => c.checkpointNumber < this.checkpointNumber)
|
|
244
199
|
.map(c => c.checkpointOutHash);
|
|
245
200
|
|
|
@@ -296,8 +251,8 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
296
251
|
}
|
|
297
252
|
|
|
298
253
|
if (blocksInCheckpoint.length === 0) {
|
|
299
|
-
this.log.warn(`No blocks were built for slot ${this.
|
|
300
|
-
this.eventEmitter.emit('checkpoint-empty', { slot: this.
|
|
254
|
+
this.log.warn(`No blocks were built for slot ${this.slot}`, { slot: this.slot });
|
|
255
|
+
this.eventEmitter.emit('checkpoint-empty', { slot: this.slot });
|
|
301
256
|
return undefined;
|
|
302
257
|
}
|
|
303
258
|
|
|
@@ -305,14 +260,14 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
305
260
|
if (minBlocksForCheckpoint !== undefined && blocksInCheckpoint.length < minBlocksForCheckpoint) {
|
|
306
261
|
this.log.warn(
|
|
307
262
|
`Checkpoint has fewer blocks than minimum (${blocksInCheckpoint.length} < ${minBlocksForCheckpoint}), skipping proposal`,
|
|
308
|
-
{ slot: this.
|
|
263
|
+
{ slot: this.slot, blocksBuilt: blocksInCheckpoint.length, minBlocksForCheckpoint },
|
|
309
264
|
);
|
|
310
265
|
return undefined;
|
|
311
266
|
}
|
|
312
267
|
|
|
313
268
|
// Assemble and broadcast the checkpoint proposal, including the last block that was not
|
|
314
269
|
// broadcasted yet, and wait to collect the committee attestations.
|
|
315
|
-
this.setStateFn(SequencerState.ASSEMBLING_CHECKPOINT, this.
|
|
270
|
+
this.setStateFn(SequencerState.ASSEMBLING_CHECKPOINT, this.slot);
|
|
316
271
|
const checkpoint = await checkpointBuilder.completeCheckpoint();
|
|
317
272
|
|
|
318
273
|
// Final validation: per-block limits are only checked if the operator set them explicitly.
|
|
@@ -343,10 +298,10 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
343
298
|
// Do not collect attestations nor publish to L1 in fisherman mode
|
|
344
299
|
if (this.config.fishermanMode) {
|
|
345
300
|
this.log.info(
|
|
346
|
-
`Built checkpoint for slot ${this.
|
|
301
|
+
`Built checkpoint for slot ${this.slot} with ${blocksInCheckpoint.length} blocks. ` +
|
|
347
302
|
`Skipping proposal in fisherman mode.`,
|
|
348
303
|
{
|
|
349
|
-
slot: this.
|
|
304
|
+
slot: this.slot,
|
|
350
305
|
checkpoint: checkpoint.header.toInspect(),
|
|
351
306
|
blocksBuilt: blocksInCheckpoint.length,
|
|
352
307
|
},
|
|
@@ -375,7 +330,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
375
330
|
const blockProposedAt = this.dateProvider.now();
|
|
376
331
|
await this.p2pClient.broadcastCheckpointProposal(proposal);
|
|
377
332
|
|
|
378
|
-
this.setStateFn(SequencerState.COLLECTING_ATTESTATIONS, this.
|
|
333
|
+
this.setStateFn(SequencerState.COLLECTING_ATTESTATIONS, this.slot);
|
|
379
334
|
const attestations = await this.waitForAttestations(proposal);
|
|
380
335
|
const blockAttestedAt = this.dateProvider.now();
|
|
381
336
|
|
|
@@ -388,7 +343,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
388
343
|
attestationsSignature = await this.validatorClient.signAttestationsAndSigners(
|
|
389
344
|
attestations,
|
|
390
345
|
signer,
|
|
391
|
-
this.
|
|
346
|
+
this.slot,
|
|
392
347
|
this.checkpointNumber,
|
|
393
348
|
);
|
|
394
349
|
} catch (err) {
|
|
@@ -401,10 +356,10 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
401
356
|
}
|
|
402
357
|
|
|
403
358
|
// Enqueue publishing the checkpoint to L1
|
|
404
|
-
this.setStateFn(SequencerState.PUBLISHING_CHECKPOINT, this.
|
|
359
|
+
this.setStateFn(SequencerState.PUBLISHING_CHECKPOINT, this.slot);
|
|
405
360
|
const aztecSlotDuration = this.l1Constants.slotDuration;
|
|
406
|
-
const
|
|
407
|
-
const txTimeoutAt = new Date((
|
|
361
|
+
const slotStartBuildTimestamp = this.getSlotStartBuildTimestamp();
|
|
362
|
+
const txTimeoutAt = new Date((slotStartBuildTimestamp + aztecSlotDuration) * 1000);
|
|
408
363
|
|
|
409
364
|
// If we have been configured to potentially skip publishing checkpoint then roll the dice here
|
|
410
365
|
if (
|
|
@@ -453,6 +408,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
453
408
|
const blocksInCheckpoint: L2Block[] = [];
|
|
454
409
|
const txHashesAlreadyIncluded = new Set<string>();
|
|
455
410
|
const initialBlockNumber = BlockNumber(this.syncedToBlockNumber + 1);
|
|
411
|
+
const slot = this.slot;
|
|
456
412
|
|
|
457
413
|
// Last block in the checkpoint will usually be flagged as pending broadcast, so we send it along with the checkpoint proposal
|
|
458
414
|
let blockPendingBroadcast: { block: L2Block; txs: Tx[] } | undefined = undefined;
|
|
@@ -466,11 +422,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
466
422
|
const timingInfo = this.timetable.canStartNextBlock(secondsIntoSlot);
|
|
467
423
|
|
|
468
424
|
if (!timingInfo.canStart) {
|
|
469
|
-
this.log.debug(`Not enough time left in slot to start another block`, {
|
|
470
|
-
slot: this.targetSlot,
|
|
471
|
-
blocksBuilt,
|
|
472
|
-
secondsIntoSlot,
|
|
473
|
-
});
|
|
425
|
+
this.log.debug(`Not enough time left in slot to start another block`, { slot, blocksBuilt, secondsIntoSlot });
|
|
474
426
|
break;
|
|
475
427
|
}
|
|
476
428
|
|
|
@@ -502,11 +454,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
502
454
|
} else if ('error' in buildResult) {
|
|
503
455
|
// If there was an error building the block, just exit the loop and give up the rest of the slot
|
|
504
456
|
if (!(buildResult.error instanceof SequencerInterruptedError)) {
|
|
505
|
-
this.log.warn(`Halting block building for slot ${
|
|
506
|
-
slot: this.targetSlot,
|
|
507
|
-
blocksBuilt,
|
|
508
|
-
error: buildResult.error,
|
|
509
|
-
});
|
|
457
|
+
this.log.warn(`Halting block building for slot ${slot}`, { slot, blocksBuilt, error: buildResult.error });
|
|
510
458
|
}
|
|
511
459
|
break;
|
|
512
460
|
}
|
|
@@ -515,15 +463,11 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
515
463
|
blocksInCheckpoint.push(block);
|
|
516
464
|
usedTxs.forEach(tx => txHashesAlreadyIncluded.add(tx.txHash.toString()));
|
|
517
465
|
|
|
518
|
-
// If this is the last block,
|
|
519
|
-
// so we can build the checkpoint and start collecting attestations.
|
|
466
|
+
// If this is the last block, send the proposed block to the archiver,
|
|
467
|
+
// and exit the loop now so we can build the checkpoint and start collecting attestations.
|
|
520
468
|
if (timingInfo.isLastBlock) {
|
|
521
469
|
await this.syncProposedBlockToArchiver(block);
|
|
522
|
-
this.log.verbose(`Completed final block ${blockNumber} for slot ${
|
|
523
|
-
slot: this.targetSlot,
|
|
524
|
-
blockNumber,
|
|
525
|
-
blocksBuilt,
|
|
526
|
-
});
|
|
470
|
+
this.log.verbose(`Completed final block ${blockNumber} for slot ${slot}`, { slot, blockNumber, blocksBuilt });
|
|
527
471
|
blockPendingBroadcast = { block, txs: usedTxs };
|
|
528
472
|
break;
|
|
529
473
|
}
|
|
@@ -546,8 +490,8 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
546
490
|
await this.waitUntilNextSubslot(timingInfo.deadline);
|
|
547
491
|
}
|
|
548
492
|
|
|
549
|
-
this.log.verbose(`Block building loop completed for slot ${this.
|
|
550
|
-
slot: this.
|
|
493
|
+
this.log.verbose(`Block building loop completed for slot ${this.slot}`, {
|
|
494
|
+
slot: this.slot,
|
|
551
495
|
blocksBuilt: blocksInCheckpoint.length,
|
|
552
496
|
});
|
|
553
497
|
|
|
@@ -579,10 +523,8 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
579
523
|
/** Sleeps until it is time to produce the next block in the slot */
|
|
580
524
|
@trackSpan('CheckpointProposalJob.waitUntilNextSubslot')
|
|
581
525
|
private async waitUntilNextSubslot(nextSubslotStart: number) {
|
|
582
|
-
this.setStateFn(SequencerState.WAITING_UNTIL_NEXT_BLOCK, this.
|
|
583
|
-
this.log.verbose(`Waiting until time for the next block at ${nextSubslotStart}s into slot`, {
|
|
584
|
-
slot: this.targetSlot,
|
|
585
|
-
});
|
|
526
|
+
this.setStateFn(SequencerState.WAITING_UNTIL_NEXT_BLOCK, this.slot);
|
|
527
|
+
this.log.verbose(`Waiting until time for the next block at ${nextSubslotStart}s into slot`, { slot: this.slot });
|
|
586
528
|
await this.waitUntilTimeInSlot(nextSubslotStart);
|
|
587
529
|
}
|
|
588
530
|
|
|
@@ -603,7 +545,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
603
545
|
opts;
|
|
604
546
|
|
|
605
547
|
this.log.verbose(
|
|
606
|
-
`Preparing block ${blockNumber} index ${indexWithinCheckpoint} at checkpoint ${this.checkpointNumber} for slot ${this.
|
|
548
|
+
`Preparing block ${blockNumber} index ${indexWithinCheckpoint} at checkpoint ${this.checkpointNumber} for slot ${this.slot}`,
|
|
607
549
|
{ ...checkpointBuilder.getConstantData(), ...opts },
|
|
608
550
|
);
|
|
609
551
|
|
|
@@ -612,10 +554,10 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
612
554
|
const { availableTxs, canStartBuilding, minTxs } = await this.waitForMinTxs(opts);
|
|
613
555
|
if (!canStartBuilding) {
|
|
614
556
|
this.log.warn(
|
|
615
|
-
`Not enough txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.
|
|
616
|
-
{ blockNumber, slot: this.
|
|
557
|
+
`Not enough txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.slot} (got ${availableTxs} txs but needs ${minTxs})`,
|
|
558
|
+
{ blockNumber, slot: this.slot, indexWithinCheckpoint },
|
|
617
559
|
);
|
|
618
|
-
this.eventEmitter.emit('block-tx-count-check-failed', { minTxs, availableTxs, slot: this.
|
|
560
|
+
this.eventEmitter.emit('block-tx-count-check-failed', { minTxs, availableTxs, slot: this.slot });
|
|
619
561
|
this.metrics.recordBlockProposalFailed('insufficient_txs');
|
|
620
562
|
return undefined;
|
|
621
563
|
}
|
|
@@ -628,10 +570,10 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
628
570
|
);
|
|
629
571
|
|
|
630
572
|
this.log.debug(
|
|
631
|
-
`Building block ${blockNumber} at index ${indexWithinCheckpoint} for slot ${this.
|
|
632
|
-
{ slot: this.
|
|
573
|
+
`Building block ${blockNumber} at index ${indexWithinCheckpoint} for slot ${this.slot} with ${availableTxs} available txs`,
|
|
574
|
+
{ slot: this.slot, blockNumber, indexWithinCheckpoint },
|
|
633
575
|
);
|
|
634
|
-
this.setStateFn(SequencerState.CREATING_BLOCK, this.
|
|
576
|
+
this.setStateFn(SequencerState.CREATING_BLOCK, this.slot);
|
|
635
577
|
|
|
636
578
|
// Per-block limits are operator overrides (from SEQ_MAX_L2_BLOCK_GAS etc.) further capped
|
|
637
579
|
// by remaining checkpoint-level budgets inside CheckpointBuilder before each block is built.
|
|
@@ -666,19 +608,16 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
666
608
|
|
|
667
609
|
if (buildResult.status === 'insufficient-valid-txs') {
|
|
668
610
|
this.log.warn(
|
|
669
|
-
`Block ${blockNumber} at index ${indexWithinCheckpoint} on slot ${this.
|
|
611
|
+
`Block ${blockNumber} at index ${indexWithinCheckpoint} on slot ${this.slot} has too few valid txs to be proposed`,
|
|
670
612
|
{
|
|
671
|
-
slot: this.
|
|
613
|
+
slot: this.slot,
|
|
672
614
|
blockNumber,
|
|
673
615
|
numTxs: buildResult.processedCount,
|
|
674
616
|
indexWithinCheckpoint,
|
|
675
617
|
minValidTxs,
|
|
676
618
|
},
|
|
677
619
|
);
|
|
678
|
-
this.eventEmitter.emit('block-build-failed', {
|
|
679
|
-
reason: `Insufficient valid txs`,
|
|
680
|
-
slot: this.targetSlot,
|
|
681
|
-
});
|
|
620
|
+
this.eventEmitter.emit('block-build-failed', { reason: `Insufficient valid txs`, slot: this.slot });
|
|
682
621
|
this.metrics.recordBlockProposalFailed('insufficient_valid_txs');
|
|
683
622
|
return undefined;
|
|
684
623
|
}
|
|
@@ -698,24 +637,17 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
698
637
|
const manaPerSec = block.header.totalManaUsed.toNumberUnsafe() / (blockBuildDuration / 1000);
|
|
699
638
|
|
|
700
639
|
this.log.info(
|
|
701
|
-
`Built block ${block.number} at checkpoint ${this.checkpointNumber} for slot ${this.
|
|
640
|
+
`Built block ${block.number} at checkpoint ${this.checkpointNumber} for slot ${this.slot} with ${numTxs} txs`,
|
|
702
641
|
{ blockHash, txHashes, manaPerSec, ...blockStats },
|
|
703
642
|
);
|
|
704
643
|
|
|
705
|
-
this.eventEmitter.emit('block-proposed', {
|
|
706
|
-
blockNumber: block.number,
|
|
707
|
-
slot: this.targetSlot,
|
|
708
|
-
buildSlot: this.slotNow,
|
|
709
|
-
});
|
|
644
|
+
this.eventEmitter.emit('block-proposed', { blockNumber: block.number, slot: this.slot });
|
|
710
645
|
this.metrics.recordBuiltBlock(blockBuildDuration, block.header.totalManaUsed.toNumberUnsafe());
|
|
711
646
|
|
|
712
647
|
return { block, usedTxs };
|
|
713
648
|
} catch (err: any) {
|
|
714
|
-
this.eventEmitter.emit('block-build-failed', {
|
|
715
|
-
|
|
716
|
-
slot: this.targetSlot,
|
|
717
|
-
});
|
|
718
|
-
this.log.error(`Error building block`, err, { blockNumber, slot: this.targetSlot });
|
|
649
|
+
this.eventEmitter.emit('block-build-failed', { reason: err.message, slot: this.slot });
|
|
650
|
+
this.log.error(`Error building block`, err, { blockNumber, slot: this.slot });
|
|
719
651
|
this.metrics.recordBlockProposalFailed(err.name || 'unknown_error');
|
|
720
652
|
this.metrics.recordFailedBlock();
|
|
721
653
|
return { error: err };
|
|
@@ -775,10 +707,10 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
775
707
|
}
|
|
776
708
|
|
|
777
709
|
// Wait a bit before checking again
|
|
778
|
-
this.setStateFn(SequencerState.WAITING_FOR_TXS, this.
|
|
710
|
+
this.setStateFn(SequencerState.WAITING_FOR_TXS, this.slot);
|
|
779
711
|
this.log.verbose(
|
|
780
|
-
`Waiting for enough txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.
|
|
781
|
-
{ blockNumber, slot: this.
|
|
712
|
+
`Waiting for enough txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.slot} (have ${availableTxs} but need ${minTxs})`,
|
|
713
|
+
{ blockNumber, slot: this.slot, indexWithinCheckpoint },
|
|
782
714
|
);
|
|
783
715
|
await this.waitForTxsPollingInterval();
|
|
784
716
|
availableTxs = await this.p2pClient.getPendingTxCount();
|
|
@@ -922,20 +854,11 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
922
854
|
this.log.warn(`Shuffling attestation ordering in checkpoint for slot ${slotNumber} (proposer #${proposerIndex})`);
|
|
923
855
|
|
|
924
856
|
const shuffled = [...attestations];
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
for (let k = 0; k < shuffled.length; k++) {
|
|
931
|
-
if (!shuffled[k].signature.isEmpty() && k !== proposerIndex) {
|
|
932
|
-
swappable.push(k);
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
if (swappable.length >= 2) {
|
|
936
|
-
const [i, j] = [swappable[0], swappable[1]];
|
|
937
|
-
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
938
|
-
}
|
|
857
|
+
const [i, j] = [(proposerIndex + 1) % shuffled.length, (proposerIndex + 2) % shuffled.length];
|
|
858
|
+
const valueI = shuffled[i];
|
|
859
|
+
const valueJ = shuffled[j];
|
|
860
|
+
shuffled[i] = valueJ;
|
|
861
|
+
shuffled[j] = valueI;
|
|
939
862
|
|
|
940
863
|
const signers = new CommitteeAttestationsAndSigners(attestations).getSigners();
|
|
941
864
|
return new MaliciousCommitteeAttestationsAndSigners(shuffled, signers);
|
|
@@ -958,9 +881,13 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
958
881
|
* Adds the proposed block to the archiver so it's available via P2P.
|
|
959
882
|
* Gossip doesn't echo messages back to the sender, so the proposer's archiver/world-state
|
|
960
883
|
* would never receive its own block without this explicit sync.
|
|
884
|
+
*
|
|
885
|
+
* In fisherman mode we skip this push: the fisherman builds blocks locally for validation
|
|
886
|
+
* and fee analysis only, and pushing them to the archiver causes spurious reorg cascades
|
|
887
|
+
* whenever the real proposer's block arrives from L1.
|
|
961
888
|
*/
|
|
962
889
|
private async syncProposedBlockToArchiver(block: L2Block): Promise<void> {
|
|
963
|
-
if (this.config.skipPushProposedBlocksToArchiver !== false) {
|
|
890
|
+
if (this.config.skipPushProposedBlocksToArchiver !== false || this.config.fishermanMode) {
|
|
964
891
|
this.log.warn(`Skipping push of proposed block ${block.number} to archiver`, {
|
|
965
892
|
blockNumber: block.number,
|
|
966
893
|
slot: block.header.globalVariables.slotNumber,
|
|
@@ -978,19 +905,19 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
978
905
|
private async handleCheckpointEndAsFisherman(checkpoint: Checkpoint | undefined) {
|
|
979
906
|
// Perform L1 fee analysis before clearing requests
|
|
980
907
|
// The callback is invoked asynchronously after the next block is mined
|
|
981
|
-
const feeAnalysis = await this.publisher.analyzeL1Fees(this.
|
|
908
|
+
const feeAnalysis = await this.publisher.analyzeL1Fees(this.slot, analysis =>
|
|
982
909
|
this.metrics.recordFishermanFeeAnalysis(analysis),
|
|
983
910
|
);
|
|
984
911
|
|
|
985
912
|
if (checkpoint) {
|
|
986
|
-
this.log.info(`Validation checkpoint building SUCCEEDED for slot ${this.
|
|
913
|
+
this.log.info(`Validation checkpoint building SUCCEEDED for slot ${this.slot}`, {
|
|
987
914
|
...checkpoint.toCheckpointInfo(),
|
|
988
915
|
...checkpoint.getStats(),
|
|
989
916
|
feeAnalysisId: feeAnalysis?.id,
|
|
990
917
|
});
|
|
991
918
|
} else {
|
|
992
|
-
this.log.warn(`Validation block building FAILED for slot ${this.
|
|
993
|
-
slot: this.
|
|
919
|
+
this.log.warn(`Validation block building FAILED for slot ${this.slot}`, {
|
|
920
|
+
slot: this.slot,
|
|
994
921
|
feeAnalysisId: feeAnalysis?.id,
|
|
995
922
|
});
|
|
996
923
|
this.metrics.recordCheckpointProposalFailed('block_build_failed');
|
|
@@ -1004,15 +931,15 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
1004
931
|
*/
|
|
1005
932
|
private handleHASigningError(err: any, errorContext: string): boolean {
|
|
1006
933
|
if (err instanceof DutyAlreadySignedError) {
|
|
1007
|
-
this.log.info(`${errorContext} for slot ${this.
|
|
1008
|
-
slot: this.
|
|
934
|
+
this.log.info(`${errorContext} for slot ${this.slot} already signed by another HA node, yielding`, {
|
|
935
|
+
slot: this.slot,
|
|
1009
936
|
signedByNode: err.signedByNode,
|
|
1010
937
|
});
|
|
1011
938
|
return true;
|
|
1012
939
|
}
|
|
1013
940
|
if (err instanceof SlashingProtectionError) {
|
|
1014
|
-
this.log.info(`${errorContext} for slot ${this.
|
|
1015
|
-
slot: this.
|
|
941
|
+
this.log.info(`${errorContext} for slot ${this.slot} blocked by slashing protection, yielding`, {
|
|
942
|
+
slot: this.slot,
|
|
1016
943
|
existingMessageHash: err.existingMessageHash,
|
|
1017
944
|
attemptedMessageHash: err.attemptedMessageHash,
|
|
1018
945
|
});
|
|
@@ -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/events.ts
CHANGED
|
@@ -13,7 +13,7 @@ export type SequencerEvents = {
|
|
|
13
13
|
['proposer-rollup-check-failed']: (args: { reason: string; slot: SlotNumber }) => void;
|
|
14
14
|
['block-tx-count-check-failed']: (args: { minTxs: number; availableTxs: number; slot: SlotNumber }) => void;
|
|
15
15
|
['block-build-failed']: (args: { reason: string; slot: SlotNumber }) => void;
|
|
16
|
-
['block-proposed']: (args: { blockNumber: BlockNumber; slot: SlotNumber
|
|
16
|
+
['block-proposed']: (args: { blockNumber: BlockNumber; slot: SlotNumber }) => void;
|
|
17
17
|
['checkpoint-empty']: (args: { slot: SlotNumber }) => void;
|
|
18
18
|
['checkpoint-publish-failed']: (args: {
|
|
19
19
|
slot: SlotNumber;
|
package/src/sequencer/metrics.ts
CHANGED
|
@@ -49,8 +49,6 @@ export class SequencerMetrics {
|
|
|
49
49
|
private checkpointBlockCount: Gauge;
|
|
50
50
|
private checkpointTxCount: Gauge;
|
|
51
51
|
private checkpointTotalMana: Gauge;
|
|
52
|
-
private pipelineDepth: Gauge;
|
|
53
|
-
private pipelineDiscards: UpDownCounter;
|
|
54
52
|
|
|
55
53
|
// Fisherman fee analysis metrics
|
|
56
54
|
private fishermanWouldBeIncluded: UpDownCounter;
|
|
@@ -145,10 +143,6 @@ export class SequencerMetrics {
|
|
|
145
143
|
|
|
146
144
|
this.slashingAttempts = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_SLASHING_ATTEMPTS_COUNT);
|
|
147
145
|
|
|
148
|
-
this.pipelineDepth = this.meter.createGauge(Metrics.SEQUENCER_PIPELINE_DEPTH);
|
|
149
|
-
this.pipelineDiscards = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_PIPELINE_DISCARDS_COUNT);
|
|
150
|
-
this.pipelineDepth.record(0);
|
|
151
|
-
|
|
152
146
|
// Fisherman fee analysis metrics
|
|
153
147
|
this.fishermanWouldBeIncluded = createUpDownCounterWithDefault(
|
|
154
148
|
this.meter,
|
|
@@ -240,14 +234,6 @@ export class SequencerMetrics {
|
|
|
240
234
|
});
|
|
241
235
|
}
|
|
242
236
|
|
|
243
|
-
recordPipelineDepth(depth: number) {
|
|
244
|
-
this.pipelineDepth.record(depth);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
recordPipelineDiscard(count = 1) {
|
|
248
|
-
this.pipelineDiscards.add(count);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
237
|
incOpenSlot(slot: SlotNumber, proposer: string) {
|
|
252
238
|
// sequencer went through the loop a second time. Noop
|
|
253
239
|
if (slot === this.lastSeenSlot) {
|