@aztec/sequencer-client 0.0.1-commit.b655e406 → 0.0.1-commit.d3ec352c
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/index.d.ts +1 -1
- package/dest/client/sequencer-client.d.ts +1 -1
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +2 -1
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +9 -0
- package/dest/global_variable_builder/global_builder.d.ts +3 -6
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +9 -6
- package/dest/global_variable_builder/index.d.ts +1 -1
- package/dest/index.d.ts +1 -1
- package/dest/publisher/config.d.ts +3 -1
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +5 -0
- package/dest/publisher/index.d.ts +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-metrics.d.ts +1 -1
- package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.d.ts +35 -29
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +115 -62
- package/dest/sequencer/block_builder.d.ts +3 -2
- package/dest/sequencer/block_builder.d.ts.map +1 -1
- package/dest/sequencer/block_builder.js +12 -8
- package/dest/sequencer/config.d.ts +1 -1
- package/dest/sequencer/errors.d.ts +1 -1
- package/dest/sequencer/errors.d.ts.map +1 -1
- package/dest/sequencer/index.d.ts +1 -1
- package/dest/sequencer/metrics.d.ts +11 -2
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +38 -0
- package/dest/sequencer/sequencer.d.ts +19 -27
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +175 -51
- package/dest/sequencer/timetable.d.ts +1 -1
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/utils.d.ts +1 -1
- package/dest/test/index.d.ts +1 -1
- package/dest/tx_validator/nullifier_cache.d.ts +1 -1
- package/dest/tx_validator/nullifier_cache.d.ts.map +1 -1
- package/dest/tx_validator/tx_validator_factory.d.ts +4 -3
- package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
- package/package.json +31 -30
- package/src/client/sequencer-client.ts +2 -2
- package/src/config.ts +10 -0
- package/src/global_variable_builder/global_builder.ts +13 -9
- package/src/publisher/config.ts +8 -0
- package/src/publisher/sequencer-publisher-factory.ts +2 -1
- package/src/publisher/sequencer-publisher.ts +155 -84
- package/src/sequencer/block_builder.ts +15 -11
- package/src/sequencer/metrics.ts +51 -2
- package/src/sequencer/sequencer.ts +215 -72
- package/src/tx_validator/tx_validator_factory.ts +2 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { L2Block } from '@aztec/aztec.js/block';
|
|
2
|
-
import {
|
|
2
|
+
import { BLOBS_PER_CHECKPOINT, FIELDS_PER_BLOB, INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
|
|
3
3
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
4
4
|
import { FormattedViemError, NoCommitteeError, type RollupContract } from '@aztec/ethereum';
|
|
5
|
+
import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
5
6
|
import { omit, pick } from '@aztec/foundation/collection';
|
|
6
7
|
import { randomInt } from '@aztec/foundation/crypto';
|
|
7
8
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
@@ -17,6 +18,7 @@ import {
|
|
|
17
18
|
CommitteeAttestation,
|
|
18
19
|
CommitteeAttestationsAndSigners,
|
|
19
20
|
type L2BlockSource,
|
|
21
|
+
MaliciousCommitteeAttestationsAndSigners,
|
|
20
22
|
type ValidateBlockResult,
|
|
21
23
|
} from '@aztec/stdlib/block';
|
|
22
24
|
import { type L1RollupConstants, getSlotAtTimestamp, getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
@@ -60,7 +62,7 @@ export type SequencerEvents = {
|
|
|
60
62
|
oldState: SequencerState;
|
|
61
63
|
newState: SequencerState;
|
|
62
64
|
secondsIntoSlot?: number;
|
|
63
|
-
slotNumber?:
|
|
65
|
+
slotNumber?: SlotNumber;
|
|
64
66
|
}) => void;
|
|
65
67
|
['proposer-rollup-check-failed']: (args: { reason: string }) => void;
|
|
66
68
|
['tx-count-check-failed']: (args: { minTxs: number; availableTxs: number }) => void;
|
|
@@ -71,7 +73,7 @@ export type SequencerEvents = {
|
|
|
71
73
|
sentActions?: Action[];
|
|
72
74
|
expiredActions?: Action[];
|
|
73
75
|
}) => void;
|
|
74
|
-
['block-published']: (args: { blockNumber:
|
|
76
|
+
['block-published']: (args: { blockNumber: BlockNumber; slot: number }) => void;
|
|
75
77
|
};
|
|
76
78
|
|
|
77
79
|
/**
|
|
@@ -99,7 +101,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
99
101
|
private governanceProposerPayload: EthAddress | undefined;
|
|
100
102
|
|
|
101
103
|
/** The last slot for which we attempted to vote when sync failed, to prevent duplicate attempts. */
|
|
102
|
-
private lastSlotForVoteWhenSyncFailed:
|
|
104
|
+
private lastSlotForVoteWhenSyncFailed: SlotNumber | undefined;
|
|
105
|
+
|
|
106
|
+
/** The last slot for which we built a validation block in fisherman mode, to prevent duplicate attempts. */
|
|
107
|
+
private lastSlotForValidationBlock: SlotNumber | undefined;
|
|
103
108
|
|
|
104
109
|
/** The maximum number of seconds that the sequencer can be into a slot to transition to a particular state. */
|
|
105
110
|
protected timetable!: SequencerTimetable;
|
|
@@ -132,6 +137,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
132
137
|
) {
|
|
133
138
|
super();
|
|
134
139
|
|
|
140
|
+
// Add [FISHERMAN] prefix to logger if in fisherman mode
|
|
141
|
+
if (this.config.fishermanMode) {
|
|
142
|
+
this.log = log.createChild('[FISHERMAN]');
|
|
143
|
+
}
|
|
144
|
+
|
|
135
145
|
this.metrics = new SequencerMetrics(telemetry, this.rollupContract, 'Sequencer');
|
|
136
146
|
// Initialize config
|
|
137
147
|
this.updateConfig(this.config);
|
|
@@ -271,7 +281,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
271
281
|
}
|
|
272
282
|
|
|
273
283
|
const chainTipArchive = syncedTo.archive;
|
|
274
|
-
const newBlockNumber = syncedTo.blockNumber + 1;
|
|
284
|
+
const newBlockNumber = BlockNumber(syncedTo.blockNumber + 1);
|
|
275
285
|
|
|
276
286
|
const syncLogData = {
|
|
277
287
|
now,
|
|
@@ -288,28 +298,55 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
288
298
|
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
289
299
|
const [canPropose, proposer] = await this.checkCanPropose(slot);
|
|
290
300
|
|
|
291
|
-
// If we are not a proposer
|
|
301
|
+
// If we are not a proposer check if we should invalidate a invalid block, and bail
|
|
292
302
|
if (!canPropose) {
|
|
293
303
|
await this.considerInvalidatingBlock(syncedTo, slot);
|
|
294
304
|
return;
|
|
295
305
|
}
|
|
296
306
|
|
|
307
|
+
// In fisherman mode, check if we've already validated this slot to prevent duplicate attempts
|
|
308
|
+
if (this.config.fishermanMode) {
|
|
309
|
+
if (this.lastSlotForValidationBlock === slot) {
|
|
310
|
+
this.log.trace(`Already validated block building for slot ${slot} (skipping)`, { slot });
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
this.log.debug(
|
|
314
|
+
`Building validation block for slot ${slot} (actual proposer: ${proposer?.toString() ?? 'none'})`,
|
|
315
|
+
{ slot, proposer: proposer?.toString() },
|
|
316
|
+
);
|
|
317
|
+
// Mark this slot as being validated
|
|
318
|
+
this.lastSlotForValidationBlock = slot;
|
|
319
|
+
}
|
|
320
|
+
|
|
297
321
|
// Check that the slot is not taken by a block already (should never happen, since only us can propose for this slot)
|
|
298
322
|
if (syncedTo.block && syncedTo.block.header.getSlot() >= slot) {
|
|
299
323
|
this.log.warn(
|
|
300
324
|
`Cannot propose block at next L2 slot ${slot} since that slot was taken by block ${syncedTo.blockNumber}`,
|
|
301
325
|
{ ...syncLogData, block: syncedTo.block.header.toInspect() },
|
|
302
326
|
);
|
|
327
|
+
this.metrics.recordBlockProposalPrecheckFailed('slot_already_taken');
|
|
303
328
|
return;
|
|
304
329
|
}
|
|
305
330
|
|
|
306
331
|
// We now need to get ourselves a publisher.
|
|
307
332
|
// The returned attestor will be the one we provided if we provided one.
|
|
308
333
|
// Otherwise it will be a valid attestor for the returned publisher.
|
|
309
|
-
|
|
334
|
+
// In fisherman mode, pass undefined to use the fisherman's own keystore instead of the actual proposer's
|
|
335
|
+
const { attestorAddress, publisher } = await this.publisherFactory.create(
|
|
336
|
+
this.config.fishermanMode ? undefined : proposer,
|
|
337
|
+
);
|
|
310
338
|
this.log.verbose(`Created publisher at address ${publisher.getSenderAddress()} for attestor ${attestorAddress}`);
|
|
311
339
|
this.publisher = publisher;
|
|
312
340
|
|
|
341
|
+
// In fisherman mode, set the actual proposer's address for simulations
|
|
342
|
+
if (this.config.fishermanMode) {
|
|
343
|
+
if (proposer) {
|
|
344
|
+
publisher.setProposerAddressForSimulation(proposer);
|
|
345
|
+
this.log.debug(`Set proposer address ${proposer} for simulation in fisherman mode`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Get proposer credentials
|
|
313
350
|
const coinbase = this.validatorClient!.getCoinbaseForAttestor(attestorAddress);
|
|
314
351
|
const feeRecipient = this.validatorClient!.getFeeRecipientForAttestor(attestorAddress);
|
|
315
352
|
|
|
@@ -330,6 +367,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
330
367
|
syncLogData,
|
|
331
368
|
);
|
|
332
369
|
this.emit('proposer-rollup-check-failed', { reason: 'Rollup contract check failed' });
|
|
370
|
+
this.metrics.recordBlockProposalPrecheckFailed('rollup_contract_check_failed');
|
|
333
371
|
return;
|
|
334
372
|
} else if (canProposeCheck.slot !== slot) {
|
|
335
373
|
this.log.warn(
|
|
@@ -337,13 +375,15 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
337
375
|
{ ...syncLogData, rollup: canProposeCheck, newBlockNumber, expectedSlot: slot },
|
|
338
376
|
);
|
|
339
377
|
this.emit('proposer-rollup-check-failed', { reason: 'Slot mismatch' });
|
|
378
|
+
this.metrics.recordBlockProposalPrecheckFailed('slot_mismatch');
|
|
340
379
|
return;
|
|
341
|
-
} else if (canProposeCheck.
|
|
380
|
+
} else if (canProposeCheck.checkpointNumber !== CheckpointNumber.fromBlockNumber(newBlockNumber)) {
|
|
342
381
|
this.log.warn(
|
|
343
|
-
`Cannot propose block due to block mismatch with rollup contract (this can be caused by a pending archiver sync). Expected block ${newBlockNumber} but got ${canProposeCheck.
|
|
382
|
+
`Cannot propose block due to block mismatch with rollup contract (this can be caused by a pending archiver sync). Expected block ${newBlockNumber} but got ${canProposeCheck.checkpointNumber}.`,
|
|
344
383
|
{ ...syncLogData, rollup: canProposeCheck, newBlockNumber, expectedSlot: slot },
|
|
345
384
|
);
|
|
346
385
|
this.emit('proposer-rollup-check-failed', { reason: 'Block mismatch' });
|
|
386
|
+
this.metrics.recordBlockProposalPrecheckFailed('block_number_mismatch');
|
|
347
387
|
return;
|
|
348
388
|
}
|
|
349
389
|
|
|
@@ -357,6 +397,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
357
397
|
);
|
|
358
398
|
|
|
359
399
|
// Enqueue governance and slashing votes (returns promises that will be awaited later)
|
|
400
|
+
// In fisherman mode, we simulate slashing but don't actually publish to L1
|
|
360
401
|
const votesPromises = this.enqueueGovernanceAndSlashingVotes(
|
|
361
402
|
publisher,
|
|
362
403
|
attestorAddress,
|
|
@@ -371,6 +412,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
371
412
|
|
|
372
413
|
// Actual block building
|
|
373
414
|
this.setState(SequencerState.INITIALIZING_PROPOSAL, slot);
|
|
415
|
+
this.metrics.incOpenSlot(slot, proposer?.toString() ?? 'unknown');
|
|
374
416
|
const block: L2Block | undefined = await this.tryBuildBlockAndEnqueuePublish(
|
|
375
417
|
slot,
|
|
376
418
|
proposer,
|
|
@@ -384,15 +426,39 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
384
426
|
// Wait until the voting promises have resolved, so all requests are enqueued
|
|
385
427
|
await Promise.all(votesPromises);
|
|
386
428
|
|
|
387
|
-
//
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
429
|
+
// In fisherman mode, we don't publish to L1
|
|
430
|
+
if (this.config.fishermanMode) {
|
|
431
|
+
// Clear pending requests
|
|
432
|
+
publisher.clearPendingRequests();
|
|
433
|
+
|
|
434
|
+
if (block) {
|
|
435
|
+
this.log.info(`Validation block building SUCCEEDED for slot ${slot}`, {
|
|
436
|
+
blockNumber: newBlockNumber,
|
|
437
|
+
slot: Number(slot),
|
|
438
|
+
archive: block.archive.toString(),
|
|
439
|
+
txCount: block.body.txEffects.length,
|
|
440
|
+
});
|
|
441
|
+
this.lastBlockPublished = block;
|
|
442
|
+
this.metrics.recordBlockProposalSuccess();
|
|
443
|
+
} else {
|
|
444
|
+
// Block building failed in fisherman mode
|
|
445
|
+
this.log.warn(`Validation block building FAILED for slot ${slot}`, {
|
|
446
|
+
blockNumber: newBlockNumber,
|
|
447
|
+
slot: Number(slot),
|
|
448
|
+
});
|
|
449
|
+
this.metrics.recordBlockProposalFailed('block_build_failed');
|
|
450
|
+
}
|
|
451
|
+
} else {
|
|
452
|
+
// Normal mode: send the tx to L1
|
|
453
|
+
const l1Response = await publisher.sendRequests();
|
|
454
|
+
const proposedBlock = l1Response?.successfulActions.find(a => a === 'propose');
|
|
455
|
+
if (proposedBlock) {
|
|
456
|
+
this.lastBlockPublished = block;
|
|
457
|
+
this.emit('block-published', { blockNumber: newBlockNumber, slot: Number(slot) });
|
|
458
|
+
await this.metrics.incFilledSlot(publisher.getSenderAddress().toString(), coinbase);
|
|
459
|
+
} else if (block) {
|
|
460
|
+
this.emit('block-publish-failed', l1Response ?? {});
|
|
461
|
+
}
|
|
396
462
|
}
|
|
397
463
|
|
|
398
464
|
this.setState(SequencerState.IDLE, undefined);
|
|
@@ -400,9 +466,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
400
466
|
|
|
401
467
|
/** Tries building a block proposal, and if successful, enqueues it for publishing. */
|
|
402
468
|
private async tryBuildBlockAndEnqueuePublish(
|
|
403
|
-
slot:
|
|
469
|
+
slot: SlotNumber,
|
|
404
470
|
proposer: EthAddress | undefined,
|
|
405
|
-
newBlockNumber:
|
|
471
|
+
newBlockNumber: BlockNumber,
|
|
406
472
|
publisher: SequencerPublisher,
|
|
407
473
|
newGlobalVariables: GlobalVariables,
|
|
408
474
|
chainTipArchive: Fr,
|
|
@@ -421,6 +487,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
421
487
|
...newGlobalVariables,
|
|
422
488
|
timestamp: newGlobalVariables.timestamp,
|
|
423
489
|
lastArchiveRoot: chainTipArchive,
|
|
490
|
+
blockHeadersHash: Fr.ZERO,
|
|
424
491
|
contentCommitment: ContentCommitment.empty(),
|
|
425
492
|
totalManaUsed: Fr.ZERO,
|
|
426
493
|
});
|
|
@@ -448,6 +515,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
448
515
|
} else {
|
|
449
516
|
this.log.error(`Error building/enqueuing block`, err, { blockNumber: newBlockNumber, slot });
|
|
450
517
|
}
|
|
518
|
+
this.metrics.recordBlockProposalFailed(err.name || 'unknown_error');
|
|
451
519
|
}
|
|
452
520
|
} else {
|
|
453
521
|
this.log.verbose(
|
|
@@ -455,6 +523,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
455
523
|
{ chainTipArchive, blockNumber: newBlockNumber, slot },
|
|
456
524
|
);
|
|
457
525
|
this.emit('tx-count-check-failed', { minTxs: this.minTxsPerBlock, availableTxs: pendingTxCount });
|
|
526
|
+
this.metrics.recordBlockProposalFailed('insufficient_txs');
|
|
458
527
|
}
|
|
459
528
|
return block;
|
|
460
529
|
}
|
|
@@ -485,13 +554,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
485
554
|
* @param slotNumber - The current slot number.
|
|
486
555
|
* @param force - Whether to force the transition even if the sequencer is stopped.
|
|
487
556
|
*/
|
|
488
|
-
setState(proposedState: SequencerStateWithSlot, slotNumber:
|
|
557
|
+
setState(proposedState: SequencerStateWithSlot, slotNumber: SlotNumber, opts?: { force?: boolean }): void;
|
|
489
558
|
setState(
|
|
490
559
|
proposedState: Exclude<SequencerState, SequencerStateWithSlot>,
|
|
491
560
|
slotNumber?: undefined,
|
|
492
561
|
opts?: { force?: boolean },
|
|
493
562
|
): void;
|
|
494
|
-
setState(proposedState: SequencerState, slotNumber:
|
|
563
|
+
setState(proposedState: SequencerState, slotNumber: SlotNumber | undefined, opts: { force?: boolean } = {}): void {
|
|
495
564
|
if (this.state === SequencerState.STOPPING && proposedState !== SequencerState.STOPPED && !opts.force) {
|
|
496
565
|
this.log.warn(`Cannot set sequencer to ${proposedState} as it is stopping.`);
|
|
497
566
|
throw new SequencerInterruptedError();
|
|
@@ -532,7 +601,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
532
601
|
await this.p2pClient.deleteTxs(failedTxHashes);
|
|
533
602
|
}
|
|
534
603
|
|
|
535
|
-
protected getBlockBuilderOptions(slot:
|
|
604
|
+
protected getBlockBuilderOptions(slot: SlotNumber): PublicProcessorLimits {
|
|
536
605
|
// Deadline for processing depends on whether we're proposing a block
|
|
537
606
|
const secondsIntoSlot = this.getSecondsIntoSlot(slot);
|
|
538
607
|
const processingEndTimeWithinSlot = this.timetable.getBlockProposalExecTimeEnd(secondsIntoSlot);
|
|
@@ -545,7 +614,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
545
614
|
maxTransactions: this.maxTxsPerBlock,
|
|
546
615
|
maxBlockSize: this.maxBlockSizeInBytes,
|
|
547
616
|
maxBlockGas: this.maxBlockGas,
|
|
548
|
-
maxBlobFields:
|
|
617
|
+
maxBlobFields: BLOBS_PER_CHECKPOINT * FIELDS_PER_BLOB,
|
|
549
618
|
deadline,
|
|
550
619
|
};
|
|
551
620
|
}
|
|
@@ -575,14 +644,14 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
575
644
|
await publisher.validateBlockHeader(proposalHeader, invalidateBlock);
|
|
576
645
|
|
|
577
646
|
const blockNumber = newGlobalVariables.blockNumber;
|
|
578
|
-
const slot = proposalHeader.slotNumber
|
|
647
|
+
const slot = proposalHeader.slotNumber;
|
|
579
648
|
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(blockNumber);
|
|
580
649
|
|
|
581
650
|
const workTimer = new Timer();
|
|
582
651
|
this.setState(SequencerState.CREATING_BLOCK, slot);
|
|
583
652
|
|
|
584
653
|
try {
|
|
585
|
-
const blockBuilderOptions = this.getBlockBuilderOptions(
|
|
654
|
+
const blockBuilderOptions = this.getBlockBuilderOptions(slot);
|
|
586
655
|
const buildBlockRes = await this.blockBuilder.buildBlock(
|
|
587
656
|
pendingTxs,
|
|
588
657
|
l1ToL2Messages,
|
|
@@ -630,19 +699,28 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
630
699
|
},
|
|
631
700
|
);
|
|
632
701
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
if (
|
|
636
|
-
this.log.
|
|
702
|
+
// In fisherman mode, skip attestation collection
|
|
703
|
+
let attestationsAndSigners: CommitteeAttestationsAndSigners;
|
|
704
|
+
if (this.config.fishermanMode) {
|
|
705
|
+
this.log.debug('Skipping attestation collection');
|
|
706
|
+
attestationsAndSigners = CommitteeAttestationsAndSigners.empty();
|
|
707
|
+
} else {
|
|
708
|
+
this.log.debug('Collecting attestations');
|
|
709
|
+
attestationsAndSigners = await this.collectAttestations(block, usedTxs, proposerAddress);
|
|
710
|
+
this.log.verbose(
|
|
711
|
+
`Collected ${attestationsAndSigners.attestations.length} attestations for block ${blockNumber} at slot ${slot}`,
|
|
712
|
+
{ blockHash, blockNumber, slot },
|
|
713
|
+
);
|
|
637
714
|
}
|
|
638
715
|
|
|
639
|
-
|
|
640
|
-
const attestationsAndSignersSignature =
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
716
|
+
// In fisherman mode, skip attestation signing
|
|
717
|
+
const attestationsAndSignersSignature =
|
|
718
|
+
this.config.fishermanMode || !this.validatorClient
|
|
719
|
+
? Signature.empty()
|
|
720
|
+
: await this.validatorClient.signAttestationsAndSigners(
|
|
721
|
+
attestationsAndSigners,
|
|
722
|
+
proposerAddress ?? publisher.getSenderAddress(),
|
|
723
|
+
);
|
|
646
724
|
|
|
647
725
|
await this.enqueuePublishL2Block(
|
|
648
726
|
block,
|
|
@@ -668,8 +746,8 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
668
746
|
block: L2Block,
|
|
669
747
|
txs: Tx[],
|
|
670
748
|
proposerAddress: EthAddress | undefined,
|
|
671
|
-
): Promise<
|
|
672
|
-
const { committee } = await this.epochCache.getCommittee(block.
|
|
749
|
+
): Promise<CommitteeAttestationsAndSigners> {
|
|
750
|
+
const { committee, seed, epoch } = await this.epochCache.getCommittee(block.slot);
|
|
673
751
|
|
|
674
752
|
// We checked above that the committee is defined, so this should never happen.
|
|
675
753
|
if (!committee) {
|
|
@@ -678,20 +756,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
678
756
|
|
|
679
757
|
if (committee.length === 0) {
|
|
680
758
|
this.log.verbose(`Attesting committee is empty`);
|
|
681
|
-
return
|
|
759
|
+
return CommitteeAttestationsAndSigners.empty();
|
|
682
760
|
} else {
|
|
683
761
|
this.log.debug(`Attesting committee length is ${committee.length}`);
|
|
684
762
|
}
|
|
685
763
|
|
|
686
764
|
if (!this.validatorClient) {
|
|
687
|
-
|
|
688
|
-
this.log.error(msg);
|
|
689
|
-
throw new Error(msg);
|
|
765
|
+
throw new Error('Missing validator client: Cannot collect attestations');
|
|
690
766
|
}
|
|
691
767
|
|
|
692
768
|
const numberOfRequiredAttestations = Math.floor((committee.length * 2) / 3) + 1;
|
|
693
769
|
|
|
694
|
-
const slotNumber = block.header.globalVariables.slotNumber
|
|
770
|
+
const slotNumber = block.header.globalVariables.slotNumber;
|
|
695
771
|
this.setState(SequencerState.COLLECTING_ATTESTATIONS, slotNumber);
|
|
696
772
|
|
|
697
773
|
this.log.debug('Creating block proposal for validators');
|
|
@@ -703,7 +779,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
703
779
|
block.header.globalVariables.blockNumber,
|
|
704
780
|
block.getCheckpointHeader(),
|
|
705
781
|
block.archive.root,
|
|
706
|
-
block.header.state,
|
|
707
782
|
txs,
|
|
708
783
|
proposerAddress,
|
|
709
784
|
blockProposalOptions,
|
|
@@ -716,7 +791,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
716
791
|
if (this.config.skipCollectingAttestations) {
|
|
717
792
|
this.log.warn('Skipping attestation collection as per config (attesting with own keys only)');
|
|
718
793
|
const attestations = await this.validatorClient?.collectOwnAttestations(proposal);
|
|
719
|
-
return orderAttestations(attestations ?? [], committee);
|
|
794
|
+
return new CommitteeAttestationsAndSigners(orderAttestations(attestations ?? [], committee));
|
|
720
795
|
}
|
|
721
796
|
|
|
722
797
|
this.log.debug('Broadcasting block proposal to validators');
|
|
@@ -742,13 +817,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
742
817
|
|
|
743
818
|
// note: the smart contract requires that the signatures are provided in the order of the committee
|
|
744
819
|
const sorted = orderAttestations(attestations, committee);
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
this.
|
|
749
|
-
unfreeze(nonEmpty[randomIndex]).signature = Signature.random();
|
|
820
|
+
|
|
821
|
+
// manipulate the attestations if we've been configured to do so
|
|
822
|
+
if (this.config.injectFakeAttestation || this.config.shuffleAttestationOrdering) {
|
|
823
|
+
return this.manipulateAttestations(block, epoch, seed, committee, sorted);
|
|
750
824
|
}
|
|
751
|
-
|
|
825
|
+
|
|
826
|
+
return new CommitteeAttestationsAndSigners(sorted);
|
|
752
827
|
} catch (err) {
|
|
753
828
|
if (err && err instanceof AttestationTimeoutError) {
|
|
754
829
|
collectedAttestationsCount = err.collectedCount;
|
|
@@ -759,6 +834,53 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
759
834
|
}
|
|
760
835
|
}
|
|
761
836
|
|
|
837
|
+
/** Breaks the attestations before publishing based on attack configs */
|
|
838
|
+
private manipulateAttestations(
|
|
839
|
+
block: L2Block,
|
|
840
|
+
epoch: EpochNumber,
|
|
841
|
+
seed: bigint,
|
|
842
|
+
committee: EthAddress[],
|
|
843
|
+
attestations: CommitteeAttestation[],
|
|
844
|
+
) {
|
|
845
|
+
// Compute the proposer index in the committee, since we dont want to tweak it.
|
|
846
|
+
// Otherwise, the L1 rollup contract will reject the block outright.
|
|
847
|
+
const proposerIndex = Number(
|
|
848
|
+
this.epochCache.computeProposerIndex(block.slot, epoch, seed, BigInt(committee.length)),
|
|
849
|
+
);
|
|
850
|
+
|
|
851
|
+
if (this.config.injectFakeAttestation) {
|
|
852
|
+
// Find non-empty attestations that are not from the proposer
|
|
853
|
+
const nonProposerIndices: number[] = [];
|
|
854
|
+
for (let i = 0; i < attestations.length; i++) {
|
|
855
|
+
if (!attestations[i].signature.isEmpty() && i !== proposerIndex) {
|
|
856
|
+
nonProposerIndices.push(i);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
if (nonProposerIndices.length > 0) {
|
|
860
|
+
const targetIndex = nonProposerIndices[randomInt(nonProposerIndices.length)];
|
|
861
|
+
this.log.warn(`Injecting fake attestation in block ${block.number} at index ${targetIndex}`);
|
|
862
|
+
unfreeze(attestations[targetIndex]).signature = Signature.random();
|
|
863
|
+
}
|
|
864
|
+
return new CommitteeAttestationsAndSigners(attestations);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
if (this.config.shuffleAttestationOrdering) {
|
|
868
|
+
this.log.warn(`Shuffling attestation ordering in block ${block.number} (proposer index ${proposerIndex})`);
|
|
869
|
+
|
|
870
|
+
const shuffled = [...attestations];
|
|
871
|
+
const [i, j] = [(proposerIndex + 1) % shuffled.length, (proposerIndex + 2) % shuffled.length];
|
|
872
|
+
const valueI = shuffled[i];
|
|
873
|
+
const valueJ = shuffled[j];
|
|
874
|
+
shuffled[i] = valueJ;
|
|
875
|
+
shuffled[j] = valueI;
|
|
876
|
+
|
|
877
|
+
const signers = new CommitteeAttestationsAndSigners(attestations).getSigners();
|
|
878
|
+
return new MaliciousCommitteeAttestationsAndSigners(shuffled, signers);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
return new CommitteeAttestationsAndSigners(attestations);
|
|
882
|
+
}
|
|
883
|
+
|
|
762
884
|
/**
|
|
763
885
|
* Publishes the L2Block to the rollup contract.
|
|
764
886
|
* @param block - The L2Block to be published.
|
|
@@ -774,10 +896,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
774
896
|
publisher: SequencerPublisher,
|
|
775
897
|
): Promise<void> {
|
|
776
898
|
// Publishes new block to the network and awaits the tx to be mined
|
|
777
|
-
this.setState(SequencerState.PUBLISHING_BLOCK, block.header.globalVariables.slotNumber
|
|
899
|
+
this.setState(SequencerState.PUBLISHING_BLOCK, block.header.globalVariables.slotNumber);
|
|
778
900
|
|
|
779
901
|
// Time out tx at the end of the slot
|
|
780
|
-
const slot = block.header.globalVariables.slotNumber
|
|
902
|
+
const slot = block.header.globalVariables.slotNumber;
|
|
781
903
|
const txTimeoutAt = new Date((this.getSlotStartBuildTimestamp(slot) + this.aztecSlotDuration) * 1000);
|
|
782
904
|
|
|
783
905
|
const enqueued = await publisher.enqueueProposeL2Block(
|
|
@@ -799,10 +921,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
799
921
|
* Returns whether all dependencies have caught up.
|
|
800
922
|
* We don't check against the previous block submitted since it may have been reorg'd out.
|
|
801
923
|
*/
|
|
802
|
-
protected async checkSync(args: { ts: bigint; slot:
|
|
924
|
+
protected async checkSync(args: { ts: bigint; slot: SlotNumber }): Promise<
|
|
803
925
|
| {
|
|
804
926
|
block?: L2Block;
|
|
805
|
-
blockNumber:
|
|
927
|
+
blockNumber: BlockNumber;
|
|
806
928
|
archive: Fr;
|
|
807
929
|
l1Timestamp: bigint;
|
|
808
930
|
pendingChainValidationStatus: ValidateBlockResult;
|
|
@@ -836,14 +958,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
836
958
|
|
|
837
959
|
const [worldState, l2BlockSource, p2p, l1ToL2MessageSource, pendingChainValidationStatus] = syncedBlocks;
|
|
838
960
|
|
|
839
|
-
//
|
|
840
|
-
//
|
|
961
|
+
// Handle zero as a special case, since the block hash won't match across services if we're changing the prefilled data for the genesis block,
|
|
962
|
+
// as the world state can compute the new genesis block hash, but other components use the hardcoded constant.
|
|
841
963
|
const result =
|
|
842
|
-
l2BlockSource.
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
l1ToL2MessageSource.hash === l2BlockSource.hash;
|
|
964
|
+
(l2BlockSource.number === 0 && worldState.number === 0 && p2p.number === 0 && l1ToL2MessageSource.number === 0) ||
|
|
965
|
+
(worldState.hash === l2BlockSource.hash &&
|
|
966
|
+
p2p.hash === l2BlockSource.hash &&
|
|
967
|
+
l1ToL2MessageSource.hash === l2BlockSource.hash);
|
|
847
968
|
|
|
848
969
|
if (!result) {
|
|
849
970
|
this.log.debug(`Sequencer sync check failed`, { worldState, l2BlockSource, p2p, l1ToL2MessageSource });
|
|
@@ -854,7 +975,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
854
975
|
const blockNumber = worldState.number;
|
|
855
976
|
if (blockNumber < INITIAL_L2_BLOCK_NUM) {
|
|
856
977
|
const archive = new Fr((await this.worldState.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root);
|
|
857
|
-
return { blockNumber: INITIAL_L2_BLOCK_NUM - 1, archive, l1Timestamp, pendingChainValidationStatus };
|
|
978
|
+
return { blockNumber: BlockNumber(INITIAL_L2_BLOCK_NUM - 1), archive, l1Timestamp, pendingChainValidationStatus };
|
|
858
979
|
}
|
|
859
980
|
|
|
860
981
|
const block = await this.l2BlockSource.getBlock(blockNumber);
|
|
@@ -885,7 +1006,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
885
1006
|
protected enqueueGovernanceAndSlashingVotes(
|
|
886
1007
|
publisher: SequencerPublisher,
|
|
887
1008
|
attestorAddress: EthAddress,
|
|
888
|
-
slot:
|
|
1009
|
+
slot: SlotNumber,
|
|
889
1010
|
timestamp: bigint,
|
|
890
1011
|
): [Promise<boolean> | undefined, Promise<boolean> | undefined] {
|
|
891
1012
|
try {
|
|
@@ -905,7 +1026,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
905
1026
|
const enqueueSlashingPromise = this.slasherClient
|
|
906
1027
|
? this.slasherClient
|
|
907
1028
|
.getProposerActions(slot)
|
|
908
|
-
.then(actions =>
|
|
1029
|
+
.then(actions => {
|
|
1030
|
+
// Record metrics for fisherman mode
|
|
1031
|
+
if (this.config.fishermanMode && actions.length > 0) {
|
|
1032
|
+
this.log.debug(`Fisherman mode: simulating ${actions.length} slashing action(s) for slot ${slot}`, {
|
|
1033
|
+
slot,
|
|
1034
|
+
actionCount: actions.length,
|
|
1035
|
+
});
|
|
1036
|
+
this.metrics.recordSlashingAttempt(actions.length);
|
|
1037
|
+
}
|
|
1038
|
+
// Enqueue the actions to fully simulate L1 tx building (they won't be sent in fisherman mode)
|
|
1039
|
+
return publisher.enqueueSlashingActions(actions, slot, timestamp, attestorAddress, signerFn);
|
|
1040
|
+
})
|
|
909
1041
|
.catch(err => {
|
|
910
1042
|
this.log.error(`Error enqueuing slashing actions`, err, { slot });
|
|
911
1043
|
return false;
|
|
@@ -923,8 +1055,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
923
1055
|
* Checks if we are the proposer for the next slot.
|
|
924
1056
|
* @returns True if we can propose, and the proposer address (undefined if anyone can propose)
|
|
925
1057
|
*/
|
|
926
|
-
protected async checkCanPropose(slot:
|
|
1058
|
+
protected async checkCanPropose(slot: SlotNumber): Promise<[boolean, EthAddress | undefined]> {
|
|
927
1059
|
let proposer: EthAddress | undefined;
|
|
1060
|
+
|
|
928
1061
|
try {
|
|
929
1062
|
proposer = await this.epochCache.getProposerAttesterAddressInSlot(slot);
|
|
930
1063
|
} catch (e) {
|
|
@@ -940,6 +1073,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
940
1073
|
if (proposer === undefined) {
|
|
941
1074
|
return [true, undefined];
|
|
942
1075
|
}
|
|
1076
|
+
// In fisherman mode, just return the current proposer
|
|
1077
|
+
if (this.config.fishermanMode) {
|
|
1078
|
+
return [true, proposer];
|
|
1079
|
+
}
|
|
943
1080
|
|
|
944
1081
|
const validatorAddresses = this.validatorClient!.getValidatorAddresses();
|
|
945
1082
|
const weAreProposer = validatorAddresses.some(addr => addr.equals(proposer));
|
|
@@ -956,7 +1093,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
956
1093
|
* Tries to vote on slashing actions and governance when the sync check fails but we're past the max time for initializing a proposal.
|
|
957
1094
|
* This allows the sequencer to participate in governance/slashing votes even when it cannot build blocks.
|
|
958
1095
|
*/
|
|
959
|
-
protected async tryVoteWhenSyncFails(args: { slot:
|
|
1096
|
+
protected async tryVoteWhenSyncFails(args: { slot: SlotNumber; ts: bigint }): Promise<void> {
|
|
960
1097
|
const { slot, ts } = args;
|
|
961
1098
|
|
|
962
1099
|
// Prevent duplicate attempts in the same slot
|
|
@@ -1023,7 +1160,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
1023
1160
|
*/
|
|
1024
1161
|
protected async considerInvalidatingBlock(
|
|
1025
1162
|
syncedTo: NonNullable<Awaited<ReturnType<Sequencer['checkSync']>>>,
|
|
1026
|
-
currentSlot:
|
|
1163
|
+
currentSlot: SlotNumber,
|
|
1027
1164
|
): Promise<void> {
|
|
1028
1165
|
const { pendingChainValidationStatus, l1Timestamp } = syncedTo;
|
|
1029
1166
|
if (pendingChainValidationStatus.valid) {
|
|
@@ -1084,14 +1221,20 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
1084
1221
|
);
|
|
1085
1222
|
|
|
1086
1223
|
publisher.enqueueInvalidateBlock(invalidateBlock);
|
|
1087
|
-
|
|
1224
|
+
|
|
1225
|
+
if (!this.config.fishermanMode) {
|
|
1226
|
+
await publisher.sendRequests();
|
|
1227
|
+
} else {
|
|
1228
|
+
this.log.info('Invalidating block in fisherman mode, clearing pending requests');
|
|
1229
|
+
publisher.clearPendingRequests();
|
|
1230
|
+
}
|
|
1088
1231
|
}
|
|
1089
1232
|
|
|
1090
|
-
private getSlotStartBuildTimestamp(slotNumber:
|
|
1233
|
+
private getSlotStartBuildTimestamp(slotNumber: SlotNumber): number {
|
|
1091
1234
|
return getSlotStartBuildTimestamp(slotNumber, this.l1Constants);
|
|
1092
1235
|
}
|
|
1093
1236
|
|
|
1094
|
-
private getSecondsIntoSlot(slotNumber:
|
|
1237
|
+
private getSecondsIntoSlot(slotNumber: SlotNumber): number {
|
|
1095
1238
|
const slotStartTimestamp = this.getSlotStartBuildTimestamp(slotNumber);
|
|
1096
1239
|
return Number((this.dateProvider.now() / 1000 - slotStartTimestamp).toFixed(3));
|
|
1097
1240
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BlockNumber } from '@aztec/foundation/branded-types';
|
|
1
2
|
import { Fr } from '@aztec/foundation/fields';
|
|
2
3
|
import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree';
|
|
3
4
|
import {
|
|
@@ -48,7 +49,7 @@ export function createValidatorForAcceptingTxs(
|
|
|
48
49
|
gasFees: GasFees;
|
|
49
50
|
skipFeeEnforcement?: boolean;
|
|
50
51
|
timestamp: UInt64;
|
|
51
|
-
blockNumber:
|
|
52
|
+
blockNumber: BlockNumber;
|
|
52
53
|
txsPermitted: boolean;
|
|
53
54
|
},
|
|
54
55
|
): TxValidator<Tx> {
|