@aztec/sequencer-client 4.0.0-nightly.20260112 → 4.0.0-nightly.20260114
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 +2 -2
- package/dest/sequencer/block_builder.d.ts.map +1 -1
- package/dest/sequencer/block_builder.js +5 -5
- package/dest/sequencer/checkpoint_proposal_job.d.ts +11 -8
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +56 -30
- 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 +13 -12
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +30 -28
- 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 +13 -3
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +27 -2
- package/dest/test/utils.d.ts +8 -4
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +19 -9
- 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 +5 -3
- package/src/sequencer/checkpoint_proposal_job.ts +68 -38
- package/src/sequencer/index.ts +0 -1
- package/src/sequencer/metrics.ts +4 -4
- package/src/sequencer/sequencer.ts +47 -34
- package/src/test/index.ts +1 -2
- package/src/test/mock_checkpoint_builder.ts +45 -3
- package/src/test/utils.ts +36 -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
|
|
|
@@ -37,6 +36,7 @@ const log = createLogger('block-builder');
|
|
|
37
36
|
async function buildBlock(
|
|
38
37
|
pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
|
|
39
38
|
l1ToL2Messages: Fr[],
|
|
39
|
+
previousCheckpointOutHashes: Fr[],
|
|
40
40
|
newGlobalVariables: GlobalVariables,
|
|
41
41
|
opts: PublicProcessorLimits = {},
|
|
42
42
|
worldStateFork: MerkleTreeWriteOperations,
|
|
@@ -63,7 +63,7 @@ async function buildBlock(
|
|
|
63
63
|
initialArchiveRoot: bufferToHex(archiveTree.root),
|
|
64
64
|
opts,
|
|
65
65
|
});
|
|
66
|
-
const blockFactory = new LightweightBlockFactory(worldStateFork, telemetryClient);
|
|
66
|
+
const blockFactory = new LightweightBlockFactory(previousCheckpointOutHashes, worldStateFork, telemetryClient);
|
|
67
67
|
await blockFactory.startNewBlock(newGlobalVariables, l1ToL2Messages);
|
|
68
68
|
|
|
69
69
|
const [publicProcessorDuration, [processedTxs, failedTxs, usedTxs]] = await elapsed(() =>
|
|
@@ -168,6 +168,7 @@ export class FullNodeBlockBuilder implements IFullNodeBlockBuilder {
|
|
|
168
168
|
async buildBlock(
|
|
169
169
|
pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
|
|
170
170
|
l1ToL2Messages: Fr[],
|
|
171
|
+
previousCheckpointOutHashes: Fr[],
|
|
171
172
|
globalVariables: GlobalVariables,
|
|
172
173
|
opts: PublicProcessorLimits,
|
|
173
174
|
suppliedFork?: MerkleTreeWriteOperations,
|
|
@@ -182,6 +183,7 @@ export class FullNodeBlockBuilder implements IFullNodeBlockBuilder {
|
|
|
182
183
|
const res = await buildBlock(
|
|
183
184
|
pendingTxs,
|
|
184
185
|
l1ToL2Messages,
|
|
186
|
+
previousCheckpointOutHashes,
|
|
185
187
|
globalVariables,
|
|
186
188
|
opts,
|
|
187
189
|
fork,
|
|
@@ -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,8 @@ import {
|
|
|
15
16
|
CommitteeAttestation,
|
|
16
17
|
CommitteeAttestationsAndSigners,
|
|
17
18
|
L2BlockNew,
|
|
19
|
+
type L2BlockSink,
|
|
20
|
+
type L2BlockSource,
|
|
18
21
|
MaliciousCommitteeAttestationsAndSigners,
|
|
19
22
|
} from '@aztec/stdlib/block';
|
|
20
23
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
@@ -25,19 +28,17 @@ import type {
|
|
|
25
28
|
ResolvedSequencerConfig,
|
|
26
29
|
WorldStateSynchronizer,
|
|
27
30
|
} from '@aztec/stdlib/interfaces/server';
|
|
28
|
-
import type
|
|
29
|
-
import type {
|
|
31
|
+
import { type L1ToL2MessageSource, computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
|
|
32
|
+
import type { BlockProposalOptions, CheckpointProposal, CheckpointProposalOptions } from '@aztec/stdlib/p2p';
|
|
30
33
|
import { orderAttestations } from '@aztec/stdlib/p2p';
|
|
31
|
-
import { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
32
34
|
import type { L2BlockBuiltStats } from '@aztec/stdlib/stats';
|
|
33
35
|
import { type FailedTx, Tx } from '@aztec/stdlib/tx';
|
|
34
36
|
import { AttestationTimeoutError } from '@aztec/stdlib/validators';
|
|
35
37
|
import { Attributes, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
|
|
36
|
-
import type
|
|
38
|
+
import { CheckpointBuilder, type FullNodeCheckpointsBuilder, type ValidatorClient } from '@aztec/validator-client';
|
|
37
39
|
|
|
38
40
|
import type { GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
|
|
39
|
-
import type {
|
|
40
|
-
import { CheckpointBuilder, type FullNodeCheckpointsBuilder } from './checkpoint_builder.js';
|
|
41
|
+
import type { InvalidateCheckpointRequest, SequencerPublisher } from '../publisher/sequencer-publisher.js';
|
|
41
42
|
import { CheckpointVoter } from './checkpoint_voter.js';
|
|
42
43
|
import { SequencerInterruptedError } from './errors.js';
|
|
43
44
|
import type { SequencerEvents } from './events.js';
|
|
@@ -57,6 +58,7 @@ const TXS_POLLING_MS = 500;
|
|
|
57
58
|
*/
|
|
58
59
|
export class CheckpointProposalJob implements Traceable {
|
|
59
60
|
constructor(
|
|
61
|
+
private readonly epoch: EpochNumber,
|
|
60
62
|
private readonly slot: SlotNumber,
|
|
61
63
|
private readonly checkpointNumber: CheckpointNumber,
|
|
62
64
|
private readonly syncedToBlockNumber: BlockNumber,
|
|
@@ -64,13 +66,15 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
64
66
|
private readonly proposer: EthAddress | undefined,
|
|
65
67
|
private readonly publisher: SequencerPublisher,
|
|
66
68
|
private readonly attestorAddress: EthAddress,
|
|
67
|
-
private readonly
|
|
69
|
+
private readonly invalidateCheckpoint: InvalidateCheckpointRequest | undefined,
|
|
68
70
|
private readonly validatorClient: ValidatorClient,
|
|
69
71
|
private readonly globalsBuilder: GlobalVariableBuilder,
|
|
70
72
|
private readonly p2pClient: P2P,
|
|
71
73
|
private readonly worldState: WorldStateSynchronizer,
|
|
72
74
|
private readonly l1ToL2MessageSource: L1ToL2MessageSource,
|
|
75
|
+
private readonly l2BlockSource: L2BlockSource,
|
|
73
76
|
private readonly checkpointsBuilder: FullNodeCheckpointsBuilder,
|
|
77
|
+
private readonly blockSink: L2BlockSink,
|
|
74
78
|
private readonly l1Constants: SequencerRollupConstants,
|
|
75
79
|
protected config: ResolvedSequencerConfig,
|
|
76
80
|
protected timetable: SequencerTimetable,
|
|
@@ -152,9 +156,9 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
152
156
|
this.setStateFn(SequencerState.INITIALIZING_CHECKPOINT, this.slot);
|
|
153
157
|
this.metrics.incOpenSlot(this.slot, this.proposer?.toString() ?? 'unknown');
|
|
154
158
|
|
|
155
|
-
// Enqueues
|
|
156
|
-
if (this.
|
|
157
|
-
this.publisher.
|
|
159
|
+
// Enqueues checkpoint invalidation (constant for the whole slot)
|
|
160
|
+
if (this.invalidateCheckpoint && !this.config.skipInvalidateBlockAsProposer) {
|
|
161
|
+
this.publisher.enqueueInvalidateCheckpoint(this.invalidateCheckpoint);
|
|
158
162
|
}
|
|
159
163
|
|
|
160
164
|
// Create checkpoint builder for the slot
|
|
@@ -164,8 +168,15 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
164
168
|
this.slot,
|
|
165
169
|
);
|
|
166
170
|
|
|
167
|
-
// Collect L1 to L2 messages for the checkpoint
|
|
171
|
+
// Collect L1 to L2 messages for the checkpoint and compute their hash
|
|
168
172
|
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(this.checkpointNumber);
|
|
173
|
+
const inHash = computeInHashFromL1ToL2Messages(l1ToL2Messages);
|
|
174
|
+
|
|
175
|
+
// Collect the out hashes of all the checkpoints before this one in the same epoch
|
|
176
|
+
const previousCheckpoints = (await this.l2BlockSource.getCheckpointsForEpoch(this.epoch)).filter(
|
|
177
|
+
c => c.number < this.checkpointNumber,
|
|
178
|
+
);
|
|
179
|
+
const previousCheckpointOutHashes = previousCheckpoints.map(c => c.getCheckpointOutHash());
|
|
169
180
|
|
|
170
181
|
// Create a long-lived forked world state for the checkpoint builder
|
|
171
182
|
using fork = await this.worldState.fork(this.syncedToBlockNumber, { closeDelayMs: 12_000 });
|
|
@@ -175,6 +186,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
175
186
|
this.checkpointNumber,
|
|
176
187
|
checkpointGlobalVariables,
|
|
177
188
|
l1ToL2Messages,
|
|
189
|
+
previousCheckpointOutHashes,
|
|
178
190
|
fork,
|
|
179
191
|
);
|
|
180
192
|
|
|
@@ -184,10 +196,16 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
184
196
|
broadcastInvalidBlockProposal: this.config.broadcastInvalidBlockProposal,
|
|
185
197
|
};
|
|
186
198
|
|
|
199
|
+
const checkpointProposalOptions: CheckpointProposalOptions = {
|
|
200
|
+
publishFullTxs: !!this.config.publishTxsWithProposals,
|
|
201
|
+
broadcastInvalidCheckpointProposal: this.config.broadcastInvalidBlockProposal,
|
|
202
|
+
};
|
|
203
|
+
|
|
187
204
|
// Main loop: build blocks for the checkpoint
|
|
188
|
-
const { blocksInCheckpoint,
|
|
205
|
+
const { blocksInCheckpoint, blockPendingBroadcast } = await this.buildBlocksForCheckpoint(
|
|
189
206
|
checkpointBuilder,
|
|
190
207
|
checkpointGlobalVariables.timestamp,
|
|
208
|
+
inHash,
|
|
191
209
|
blockProposalOptions,
|
|
192
210
|
);
|
|
193
211
|
|
|
@@ -217,22 +235,30 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
217
235
|
return checkpoint;
|
|
218
236
|
}
|
|
219
237
|
|
|
220
|
-
//
|
|
238
|
+
// Include the block pending broadcast in the checkpoint proposal if any
|
|
239
|
+
const lastBlock = blockPendingBroadcast && {
|
|
240
|
+
blockHeader: blockPendingBroadcast.block.header,
|
|
241
|
+
indexWithinCheckpoint: blockPendingBroadcast.block.indexWithinCheckpoint,
|
|
242
|
+
txs: blockPendingBroadcast.txs,
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// Create the checkpoint proposal and broadcast it
|
|
221
246
|
const proposal = await this.validatorClient.createCheckpointProposal(
|
|
222
247
|
checkpoint.header,
|
|
223
248
|
checkpoint.archive.root,
|
|
224
|
-
|
|
249
|
+
lastBlock,
|
|
225
250
|
this.proposer,
|
|
226
|
-
|
|
251
|
+
checkpointProposalOptions,
|
|
227
252
|
);
|
|
253
|
+
|
|
228
254
|
const blockProposedAt = this.dateProvider.now();
|
|
229
|
-
await this.p2pClient.
|
|
255
|
+
await this.p2pClient.broadcastCheckpointProposal(proposal);
|
|
230
256
|
|
|
231
257
|
this.setStateFn(SequencerState.COLLECTING_ATTESTATIONS, this.slot);
|
|
232
258
|
const attestations = await this.waitForAttestations(proposal);
|
|
233
259
|
const blockAttestedAt = this.dateProvider.now();
|
|
234
260
|
|
|
235
|
-
this.metrics.
|
|
261
|
+
this.metrics.recordCheckpointAttestationDelay(blockAttestedAt - blockProposedAt);
|
|
236
262
|
|
|
237
263
|
// Proposer must sign over the attestations before pushing them to L1
|
|
238
264
|
const signer = this.proposer ?? this.publisher.getSenderAddress();
|
|
@@ -245,7 +271,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
245
271
|
const txTimeoutAt = new Date((slotStartBuildTimestamp + aztecSlotDuration) * 1000);
|
|
246
272
|
await this.publisher.enqueueProposeCheckpoint(checkpoint, attestations, attestationsSignature, {
|
|
247
273
|
txTimeoutAt,
|
|
248
|
-
|
|
274
|
+
forcePendingCheckpointNumber: this.invalidateCheckpoint?.forcePendingCheckpointNumber,
|
|
249
275
|
});
|
|
250
276
|
|
|
251
277
|
return checkpoint;
|
|
@@ -262,17 +288,18 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
262
288
|
private async buildBlocksForCheckpoint(
|
|
263
289
|
checkpointBuilder: CheckpointBuilder,
|
|
264
290
|
timestamp: bigint,
|
|
291
|
+
inHash: Fr,
|
|
265
292
|
blockProposalOptions: BlockProposalOptions,
|
|
266
293
|
): Promise<{
|
|
267
294
|
blocksInCheckpoint: L2BlockNew[];
|
|
268
|
-
|
|
295
|
+
blockPendingBroadcast: { block: L2BlockNew; txs: Tx[] } | undefined;
|
|
269
296
|
}> {
|
|
270
297
|
const blocksInCheckpoint: L2BlockNew[] = [];
|
|
271
298
|
const txHashesAlreadyIncluded = new Set<string>();
|
|
272
299
|
const initialBlockNumber = BlockNumber(this.syncedToBlockNumber + 1);
|
|
273
300
|
|
|
274
301
|
// Last block in the checkpoint will usually be flagged as pending broadcast, so we send it along with the checkpoint proposal
|
|
275
|
-
let
|
|
302
|
+
let blockPendingBroadcast: { block: L2BlockNew; txs: Tx[] } | undefined = undefined;
|
|
276
303
|
|
|
277
304
|
while (true) {
|
|
278
305
|
const blocksBuilt = blocksInCheckpoint.length;
|
|
@@ -342,17 +369,17 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
342
369
|
blockNumber,
|
|
343
370
|
blocksBuilt,
|
|
344
371
|
});
|
|
345
|
-
|
|
372
|
+
blockPendingBroadcast = { block, txs: usedTxs };
|
|
346
373
|
break;
|
|
347
374
|
}
|
|
348
375
|
|
|
349
376
|
// For non-last blocks, broadcast the block proposal (unless we're in fisherman mode)
|
|
350
377
|
// If the block is the last one, we'll broadcast it along with the checkpoint at the end of the loop
|
|
351
378
|
if (!this.config.fishermanMode) {
|
|
352
|
-
// TODO(palla/mbps): Wire this to the new p2p API once available
|
|
353
379
|
const proposal = await this.validatorClient.createBlockProposal(
|
|
354
|
-
block.header
|
|
355
|
-
|
|
380
|
+
block.header,
|
|
381
|
+
block.indexWithinCheckpoint,
|
|
382
|
+
inHash,
|
|
356
383
|
block.archive.root,
|
|
357
384
|
usedTxs,
|
|
358
385
|
this.proposer,
|
|
@@ -370,10 +397,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
370
397
|
blocksBuilt: blocksInCheckpoint.length,
|
|
371
398
|
});
|
|
372
399
|
|
|
373
|
-
return {
|
|
374
|
-
blocksInCheckpoint,
|
|
375
|
-
pendingBroadcast,
|
|
376
|
-
};
|
|
400
|
+
return { blocksInCheckpoint, blockPendingBroadcast };
|
|
377
401
|
}
|
|
378
402
|
|
|
379
403
|
/** Sleeps until it is time to produce the next block in the slot */
|
|
@@ -539,7 +563,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
539
563
|
* This is run after all blocks for the checkpoint have been built.
|
|
540
564
|
*/
|
|
541
565
|
@trackSpan('CheckpointProposalJob.waitForAttestations')
|
|
542
|
-
private async waitForAttestations(proposal:
|
|
566
|
+
private async waitForAttestations(proposal: CheckpointProposal): Promise<CommitteeAttestationsAndSigners> {
|
|
543
567
|
if (this.config.fishermanMode) {
|
|
544
568
|
this.log.debug('Skipping attestation collection in fisherman mode');
|
|
545
569
|
return CommitteeAttestationsAndSigners.empty();
|
|
@@ -588,8 +612,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
588
612
|
|
|
589
613
|
// Manipulate the attestations if we've been configured to do so
|
|
590
614
|
if (this.config.injectFakeAttestation || this.config.shuffleAttestationOrdering) {
|
|
591
|
-
|
|
592
|
-
return this.manipulateAttestations(checkpoint, epoch, seed, committee, sorted);
|
|
615
|
+
return this.manipulateAttestations(proposal.slotNumber, epoch, seed, committee, sorted);
|
|
593
616
|
}
|
|
594
617
|
|
|
595
618
|
return new CommitteeAttestationsAndSigners(sorted);
|
|
@@ -605,7 +628,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
605
628
|
|
|
606
629
|
/** Breaks the attestations before publishing based on attack configs */
|
|
607
630
|
private manipulateAttestations(
|
|
608
|
-
|
|
631
|
+
slotNumber: SlotNumber,
|
|
609
632
|
epoch: EpochNumber,
|
|
610
633
|
seed: bigint,
|
|
611
634
|
committee: EthAddress[],
|
|
@@ -613,7 +636,6 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
613
636
|
) {
|
|
614
637
|
// Compute the proposer index in the committee, since we dont want to tweak it.
|
|
615
638
|
// Otherwise, the L1 rollup contract will reject the block outright.
|
|
616
|
-
const { slotNumber } = checkpoint;
|
|
617
639
|
const proposerIndex = Number(
|
|
618
640
|
this.epochCache.computeProposerIndex(slotNumber, epoch, seed, BigInt(committee.length)),
|
|
619
641
|
);
|
|
@@ -662,16 +684,24 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
662
684
|
}
|
|
663
685
|
|
|
664
686
|
/**
|
|
665
|
-
*
|
|
666
|
-
*
|
|
687
|
+
* Adds the proposed block to the archiver so it's available via P2P.
|
|
688
|
+
* Gossip doesn't echo messages back to the sender, so the proposer's archiver/world-state
|
|
689
|
+
* would never receive its own block without this explicit sync.
|
|
667
690
|
*/
|
|
668
691
|
private async syncProposedBlockToArchiver(block: L2BlockNew): Promise<void> {
|
|
669
|
-
|
|
692
|
+
// TODO(palla/mbps): Change default to false once block sync is stable.
|
|
693
|
+
if (this.config.skipPushProposedBlocksToArchiver !== false) {
|
|
694
|
+
this.log.warn(`Skipping push of proposed block ${block.number} to archiver`, {
|
|
695
|
+
blockNumber: block.number,
|
|
696
|
+
slot: block.header.globalVariables.slotNumber,
|
|
697
|
+
});
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
this.log.debug(`Syncing proposed block ${block.number} to archiver`, {
|
|
670
701
|
blockNumber: block.number,
|
|
671
702
|
slot: block.header.globalVariables.slotNumber,
|
|
672
703
|
});
|
|
673
|
-
|
|
674
|
-
await Promise.resolve();
|
|
704
|
+
await this.blockSink.addBlock(block);
|
|
675
705
|
}
|
|
676
706
|
|
|
677
707
|
/** Runs fee analysis and logs checkpoint outcome as fisherman */
|