@aztec/sequencer-client 3.0.0-nightly.20251111 → 3.0.0-nightly.20251113
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/config.d.ts.map +1 -1
- package/dest/config.js +5 -0
- package/dest/publisher/config.d.ts +2 -0
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +5 -0
- package/dest/publisher/sequencer-publisher.d.ts +11 -0
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +92 -41
- package/dest/sequencer/metrics.d.ts +8 -0
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +38 -0
- package/dest/sequencer/sequencer.d.ts +2 -0
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +114 -24
- package/package.json +27 -27
- package/src/config.ts +6 -0
- package/src/publisher/config.ts +8 -0
- package/src/publisher/sequencer-publisher.ts +101 -41
- package/src/sequencer/metrics.ts +48 -0
- package/src/sequencer/sequencer.ts +120 -23
|
@@ -102,6 +102,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
102
102
|
/** The last slot for which we attempted to vote when sync failed, to prevent duplicate attempts. */
|
|
103
103
|
private lastSlotForVoteWhenSyncFailed: bigint | undefined;
|
|
104
104
|
|
|
105
|
+
/** The last slot for which we built a validation block in fisherman mode, to prevent duplicate attempts. */
|
|
106
|
+
private lastSlotForValidationBlock: bigint | undefined;
|
|
107
|
+
|
|
105
108
|
/** The maximum number of seconds that the sequencer can be into a slot to transition to a particular state. */
|
|
106
109
|
protected timetable!: SequencerTimetable;
|
|
107
110
|
protected enforceTimeTable: boolean = false;
|
|
@@ -133,6 +136,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
133
136
|
) {
|
|
134
137
|
super();
|
|
135
138
|
|
|
139
|
+
// Add [FISHERMAN] prefix to logger if in fisherman mode
|
|
140
|
+
if (this.config.fishermanMode) {
|
|
141
|
+
this.log = log.createChild('[FISHERMAN]');
|
|
142
|
+
}
|
|
143
|
+
|
|
136
144
|
this.metrics = new SequencerMetrics(telemetry, this.rollupContract, 'Sequencer');
|
|
137
145
|
// Initialize config
|
|
138
146
|
this.updateConfig(this.config);
|
|
@@ -289,28 +297,55 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
289
297
|
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
290
298
|
const [canPropose, proposer] = await this.checkCanPropose(slot);
|
|
291
299
|
|
|
292
|
-
// If we are not a proposer
|
|
300
|
+
// If we are not a proposer check if we should invalidate a invalid block, and bail
|
|
293
301
|
if (!canPropose) {
|
|
294
302
|
await this.considerInvalidatingBlock(syncedTo, slot);
|
|
295
303
|
return;
|
|
296
304
|
}
|
|
297
305
|
|
|
306
|
+
// In fisherman mode, check if we've already validated this slot to prevent duplicate attempts
|
|
307
|
+
if (this.config.fishermanMode) {
|
|
308
|
+
if (this.lastSlotForValidationBlock === slot) {
|
|
309
|
+
this.log.trace(`Already validated block building for slot ${slot} (skipping)`, { slot });
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
this.log.debug(
|
|
313
|
+
`Building validation block for slot ${slot} (actual proposer: ${proposer?.toString() ?? 'none'})`,
|
|
314
|
+
{ slot, proposer: proposer?.toString() },
|
|
315
|
+
);
|
|
316
|
+
// Mark this slot as being validated
|
|
317
|
+
this.lastSlotForValidationBlock = slot;
|
|
318
|
+
}
|
|
319
|
+
|
|
298
320
|
// Check that the slot is not taken by a block already (should never happen, since only us can propose for this slot)
|
|
299
321
|
if (syncedTo.block && syncedTo.block.header.getSlot() >= slot) {
|
|
300
322
|
this.log.warn(
|
|
301
323
|
`Cannot propose block at next L2 slot ${slot} since that slot was taken by block ${syncedTo.blockNumber}`,
|
|
302
324
|
{ ...syncLogData, block: syncedTo.block.header.toInspect() },
|
|
303
325
|
);
|
|
326
|
+
this.metrics.recordBlockProposalPrecheckFailed('slot_already_taken');
|
|
304
327
|
return;
|
|
305
328
|
}
|
|
306
329
|
|
|
307
330
|
// We now need to get ourselves a publisher.
|
|
308
331
|
// The returned attestor will be the one we provided if we provided one.
|
|
309
332
|
// Otherwise it will be a valid attestor for the returned publisher.
|
|
310
|
-
|
|
333
|
+
// In fisherman mode, pass undefined to use the fisherman's own keystore instead of the actual proposer's
|
|
334
|
+
const { attestorAddress, publisher } = await this.publisherFactory.create(
|
|
335
|
+
this.config.fishermanMode ? undefined : proposer,
|
|
336
|
+
);
|
|
311
337
|
this.log.verbose(`Created publisher at address ${publisher.getSenderAddress()} for attestor ${attestorAddress}`);
|
|
312
338
|
this.publisher = publisher;
|
|
313
339
|
|
|
340
|
+
// In fisherman mode, set the actual proposer's address for simulations
|
|
341
|
+
if (this.config.fishermanMode) {
|
|
342
|
+
if (proposer) {
|
|
343
|
+
publisher.setProposerAddressForSimulation(proposer);
|
|
344
|
+
this.log.debug(`Set proposer address ${proposer} for simulation in fisherman mode`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Get proposer credentials
|
|
314
349
|
const coinbase = this.validatorClient!.getCoinbaseForAttestor(attestorAddress);
|
|
315
350
|
const feeRecipient = this.validatorClient!.getFeeRecipientForAttestor(attestorAddress);
|
|
316
351
|
|
|
@@ -331,6 +366,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
331
366
|
syncLogData,
|
|
332
367
|
);
|
|
333
368
|
this.emit('proposer-rollup-check-failed', { reason: 'Rollup contract check failed' });
|
|
369
|
+
this.metrics.recordBlockProposalPrecheckFailed('rollup_contract_check_failed');
|
|
334
370
|
return;
|
|
335
371
|
} else if (canProposeCheck.slot !== slot) {
|
|
336
372
|
this.log.warn(
|
|
@@ -338,6 +374,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
338
374
|
{ ...syncLogData, rollup: canProposeCheck, newBlockNumber, expectedSlot: slot },
|
|
339
375
|
);
|
|
340
376
|
this.emit('proposer-rollup-check-failed', { reason: 'Slot mismatch' });
|
|
377
|
+
this.metrics.recordBlockProposalPrecheckFailed('slot_mismatch');
|
|
341
378
|
return;
|
|
342
379
|
} else if (canProposeCheck.blockNumber !== BigInt(newBlockNumber)) {
|
|
343
380
|
this.log.warn(
|
|
@@ -345,6 +382,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
345
382
|
{ ...syncLogData, rollup: canProposeCheck, newBlockNumber, expectedSlot: slot },
|
|
346
383
|
);
|
|
347
384
|
this.emit('proposer-rollup-check-failed', { reason: 'Block mismatch' });
|
|
385
|
+
this.metrics.recordBlockProposalPrecheckFailed('block_number_mismatch');
|
|
348
386
|
return;
|
|
349
387
|
}
|
|
350
388
|
|
|
@@ -358,6 +396,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
358
396
|
);
|
|
359
397
|
|
|
360
398
|
// Enqueue governance and slashing votes (returns promises that will be awaited later)
|
|
399
|
+
// In fisherman mode, we simulate slashing but don't actually publish to L1
|
|
361
400
|
const votesPromises = this.enqueueGovernanceAndSlashingVotes(
|
|
362
401
|
publisher,
|
|
363
402
|
attestorAddress,
|
|
@@ -385,15 +424,39 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
385
424
|
// Wait until the voting promises have resolved, so all requests are enqueued
|
|
386
425
|
await Promise.all(votesPromises);
|
|
387
426
|
|
|
388
|
-
//
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
427
|
+
// In fisherman mode, we don't publish to L1
|
|
428
|
+
if (this.config.fishermanMode) {
|
|
429
|
+
// Clear pending requests
|
|
430
|
+
publisher.clearPendingRequests();
|
|
431
|
+
|
|
432
|
+
if (block) {
|
|
433
|
+
this.log.info(`Validation block building SUCCEEDED for slot ${slot}`, {
|
|
434
|
+
blockNumber: newBlockNumber,
|
|
435
|
+
slot: Number(slot),
|
|
436
|
+
archive: block.archive.toString(),
|
|
437
|
+
txCount: block.body.txEffects.length,
|
|
438
|
+
});
|
|
439
|
+
this.lastBlockPublished = block;
|
|
440
|
+
this.metrics.recordBlockProposalSuccess();
|
|
441
|
+
} else {
|
|
442
|
+
// Block building failed in fisherman mode
|
|
443
|
+
this.log.warn(`Validation block building FAILED for slot ${slot}`, {
|
|
444
|
+
blockNumber: newBlockNumber,
|
|
445
|
+
slot: Number(slot),
|
|
446
|
+
});
|
|
447
|
+
this.metrics.recordBlockProposalFailed('block_build_failed');
|
|
448
|
+
}
|
|
449
|
+
} else {
|
|
450
|
+
// Normal mode: send the tx to L1
|
|
451
|
+
const l1Response = await publisher.sendRequests();
|
|
452
|
+
const proposedBlock = l1Response?.successfulActions.find(a => a === 'propose');
|
|
453
|
+
if (proposedBlock) {
|
|
454
|
+
this.lastBlockPublished = block;
|
|
455
|
+
this.emit('block-published', { blockNumber: newBlockNumber, slot: Number(slot) });
|
|
456
|
+
await this.metrics.incFilledSlot(publisher.getSenderAddress().toString(), coinbase);
|
|
457
|
+
} else if (block) {
|
|
458
|
+
this.emit('block-publish-failed', l1Response ?? {});
|
|
459
|
+
}
|
|
397
460
|
}
|
|
398
461
|
|
|
399
462
|
this.setState(SequencerState.IDLE, undefined);
|
|
@@ -449,6 +512,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
449
512
|
} else {
|
|
450
513
|
this.log.error(`Error building/enqueuing block`, err, { blockNumber: newBlockNumber, slot });
|
|
451
514
|
}
|
|
515
|
+
this.metrics.recordBlockProposalFailed(err.name || 'unknown_error');
|
|
452
516
|
}
|
|
453
517
|
} else {
|
|
454
518
|
this.log.verbose(
|
|
@@ -456,6 +520,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
456
520
|
{ chainTipArchive, blockNumber: newBlockNumber, slot },
|
|
457
521
|
);
|
|
458
522
|
this.emit('tx-count-check-failed', { minTxs: this.minTxsPerBlock, availableTxs: pendingTxCount });
|
|
523
|
+
this.metrics.recordBlockProposalFailed('insufficient_txs');
|
|
459
524
|
}
|
|
460
525
|
return block;
|
|
461
526
|
}
|
|
@@ -631,18 +696,28 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
631
696
|
},
|
|
632
697
|
);
|
|
633
698
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
this.
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
699
|
+
// In fisherman mode, skip attestation collection
|
|
700
|
+
let attestationsAndSigners: CommitteeAttestationsAndSigners;
|
|
701
|
+
if (this.config.fishermanMode) {
|
|
702
|
+
this.log.debug('Skipping attestation collection');
|
|
703
|
+
attestationsAndSigners = CommitteeAttestationsAndSigners.empty();
|
|
704
|
+
} else {
|
|
705
|
+
this.log.debug('Collecting attestations');
|
|
706
|
+
attestationsAndSigners = await this.collectAttestations(block, usedTxs, proposerAddress);
|
|
707
|
+
this.log.verbose(
|
|
708
|
+
`Collected ${attestationsAndSigners.attestations.length} attestations for block ${blockNumber} at slot ${slot}`,
|
|
709
|
+
{ blockHash, blockNumber, slot },
|
|
710
|
+
);
|
|
711
|
+
}
|
|
640
712
|
|
|
713
|
+
// In fisherman mode, skip attestation signing
|
|
641
714
|
const attestationsAndSignersSignature =
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
715
|
+
this.config.fishermanMode || !this.validatorClient
|
|
716
|
+
? Signature.empty()
|
|
717
|
+
: await this.validatorClient.signAttestationsAndSigners(
|
|
718
|
+
attestationsAndSigners,
|
|
719
|
+
proposerAddress ?? publisher.getSenderAddress(),
|
|
720
|
+
);
|
|
646
721
|
|
|
647
722
|
await this.enqueuePublishL2Block(
|
|
648
723
|
block,
|
|
@@ -950,7 +1025,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
950
1025
|
const enqueueSlashingPromise = this.slasherClient
|
|
951
1026
|
? this.slasherClient
|
|
952
1027
|
.getProposerActions(slot)
|
|
953
|
-
.then(actions =>
|
|
1028
|
+
.then(actions => {
|
|
1029
|
+
// Record metrics for fisherman mode
|
|
1030
|
+
if (this.config.fishermanMode && actions.length > 0) {
|
|
1031
|
+
this.log.debug(`Fisherman mode: simulating ${actions.length} slashing action(s) for slot ${slot}`, {
|
|
1032
|
+
slot,
|
|
1033
|
+
actionCount: actions.length,
|
|
1034
|
+
});
|
|
1035
|
+
this.metrics.recordSlashingAttempt(actions.length);
|
|
1036
|
+
}
|
|
1037
|
+
// Enqueue the actions to fully simulate L1 tx building (they won't be sent in fisherman mode)
|
|
1038
|
+
return publisher.enqueueSlashingActions(actions, slot, timestamp, attestorAddress, signerFn);
|
|
1039
|
+
})
|
|
954
1040
|
.catch(err => {
|
|
955
1041
|
this.log.error(`Error enqueuing slashing actions`, err, { slot });
|
|
956
1042
|
return false;
|
|
@@ -970,6 +1056,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
970
1056
|
*/
|
|
971
1057
|
protected async checkCanPropose(slot: bigint): Promise<[boolean, EthAddress | undefined]> {
|
|
972
1058
|
let proposer: EthAddress | undefined;
|
|
1059
|
+
|
|
973
1060
|
try {
|
|
974
1061
|
proposer = await this.epochCache.getProposerAttesterAddressInSlot(slot);
|
|
975
1062
|
} catch (e) {
|
|
@@ -985,6 +1072,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
985
1072
|
if (proposer === undefined) {
|
|
986
1073
|
return [true, undefined];
|
|
987
1074
|
}
|
|
1075
|
+
// In fisherman mode, just return the current proposer
|
|
1076
|
+
if (this.config.fishermanMode) {
|
|
1077
|
+
return [true, proposer];
|
|
1078
|
+
}
|
|
988
1079
|
|
|
989
1080
|
const validatorAddresses = this.validatorClient!.getValidatorAddresses();
|
|
990
1081
|
const weAreProposer = validatorAddresses.some(addr => addr.equals(proposer));
|
|
@@ -1129,7 +1220,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
1129
1220
|
);
|
|
1130
1221
|
|
|
1131
1222
|
publisher.enqueueInvalidateBlock(invalidateBlock);
|
|
1132
|
-
|
|
1223
|
+
|
|
1224
|
+
if (!this.config.fishermanMode) {
|
|
1225
|
+
await publisher.sendRequests();
|
|
1226
|
+
} else {
|
|
1227
|
+
this.log.info('Invalidating block in fisherman mode, clearing pending requests');
|
|
1228
|
+
publisher.clearPendingRequests();
|
|
1229
|
+
}
|
|
1133
1230
|
}
|
|
1134
1231
|
|
|
1135
1232
|
private getSlotStartBuildTimestamp(slotNumber: number | bigint): number {
|