@aztec/sequencer-client 4.0.0-nightly.20260111 → 4.0.0-nightly.20260113
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 +4 -5
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +8 -1
- package/dest/index.d.ts +2 -3
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -2
- package/dest/publisher/sequencer-publisher.d.ts +15 -15
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +44 -46
- package/dest/sequencer/block_builder.d.ts +1 -1
- package/dest/sequencer/block_builder.d.ts.map +1 -1
- package/dest/sequencer/block_builder.js +1 -1
- package/dest/sequencer/checkpoint_proposal_job.d.ts +8 -7
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +47 -28
- package/dest/sequencer/index.d.ts +1 -2
- package/dest/sequencer/index.d.ts.map +1 -1
- package/dest/sequencer/index.js +0 -1
- package/dest/sequencer/metrics.d.ts +3 -3
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +4 -4
- package/dest/sequencer/sequencer.d.ts +12 -11
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +28 -26
- package/dest/test/index.d.ts +2 -3
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts +10 -2
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +23 -0
- package/dest/test/utils.d.ts +8 -4
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +18 -8
- package/package.json +27 -27
- package/src/client/sequencer-client.ts +3 -4
- package/src/config.ts +7 -0
- package/src/index.ts +0 -3
- package/src/publisher/sequencer-publisher.ts +75 -70
- package/src/sequencer/block_builder.ts +1 -2
- package/src/sequencer/checkpoint_proposal_job.ts +58 -38
- package/src/sequencer/index.ts +0 -1
- package/src/sequencer/metrics.ts +4 -4
- package/src/sequencer/sequencer.ts +41 -33
- package/src/test/index.ts +1 -2
- package/src/test/mock_checkpoint_builder.ts +34 -2
- package/src/test/utils.ts +35 -9
- package/dest/sequencer/checkpoint_builder.d.ts +0 -63
- package/dest/sequencer/checkpoint_builder.d.ts.map +0 -1
- package/dest/sequencer/checkpoint_builder.js +0 -131
- package/dest/tx_validator/nullifier_cache.d.ts +0 -14
- package/dest/tx_validator/nullifier_cache.d.ts.map +0 -1
- package/dest/tx_validator/nullifier_cache.js +0 -24
- package/dest/tx_validator/tx_validator_factory.d.ts +0 -18
- package/dest/tx_validator/tx_validator_factory.d.ts.map +0 -1
- package/dest/tx_validator/tx_validator_factory.js +0 -53
- package/src/sequencer/checkpoint_builder.ts +0 -217
- package/src/tx_validator/nullifier_cache.ts +0 -30
- package/src/tx_validator/tx_validator_factory.ts +0 -133
|
@@ -25,7 +25,7 @@ import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs'
|
|
|
25
25
|
import { FormattedViemError, formatViemError, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
26
26
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
27
27
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
28
|
-
import {
|
|
28
|
+
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
29
29
|
import { pick } from '@aztec/foundation/collection';
|
|
30
30
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
31
31
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
@@ -35,7 +35,7 @@ import { bufferToHex } from '@aztec/foundation/string';
|
|
|
35
35
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
36
36
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
37
37
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
38
|
-
import { CommitteeAttestationsAndSigners, type
|
|
38
|
+
import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
39
39
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
40
40
|
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
41
41
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
@@ -80,12 +80,12 @@ type GovernanceSignalAction = Extract<Action, 'governance-signal' | 'empire-slas
|
|
|
80
80
|
// Sorting for actions such that invalidations go before proposals, and proposals go before votes
|
|
81
81
|
export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
|
|
82
82
|
|
|
83
|
-
export type
|
|
83
|
+
export type InvalidateCheckpointRequest = {
|
|
84
84
|
request: L1TxRequest;
|
|
85
85
|
reason: 'invalid-attestation' | 'insufficient-attestations';
|
|
86
86
|
gasUsed: bigint;
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
checkpointNumber: CheckpointNumber;
|
|
88
|
+
forcePendingCheckpointNumber: CheckpointNumber;
|
|
89
89
|
};
|
|
90
90
|
|
|
91
91
|
interface RequestWithExpiry {
|
|
@@ -417,17 +417,14 @@ export class SequencerPublisher {
|
|
|
417
417
|
public canProposeAtNextEthBlock(
|
|
418
418
|
tipArchive: Fr,
|
|
419
419
|
msgSender: EthAddress,
|
|
420
|
-
opts: {
|
|
420
|
+
opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
421
421
|
) {
|
|
422
422
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
423
423
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
424
424
|
|
|
425
425
|
return this.rollupContract
|
|
426
426
|
.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
|
|
427
|
-
forcePendingCheckpointNumber:
|
|
428
|
-
opts.forcePendingBlockNumber !== undefined
|
|
429
|
-
? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber)
|
|
430
|
-
: undefined,
|
|
427
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
431
428
|
})
|
|
432
429
|
.catch(err => {
|
|
433
430
|
if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
|
|
@@ -449,7 +446,7 @@ export class SequencerPublisher {
|
|
|
449
446
|
@trackSpan('SequencerPublisher.validateBlockHeader')
|
|
450
447
|
public async validateBlockHeader(
|
|
451
448
|
header: CheckpointHeader,
|
|
452
|
-
opts?: {
|
|
449
|
+
opts?: { forcePendingCheckpointNumber: CheckpointNumber | undefined },
|
|
453
450
|
): Promise<void> {
|
|
454
451
|
const flags = { ignoreDA: true, ignoreSignatures: true };
|
|
455
452
|
|
|
@@ -464,12 +461,8 @@ export class SequencerPublisher {
|
|
|
464
461
|
] as const;
|
|
465
462
|
|
|
466
463
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
467
|
-
const optsForcePendingCheckpointNumber =
|
|
468
|
-
opts?.forcePendingBlockNumber !== undefined
|
|
469
|
-
? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber)
|
|
470
|
-
: undefined;
|
|
471
464
|
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
472
|
-
|
|
465
|
+
opts?.forcePendingCheckpointNumber,
|
|
473
466
|
);
|
|
474
467
|
let balance = 0n;
|
|
475
468
|
if (this.config.fishermanMode) {
|
|
@@ -497,77 +490,90 @@ export class SequencerPublisher {
|
|
|
497
490
|
}
|
|
498
491
|
|
|
499
492
|
/**
|
|
500
|
-
* Simulate making a call to invalidate a
|
|
501
|
-
* @param
|
|
493
|
+
* Simulate making a call to invalidate a checkpoint with invalid attestations. Returns undefined if no need to invalidate.
|
|
494
|
+
* @param validationResult - The validation result indicating which checkpoint to invalidate (as returned by the archiver)
|
|
502
495
|
*/
|
|
503
|
-
public async
|
|
504
|
-
validationResult:
|
|
505
|
-
): Promise<
|
|
496
|
+
public async simulateInvalidateCheckpoint(
|
|
497
|
+
validationResult: ValidateCheckpointResult,
|
|
498
|
+
): Promise<InvalidateCheckpointRequest | undefined> {
|
|
506
499
|
if (validationResult.valid) {
|
|
507
500
|
return undefined;
|
|
508
501
|
}
|
|
509
502
|
|
|
510
|
-
const { reason,
|
|
511
|
-
const
|
|
512
|
-
const logData = { ...
|
|
503
|
+
const { reason, checkpoint } = validationResult;
|
|
504
|
+
const checkpointNumber = checkpoint.checkpointNumber;
|
|
505
|
+
const logData = { ...checkpoint, reason };
|
|
513
506
|
|
|
514
|
-
const
|
|
515
|
-
if (
|
|
507
|
+
const currentCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
508
|
+
if (currentCheckpointNumber < checkpointNumber) {
|
|
516
509
|
this.log.verbose(
|
|
517
|
-
`Skipping
|
|
518
|
-
{
|
|
510
|
+
`Skipping checkpoint ${checkpointNumber} invalidation since it has already been removed from the pending chain`,
|
|
511
|
+
{ currentCheckpointNumber, ...logData },
|
|
519
512
|
);
|
|
520
513
|
return undefined;
|
|
521
514
|
}
|
|
522
515
|
|
|
523
|
-
const request = this.
|
|
524
|
-
this.log.debug(`Simulating invalidate
|
|
516
|
+
const request = this.buildInvalidateCheckpointRequest(validationResult);
|
|
517
|
+
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
|
|
525
518
|
|
|
526
519
|
try {
|
|
527
520
|
const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, ErrorsAbi);
|
|
528
|
-
this.log.verbose(`Simulation for invalidate
|
|
521
|
+
this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} succeeded`, {
|
|
522
|
+
...logData,
|
|
523
|
+
request,
|
|
524
|
+
gasUsed,
|
|
525
|
+
});
|
|
529
526
|
|
|
530
|
-
return {
|
|
527
|
+
return {
|
|
528
|
+
request,
|
|
529
|
+
gasUsed,
|
|
530
|
+
checkpointNumber,
|
|
531
|
+
forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
|
|
532
|
+
reason,
|
|
533
|
+
};
|
|
531
534
|
} catch (err) {
|
|
532
535
|
const viemError = formatViemError(err);
|
|
533
536
|
|
|
534
|
-
// If the error is due to the
|
|
535
|
-
// we can safely ignore it and return undefined so we go ahead with
|
|
537
|
+
// If the error is due to the checkpoint not being in the pending chain, and it was indeed removed by someone else,
|
|
538
|
+
// we can safely ignore it and return undefined so we go ahead with checkpoint building.
|
|
536
539
|
if (viemError.message?.includes('Rollup__BlockNotInPendingChain')) {
|
|
537
540
|
this.log.verbose(
|
|
538
|
-
`Simulation for invalidate
|
|
541
|
+
`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
|
|
539
542
|
{ ...logData, request, error: viemError.message },
|
|
540
543
|
);
|
|
541
|
-
const
|
|
542
|
-
if (
|
|
543
|
-
this.log.verbose(`
|
|
544
|
+
const latestPendingCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
545
|
+
if (latestPendingCheckpointNumber < checkpointNumber) {
|
|
546
|
+
this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, { ...logData });
|
|
544
547
|
return undefined;
|
|
545
548
|
} else {
|
|
546
549
|
this.log.error(
|
|
547
|
-
`Simulation for invalidate ${
|
|
550
|
+
`Simulation for invalidate checkpoint ${checkpointNumber} failed and it is still in pending chain`,
|
|
548
551
|
viemError,
|
|
549
552
|
logData,
|
|
550
553
|
);
|
|
551
|
-
throw new Error(
|
|
552
|
-
|
|
553
|
-
|
|
554
|
+
throw new Error(
|
|
555
|
+
`Failed to simulate invalidate checkpoint ${checkpointNumber} while it is still in pending chain`,
|
|
556
|
+
{
|
|
557
|
+
cause: viemError,
|
|
558
|
+
},
|
|
559
|
+
);
|
|
554
560
|
}
|
|
555
561
|
}
|
|
556
562
|
|
|
557
|
-
// Otherwise, throw. We cannot build the next
|
|
558
|
-
this.log.error(`Simulation for invalidate
|
|
559
|
-
throw new Error(`Failed to simulate invalidate
|
|
563
|
+
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
564
|
+
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
|
|
565
|
+
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
|
|
560
566
|
}
|
|
561
567
|
}
|
|
562
568
|
|
|
563
|
-
private
|
|
569
|
+
private buildInvalidateCheckpointRequest(validationResult: ValidateCheckpointResult) {
|
|
564
570
|
if (validationResult.valid) {
|
|
565
|
-
throw new Error('Cannot invalidate a valid
|
|
571
|
+
throw new Error('Cannot invalidate a valid checkpoint');
|
|
566
572
|
}
|
|
567
573
|
|
|
568
|
-
const {
|
|
569
|
-
const logData = { ...
|
|
570
|
-
this.log.debug(`
|
|
574
|
+
const { checkpoint, committee, reason } = validationResult;
|
|
575
|
+
const logData = { ...checkpoint, reason };
|
|
576
|
+
this.log.debug(`Building invalidate checkpoint ${checkpoint.checkpointNumber} request`, logData);
|
|
571
577
|
|
|
572
578
|
const attestationsAndSigners = new CommitteeAttestationsAndSigners(
|
|
573
579
|
validationResult.attestations,
|
|
@@ -575,14 +581,14 @@ export class SequencerPublisher {
|
|
|
575
581
|
|
|
576
582
|
if (reason === 'invalid-attestation') {
|
|
577
583
|
return this.rollupContract.buildInvalidateBadAttestationRequest(
|
|
578
|
-
|
|
584
|
+
checkpoint.checkpointNumber,
|
|
579
585
|
attestationsAndSigners,
|
|
580
586
|
committee,
|
|
581
587
|
validationResult.invalidIndex,
|
|
582
588
|
);
|
|
583
589
|
} else if (reason === 'insufficient-attestations') {
|
|
584
590
|
return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
|
|
585
|
-
|
|
591
|
+
checkpoint.checkpointNumber,
|
|
586
592
|
attestationsAndSigners,
|
|
587
593
|
committee,
|
|
588
594
|
);
|
|
@@ -598,7 +604,7 @@ export class SequencerPublisher {
|
|
|
598
604
|
checkpoint: Checkpoint,
|
|
599
605
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
600
606
|
attestationsAndSignersSignature: Signature,
|
|
601
|
-
options: {
|
|
607
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
602
608
|
): Promise<bigint> {
|
|
603
609
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
604
610
|
|
|
@@ -897,7 +903,7 @@ export class SequencerPublisher {
|
|
|
897
903
|
checkpoint: Checkpoint,
|
|
898
904
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
899
905
|
attestationsAndSignersSignature: Signature,
|
|
900
|
-
opts: { txTimeoutAt?: Date;
|
|
906
|
+
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
901
907
|
): Promise<void> {
|
|
902
908
|
const checkpointHeader = checkpoint.header;
|
|
903
909
|
|
|
@@ -930,7 +936,7 @@ export class SequencerPublisher {
|
|
|
930
936
|
this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
931
937
|
...checkpoint.getStats(),
|
|
932
938
|
slotNumber: checkpoint.header.slotNumber,
|
|
933
|
-
|
|
939
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
934
940
|
});
|
|
935
941
|
throw err;
|
|
936
942
|
}
|
|
@@ -939,7 +945,10 @@ export class SequencerPublisher {
|
|
|
939
945
|
await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
|
|
940
946
|
}
|
|
941
947
|
|
|
942
|
-
public
|
|
948
|
+
public enqueueInvalidateCheckpoint(
|
|
949
|
+
request: InvalidateCheckpointRequest | undefined,
|
|
950
|
+
opts: { txTimeoutAt?: Date } = {},
|
|
951
|
+
) {
|
|
943
952
|
if (!request) {
|
|
944
953
|
return;
|
|
945
954
|
}
|
|
@@ -947,9 +956,9 @@ export class SequencerPublisher {
|
|
|
947
956
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
948
957
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(request.gasUsed) * 64) / 63)));
|
|
949
958
|
|
|
950
|
-
const { gasUsed,
|
|
951
|
-
const logData = { gasUsed,
|
|
952
|
-
this.log.verbose(`Enqueuing invalidate
|
|
959
|
+
const { gasUsed, checkpointNumber } = request;
|
|
960
|
+
const logData = { gasUsed, checkpointNumber, gasLimit, opts };
|
|
961
|
+
this.log.verbose(`Enqueuing invalidate checkpoint request`, logData);
|
|
953
962
|
this.addRequest({
|
|
954
963
|
action: `invalidate-by-${request.reason}`,
|
|
955
964
|
request: request.request,
|
|
@@ -962,9 +971,9 @@ export class SequencerPublisher {
|
|
|
962
971
|
result.receipt.status === 'success' &&
|
|
963
972
|
tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointInvalidated');
|
|
964
973
|
if (!success) {
|
|
965
|
-
this.log.warn(`Invalidate
|
|
974
|
+
this.log.warn(`Invalidate checkpoint ${request.checkpointNumber} failed`, { ...result, ...logData });
|
|
966
975
|
} else {
|
|
967
|
-
this.log.info(`Invalidate
|
|
976
|
+
this.log.info(`Invalidate checkpoint ${request.checkpointNumber} succeeded`, { ...result, ...logData });
|
|
968
977
|
}
|
|
969
978
|
return !!success;
|
|
970
979
|
},
|
|
@@ -1043,7 +1052,7 @@ export class SequencerPublisher {
|
|
|
1043
1052
|
private async prepareProposeTx(
|
|
1044
1053
|
encodedData: L1ProcessArgs,
|
|
1045
1054
|
timestamp: bigint,
|
|
1046
|
-
options: {
|
|
1055
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1047
1056
|
) {
|
|
1048
1057
|
const kzg = Blob.getViemKzgInstance();
|
|
1049
1058
|
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
@@ -1124,7 +1133,7 @@ export class SequencerPublisher {
|
|
|
1124
1133
|
`0x${string}`,
|
|
1125
1134
|
],
|
|
1126
1135
|
timestamp: bigint,
|
|
1127
|
-
options: {
|
|
1136
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1128
1137
|
) {
|
|
1129
1138
|
const rollupData = encodeFunctionData({
|
|
1130
1139
|
abi: RollupAbi,
|
|
@@ -1133,13 +1142,9 @@ export class SequencerPublisher {
|
|
|
1133
1142
|
});
|
|
1134
1143
|
|
|
1135
1144
|
// override the pending checkpoint number if requested
|
|
1136
|
-
const optsForcePendingCheckpointNumber =
|
|
1137
|
-
options.forcePendingBlockNumber !== undefined
|
|
1138
|
-
? CheckpointNumber.fromBlockNumber(options.forcePendingBlockNumber)
|
|
1139
|
-
: undefined;
|
|
1140
1145
|
const forcePendingCheckpointNumberStateDiff = (
|
|
1141
|
-
|
|
1142
|
-
? await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
1146
|
+
options.forcePendingCheckpointNumber !== undefined
|
|
1147
|
+
? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber)
|
|
1143
1148
|
: []
|
|
1144
1149
|
).flatMap(override => override.stateDiff ?? []);
|
|
1145
1150
|
|
|
@@ -1203,7 +1208,7 @@ export class SequencerPublisher {
|
|
|
1203
1208
|
private async addProposeTx(
|
|
1204
1209
|
checkpoint: Checkpoint,
|
|
1205
1210
|
encodedData: L1ProcessArgs,
|
|
1206
|
-
opts: { txTimeoutAt?: Date;
|
|
1211
|
+
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
1207
1212
|
timestamp: bigint,
|
|
1208
1213
|
): Promise<void> {
|
|
1209
1214
|
const slot = checkpoint.header.slotNumber;
|
|
@@ -28,8 +28,7 @@ import type {
|
|
|
28
28
|
} from '@aztec/stdlib/interfaces/server';
|
|
29
29
|
import { GlobalVariables, Tx } from '@aztec/stdlib/tx';
|
|
30
30
|
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
31
|
-
|
|
32
|
-
import { createValidatorForBlockBuilding } from '../tx_validator/tx_validator_factory.js';
|
|
31
|
+
import { createValidatorForBlockBuilding } from '@aztec/validator-client';
|
|
33
32
|
|
|
34
33
|
const log = createLogger('block-builder');
|
|
35
34
|
|
|
@@ -2,6 +2,7 @@ import { BLOBS_PER_CHECKPOINT, FIELDS_PER_BLOB } from '@aztec/constants';
|
|
|
2
2
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
3
3
|
import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
4
4
|
import { randomInt } from '@aztec/foundation/crypto/random';
|
|
5
|
+
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
5
6
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
6
7
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
7
8
|
import { filter } from '@aztec/foundation/iterator';
|
|
@@ -15,6 +16,7 @@ import {
|
|
|
15
16
|
CommitteeAttestation,
|
|
16
17
|
CommitteeAttestationsAndSigners,
|
|
17
18
|
L2BlockNew,
|
|
19
|
+
type L2BlockSink,
|
|
18
20
|
MaliciousCommitteeAttestationsAndSigners,
|
|
19
21
|
} from '@aztec/stdlib/block';
|
|
20
22
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
@@ -25,19 +27,17 @@ import type {
|
|
|
25
27
|
ResolvedSequencerConfig,
|
|
26
28
|
WorldStateSynchronizer,
|
|
27
29
|
} from '@aztec/stdlib/interfaces/server';
|
|
28
|
-
import type
|
|
29
|
-
import type {
|
|
30
|
+
import { type L1ToL2MessageSource, computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
|
|
31
|
+
import type { BlockProposalOptions, CheckpointProposal, CheckpointProposalOptions } from '@aztec/stdlib/p2p';
|
|
30
32
|
import { orderAttestations } from '@aztec/stdlib/p2p';
|
|
31
|
-
import { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
32
33
|
import type { L2BlockBuiltStats } from '@aztec/stdlib/stats';
|
|
33
34
|
import { type FailedTx, Tx } from '@aztec/stdlib/tx';
|
|
34
35
|
import { AttestationTimeoutError } from '@aztec/stdlib/validators';
|
|
35
36
|
import { Attributes, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
|
|
36
|
-
import type
|
|
37
|
+
import { CheckpointBuilder, type FullNodeCheckpointsBuilder, type ValidatorClient } from '@aztec/validator-client';
|
|
37
38
|
|
|
38
39
|
import type { GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
|
|
39
|
-
import type {
|
|
40
|
-
import { CheckpointBuilder, type FullNodeCheckpointsBuilder } from './checkpoint_builder.js';
|
|
40
|
+
import type { InvalidateCheckpointRequest, SequencerPublisher } from '../publisher/sequencer-publisher.js';
|
|
41
41
|
import { CheckpointVoter } from './checkpoint_voter.js';
|
|
42
42
|
import { SequencerInterruptedError } from './errors.js';
|
|
43
43
|
import type { SequencerEvents } from './events.js';
|
|
@@ -64,13 +64,14 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
64
64
|
private readonly proposer: EthAddress | undefined,
|
|
65
65
|
private readonly publisher: SequencerPublisher,
|
|
66
66
|
private readonly attestorAddress: EthAddress,
|
|
67
|
-
private readonly
|
|
67
|
+
private readonly invalidateCheckpoint: InvalidateCheckpointRequest | undefined,
|
|
68
68
|
private readonly validatorClient: ValidatorClient,
|
|
69
69
|
private readonly globalsBuilder: GlobalVariableBuilder,
|
|
70
70
|
private readonly p2pClient: P2P,
|
|
71
71
|
private readonly worldState: WorldStateSynchronizer,
|
|
72
72
|
private readonly l1ToL2MessageSource: L1ToL2MessageSource,
|
|
73
73
|
private readonly checkpointsBuilder: FullNodeCheckpointsBuilder,
|
|
74
|
+
private readonly blockSink: L2BlockSink,
|
|
74
75
|
private readonly l1Constants: SequencerRollupConstants,
|
|
75
76
|
protected config: ResolvedSequencerConfig,
|
|
76
77
|
protected timetable: SequencerTimetable,
|
|
@@ -152,9 +153,9 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
152
153
|
this.setStateFn(SequencerState.INITIALIZING_CHECKPOINT, this.slot);
|
|
153
154
|
this.metrics.incOpenSlot(this.slot, this.proposer?.toString() ?? 'unknown');
|
|
154
155
|
|
|
155
|
-
// Enqueues
|
|
156
|
-
if (this.
|
|
157
|
-
this.publisher.
|
|
156
|
+
// Enqueues checkpoint invalidation (constant for the whole slot)
|
|
157
|
+
if (this.invalidateCheckpoint && !this.config.skipInvalidateBlockAsProposer) {
|
|
158
|
+
this.publisher.enqueueInvalidateCheckpoint(this.invalidateCheckpoint);
|
|
158
159
|
}
|
|
159
160
|
|
|
160
161
|
// Create checkpoint builder for the slot
|
|
@@ -164,8 +165,9 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
164
165
|
this.slot,
|
|
165
166
|
);
|
|
166
167
|
|
|
167
|
-
// Collect L1 to L2 messages for the checkpoint
|
|
168
|
+
// Collect L1 to L2 messages for the checkpoint and compute their hash
|
|
168
169
|
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(this.checkpointNumber);
|
|
170
|
+
const inHash = computeInHashFromL1ToL2Messages(l1ToL2Messages);
|
|
169
171
|
|
|
170
172
|
// Create a long-lived forked world state for the checkpoint builder
|
|
171
173
|
using fork = await this.worldState.fork(this.syncedToBlockNumber, { closeDelayMs: 12_000 });
|
|
@@ -184,10 +186,16 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
184
186
|
broadcastInvalidBlockProposal: this.config.broadcastInvalidBlockProposal,
|
|
185
187
|
};
|
|
186
188
|
|
|
189
|
+
const checkpointProposalOptions: CheckpointProposalOptions = {
|
|
190
|
+
publishFullTxs: !!this.config.publishTxsWithProposals,
|
|
191
|
+
broadcastInvalidCheckpointProposal: this.config.broadcastInvalidBlockProposal,
|
|
192
|
+
};
|
|
193
|
+
|
|
187
194
|
// Main loop: build blocks for the checkpoint
|
|
188
|
-
const { blocksInCheckpoint,
|
|
195
|
+
const { blocksInCheckpoint, blockPendingBroadcast } = await this.buildBlocksForCheckpoint(
|
|
189
196
|
checkpointBuilder,
|
|
190
197
|
checkpointGlobalVariables.timestamp,
|
|
198
|
+
inHash,
|
|
191
199
|
blockProposalOptions,
|
|
192
200
|
);
|
|
193
201
|
|
|
@@ -217,22 +225,30 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
217
225
|
return checkpoint;
|
|
218
226
|
}
|
|
219
227
|
|
|
220
|
-
//
|
|
228
|
+
// Include the block pending broadcast in the checkpoint proposal if any
|
|
229
|
+
const lastBlock = blockPendingBroadcast && {
|
|
230
|
+
blockHeader: blockPendingBroadcast.block.header,
|
|
231
|
+
indexWithinCheckpoint: blockPendingBroadcast.block.indexWithinCheckpoint,
|
|
232
|
+
txs: blockPendingBroadcast.txs,
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// Create the checkpoint proposal and broadcast it
|
|
221
236
|
const proposal = await this.validatorClient.createCheckpointProposal(
|
|
222
237
|
checkpoint.header,
|
|
223
238
|
checkpoint.archive.root,
|
|
224
|
-
|
|
239
|
+
lastBlock,
|
|
225
240
|
this.proposer,
|
|
226
|
-
|
|
241
|
+
checkpointProposalOptions,
|
|
227
242
|
);
|
|
243
|
+
|
|
228
244
|
const blockProposedAt = this.dateProvider.now();
|
|
229
|
-
await this.p2pClient.
|
|
245
|
+
await this.p2pClient.broadcastCheckpointProposal(proposal);
|
|
230
246
|
|
|
231
247
|
this.setStateFn(SequencerState.COLLECTING_ATTESTATIONS, this.slot);
|
|
232
248
|
const attestations = await this.waitForAttestations(proposal);
|
|
233
249
|
const blockAttestedAt = this.dateProvider.now();
|
|
234
250
|
|
|
235
|
-
this.metrics.
|
|
251
|
+
this.metrics.recordCheckpointAttestationDelay(blockAttestedAt - blockProposedAt);
|
|
236
252
|
|
|
237
253
|
// Proposer must sign over the attestations before pushing them to L1
|
|
238
254
|
const signer = this.proposer ?? this.publisher.getSenderAddress();
|
|
@@ -245,7 +261,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
245
261
|
const txTimeoutAt = new Date((slotStartBuildTimestamp + aztecSlotDuration) * 1000);
|
|
246
262
|
await this.publisher.enqueueProposeCheckpoint(checkpoint, attestations, attestationsSignature, {
|
|
247
263
|
txTimeoutAt,
|
|
248
|
-
|
|
264
|
+
forcePendingCheckpointNumber: this.invalidateCheckpoint?.forcePendingCheckpointNumber,
|
|
249
265
|
});
|
|
250
266
|
|
|
251
267
|
return checkpoint;
|
|
@@ -262,17 +278,18 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
262
278
|
private async buildBlocksForCheckpoint(
|
|
263
279
|
checkpointBuilder: CheckpointBuilder,
|
|
264
280
|
timestamp: bigint,
|
|
281
|
+
inHash: Fr,
|
|
265
282
|
blockProposalOptions: BlockProposalOptions,
|
|
266
283
|
): Promise<{
|
|
267
284
|
blocksInCheckpoint: L2BlockNew[];
|
|
268
|
-
|
|
285
|
+
blockPendingBroadcast: { block: L2BlockNew; txs: Tx[] } | undefined;
|
|
269
286
|
}> {
|
|
270
287
|
const blocksInCheckpoint: L2BlockNew[] = [];
|
|
271
288
|
const txHashesAlreadyIncluded = new Set<string>();
|
|
272
289
|
const initialBlockNumber = BlockNumber(this.syncedToBlockNumber + 1);
|
|
273
290
|
|
|
274
291
|
// Last block in the checkpoint will usually be flagged as pending broadcast, so we send it along with the checkpoint proposal
|
|
275
|
-
let
|
|
292
|
+
let blockPendingBroadcast: { block: L2BlockNew; txs: Tx[] } | undefined = undefined;
|
|
276
293
|
|
|
277
294
|
while (true) {
|
|
278
295
|
const blocksBuilt = blocksInCheckpoint.length;
|
|
@@ -342,17 +359,17 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
342
359
|
blockNumber,
|
|
343
360
|
blocksBuilt,
|
|
344
361
|
});
|
|
345
|
-
|
|
362
|
+
blockPendingBroadcast = { block, txs: usedTxs };
|
|
346
363
|
break;
|
|
347
364
|
}
|
|
348
365
|
|
|
349
366
|
// For non-last blocks, broadcast the block proposal (unless we're in fisherman mode)
|
|
350
367
|
// If the block is the last one, we'll broadcast it along with the checkpoint at the end of the loop
|
|
351
368
|
if (!this.config.fishermanMode) {
|
|
352
|
-
// TODO(palla/mbps): Wire this to the new p2p API once available
|
|
353
369
|
const proposal = await this.validatorClient.createBlockProposal(
|
|
354
|
-
block.header
|
|
355
|
-
|
|
370
|
+
block.header,
|
|
371
|
+
block.indexWithinCheckpoint,
|
|
372
|
+
inHash,
|
|
356
373
|
block.archive.root,
|
|
357
374
|
usedTxs,
|
|
358
375
|
this.proposer,
|
|
@@ -370,10 +387,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
370
387
|
blocksBuilt: blocksInCheckpoint.length,
|
|
371
388
|
});
|
|
372
389
|
|
|
373
|
-
return {
|
|
374
|
-
blocksInCheckpoint,
|
|
375
|
-
pendingBroadcast,
|
|
376
|
-
};
|
|
390
|
+
return { blocksInCheckpoint, blockPendingBroadcast };
|
|
377
391
|
}
|
|
378
392
|
|
|
379
393
|
/** Sleeps until it is time to produce the next block in the slot */
|
|
@@ -539,7 +553,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
539
553
|
* This is run after all blocks for the checkpoint have been built.
|
|
540
554
|
*/
|
|
541
555
|
@trackSpan('CheckpointProposalJob.waitForAttestations')
|
|
542
|
-
private async waitForAttestations(proposal:
|
|
556
|
+
private async waitForAttestations(proposal: CheckpointProposal): Promise<CommitteeAttestationsAndSigners> {
|
|
543
557
|
if (this.config.fishermanMode) {
|
|
544
558
|
this.log.debug('Skipping attestation collection in fisherman mode');
|
|
545
559
|
return CommitteeAttestationsAndSigners.empty();
|
|
@@ -588,8 +602,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
588
602
|
|
|
589
603
|
// Manipulate the attestations if we've been configured to do so
|
|
590
604
|
if (this.config.injectFakeAttestation || this.config.shuffleAttestationOrdering) {
|
|
591
|
-
|
|
592
|
-
return this.manipulateAttestations(checkpoint, epoch, seed, committee, sorted);
|
|
605
|
+
return this.manipulateAttestations(proposal.slotNumber, epoch, seed, committee, sorted);
|
|
593
606
|
}
|
|
594
607
|
|
|
595
608
|
return new CommitteeAttestationsAndSigners(sorted);
|
|
@@ -605,7 +618,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
605
618
|
|
|
606
619
|
/** Breaks the attestations before publishing based on attack configs */
|
|
607
620
|
private manipulateAttestations(
|
|
608
|
-
|
|
621
|
+
slotNumber: SlotNumber,
|
|
609
622
|
epoch: EpochNumber,
|
|
610
623
|
seed: bigint,
|
|
611
624
|
committee: EthAddress[],
|
|
@@ -613,7 +626,6 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
613
626
|
) {
|
|
614
627
|
// Compute the proposer index in the committee, since we dont want to tweak it.
|
|
615
628
|
// Otherwise, the L1 rollup contract will reject the block outright.
|
|
616
|
-
const { slotNumber } = checkpoint;
|
|
617
629
|
const proposerIndex = Number(
|
|
618
630
|
this.epochCache.computeProposerIndex(slotNumber, epoch, seed, BigInt(committee.length)),
|
|
619
631
|
);
|
|
@@ -662,16 +674,24 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
662
674
|
}
|
|
663
675
|
|
|
664
676
|
/**
|
|
665
|
-
*
|
|
666
|
-
*
|
|
677
|
+
* Adds the proposed block to the archiver so it's available via P2P.
|
|
678
|
+
* Gossip doesn't echo messages back to the sender, so the proposer's archiver/world-state
|
|
679
|
+
* would never receive its own block without this explicit sync.
|
|
667
680
|
*/
|
|
668
681
|
private async syncProposedBlockToArchiver(block: L2BlockNew): Promise<void> {
|
|
669
|
-
|
|
682
|
+
// TODO(palla/mbps): Change default to false once block sync is stable.
|
|
683
|
+
if (this.config.skipPushProposedBlocksToArchiver !== false) {
|
|
684
|
+
this.log.warn(`Skipping push of proposed block ${block.number} to archiver`, {
|
|
685
|
+
blockNumber: block.number,
|
|
686
|
+
slot: block.header.globalVariables.slotNumber,
|
|
687
|
+
});
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
this.log.debug(`Syncing proposed block ${block.number} to archiver`, {
|
|
670
691
|
blockNumber: block.number,
|
|
671
692
|
slot: block.header.globalVariables.slotNumber,
|
|
672
693
|
});
|
|
673
|
-
|
|
674
|
-
await Promise.resolve();
|
|
694
|
+
await this.blockSink.addBlock(block);
|
|
675
695
|
}
|
|
676
696
|
|
|
677
697
|
/** Runs fee analysis and logs checkpoint outcome as fisherman */
|
package/src/sequencer/index.ts
CHANGED
package/src/sequencer/metrics.ts
CHANGED
|
@@ -43,7 +43,7 @@ export class SequencerMetrics {
|
|
|
43
43
|
private blockProposalPrecheckFailed: UpDownCounter;
|
|
44
44
|
private checkpointSuccess: UpDownCounter;
|
|
45
45
|
private slashingAttempts: UpDownCounter;
|
|
46
|
-
private
|
|
46
|
+
private checkpointAttestationDelay: Histogram;
|
|
47
47
|
|
|
48
48
|
// Fisherman fee analysis metrics
|
|
49
49
|
private fishermanWouldBeIncluded: UpDownCounter;
|
|
@@ -75,7 +75,7 @@ export class SequencerMetrics {
|
|
|
75
75
|
|
|
76
76
|
this.stateTransitionBufferDuration = this.meter.createHistogram(Metrics.SEQUENCER_STATE_TRANSITION_BUFFER_DURATION);
|
|
77
77
|
|
|
78
|
-
this.
|
|
78
|
+
this.checkpointAttestationDelay = this.meter.createHistogram(Metrics.SEQUENCER_CHECKPOINT_ATTESTATION_DELAY);
|
|
79
79
|
|
|
80
80
|
// Init gauges and counters
|
|
81
81
|
this.blockCounter.add(0, {
|
|
@@ -156,8 +156,8 @@ export class SequencerMetrics {
|
|
|
156
156
|
this.timeToCollectAttestations.record(0);
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
public
|
|
160
|
-
this.
|
|
159
|
+
public recordCheckpointAttestationDelay(duration: number) {
|
|
160
|
+
this.checkpointAttestationDelay.record(duration);
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
public recordCollectedAttestations(count: number, durationMs: number) {
|