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