@aztec/sequencer-client 3.0.0-canary.a9708bd → 3.0.0-devnet.3

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.
Files changed (56) hide show
  1. package/dest/client/sequencer-client.d.ts +5 -4
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +17 -12
  4. package/dest/config.d.ts +2 -1
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +10 -0
  7. package/dest/publisher/config.d.ts +2 -8
  8. package/dest/publisher/config.d.ts.map +1 -1
  9. package/dest/publisher/config.js +7 -16
  10. package/dest/publisher/index.d.ts +1 -1
  11. package/dest/publisher/index.d.ts.map +1 -1
  12. package/dest/publisher/index.js +1 -1
  13. package/dest/publisher/sequencer-publisher-factory.d.ts +6 -1
  14. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  15. package/dest/publisher/sequencer-publisher-factory.js +8 -1
  16. package/dest/publisher/sequencer-publisher-metrics.d.ts +1 -1
  17. package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
  18. package/dest/publisher/sequencer-publisher-metrics.js +1 -1
  19. package/dest/publisher/sequencer-publisher.d.ts +16 -22
  20. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  21. package/dest/publisher/sequencer-publisher.js +72 -72
  22. package/dest/sequencer/block_builder.d.ts +2 -5
  23. package/dest/sequencer/block_builder.d.ts.map +1 -1
  24. package/dest/sequencer/block_builder.js +18 -6
  25. package/dest/sequencer/errors.d.ts +11 -0
  26. package/dest/sequencer/errors.d.ts.map +1 -0
  27. package/dest/sequencer/errors.js +15 -0
  28. package/dest/sequencer/metrics.d.ts +6 -18
  29. package/dest/sequencer/metrics.d.ts.map +1 -1
  30. package/dest/sequencer/metrics.js +22 -88
  31. package/dest/sequencer/sequencer.d.ts +8 -7
  32. package/dest/sequencer/sequencer.d.ts.map +1 -1
  33. package/dest/sequencer/sequencer.js +55 -33
  34. package/dest/sequencer/timetable.d.ts +1 -7
  35. package/dest/sequencer/timetable.d.ts.map +1 -1
  36. package/dest/sequencer/timetable.js +3 -10
  37. package/dest/sequencer/utils.d.ts +10 -24
  38. package/dest/sequencer/utils.d.ts.map +1 -1
  39. package/dest/sequencer/utils.js +9 -24
  40. package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
  41. package/dest/tx_validator/tx_validator_factory.js +11 -8
  42. package/package.json +29 -29
  43. package/src/client/sequencer-client.ts +18 -9
  44. package/src/config.ts +11 -0
  45. package/src/publisher/config.ts +13 -22
  46. package/src/publisher/index.ts +1 -1
  47. package/src/publisher/sequencer-publisher-factory.ts +13 -2
  48. package/src/publisher/sequencer-publisher-metrics.ts +1 -1
  49. package/src/publisher/sequencer-publisher.ts +101 -98
  50. package/src/sequencer/block_builder.ts +20 -21
  51. package/src/sequencer/errors.ts +21 -0
  52. package/src/sequencer/metrics.ts +25 -101
  53. package/src/sequencer/sequencer.ts +85 -58
  54. package/src/sequencer/timetable.ts +3 -14
  55. package/src/sequencer/utils.ts +10 -24
  56. package/src/tx_validator/tx_validator_factory.ts +10 -5
@@ -1,16 +1,15 @@
1
- import type { L2Block } from '@aztec/aztec.js';
2
- import { Blob } from '@aztec/blob-lib';
1
+ import { L2Block } from '@aztec/aztec.js/block';
2
+ import { Blob, getBlobsPerL1Block, getPrefixedEthBlobCommitments } from '@aztec/blob-lib';
3
3
  import { type BlobSinkClientInterface, createBlobSinkClient } from '@aztec/blob-sink/client';
4
4
  import type { EpochCache } from '@aztec/epoch-cache';
5
5
  import {
6
6
  type EmpireSlashingProposerContract,
7
7
  FormattedViemError,
8
- type GasPrice,
9
8
  type GovernanceProposerContract,
10
9
  type IEmpireBase,
11
10
  type L1BlobInputs,
12
11
  type L1ContractsConfig,
13
- type L1GasConfig,
12
+ type L1TxConfig,
14
13
  type L1TxRequest,
15
14
  MULTI_CALL_3_ADDRESS,
16
15
  Multicall3,
@@ -27,20 +26,20 @@ import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs'
27
26
  import { sumBigint } from '@aztec/foundation/bigint';
28
27
  import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
29
28
  import { EthAddress } from '@aztec/foundation/eth-address';
29
+ import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
30
30
  import type { Fr } from '@aztec/foundation/fields';
31
- import { createLogger } from '@aztec/foundation/log';
31
+ import { type Logger, createLogger } from '@aztec/foundation/log';
32
32
  import { bufferToHex } from '@aztec/foundation/string';
33
33
  import { DateProvider, Timer } from '@aztec/foundation/timer';
34
34
  import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
35
35
  import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
36
- import { CommitteeAttestation, type ValidateBlockResult } from '@aztec/stdlib/block';
36
+ import { CommitteeAttestation, CommitteeAttestationsAndSigners, type ValidateBlockResult } from '@aztec/stdlib/block';
37
37
  import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
38
- import { ConsensusPayload, SignatureDomainSeparator, getHashedSignaturePayload } from '@aztec/stdlib/p2p';
38
+ import type { CheckpointHeader } from '@aztec/stdlib/rollup';
39
39
  import type { L1PublishBlockStats } from '@aztec/stdlib/stats';
40
- import { type ProposedBlockHeader, StateReference, TxHash } from '@aztec/stdlib/tx';
40
+ import { StateReference } from '@aztec/stdlib/tx';
41
41
  import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
42
42
 
43
- import pick from 'lodash.pick';
44
43
  import { type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
45
44
 
46
45
  import type { PublisherConfig, TxSenderConfig } from './config.js';
@@ -49,24 +48,19 @@ import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
49
48
  /** Arguments to the process method of the rollup contract */
50
49
  type L1ProcessArgs = {
51
50
  /** The L2 block header. */
52
- header: ProposedBlockHeader;
51
+ header: CheckpointHeader;
53
52
  /** A root of the archive tree after the L2 block is applied. */
54
53
  archive: Buffer;
55
54
  /** State reference after the L2 block is applied. */
56
55
  stateReference: StateReference;
57
56
  /** L2 block blobs containing all tx effects. */
58
57
  blobs: Blob[];
59
- /** L2 block tx hashes */
60
- txHashes: TxHash[];
61
58
  /** Attestations */
62
- attestations?: CommitteeAttestation[];
59
+ attestationsAndSigners: CommitteeAttestationsAndSigners;
60
+ /** Attestations and signers signature */
61
+ attestationsAndSignersSignature: Signature;
63
62
  };
64
63
 
65
- export enum SignalType {
66
- GOVERNANCE,
67
- SLASHING,
68
- }
69
-
70
64
  export const Actions = [
71
65
  'invalidate-by-invalid-attestation',
72
66
  'invalidate-by-insufficient-attestations',
@@ -78,8 +72,11 @@ export const Actions = [
78
72
  'vote-offenses',
79
73
  'execute-slash',
80
74
  ] as const;
75
+
81
76
  export type Action = (typeof Actions)[number];
82
77
 
78
+ type GovernanceSignalAction = Extract<Action, 'governance-signal' | 'empire-slashing-signal'>;
79
+
83
80
  // Sorting for actions such that invalidations go before proposals, and proposals go before votes
84
81
  export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
85
82
 
@@ -95,11 +92,11 @@ interface RequestWithExpiry {
95
92
  action: Action;
96
93
  request: L1TxRequest;
97
94
  lastValidL2Slot: bigint;
98
- gasConfig?: Pick<L1GasConfig, 'txTimeoutAt' | 'gasLimit'>;
95
+ gasConfig?: Pick<L1TxConfig, 'txTimeoutAt' | 'gasLimit'>;
99
96
  blobConfig?: L1BlobInputs;
100
97
  checkSuccess: (
101
98
  request: L1TxRequest,
102
- result?: { receipt: TransactionReceipt; gasPrice: GasPrice; stats?: TransactionStats; errorMsg?: string },
99
+ result?: { receipt: TransactionReceipt; stats?: TransactionStats; errorMsg?: string },
103
100
  ) => boolean;
104
101
  }
105
102
 
@@ -111,12 +108,9 @@ export class SequencerPublisher {
111
108
  protected governanceLog = createLogger('sequencer:publisher:governance');
112
109
  protected slashingLog = createLogger('sequencer:publisher:slashing');
113
110
 
114
- private myLastSignals: Record<SignalType, bigint> = {
115
- [SignalType.GOVERNANCE]: 0n,
116
- [SignalType.SLASHING]: 0n,
117
- };
111
+ protected lastActions: Partial<Record<Action, bigint>> = {};
118
112
 
119
- protected log = createLogger('sequencer:publisher');
113
+ protected log: Logger;
120
114
  protected ethereumSlotDuration: bigint;
121
115
 
122
116
  private blobSinkClient: BlobSinkClientInterface;
@@ -152,10 +146,14 @@ export class SequencerPublisher {
152
146
  epochCache: EpochCache;
153
147
  dateProvider: DateProvider;
154
148
  metrics: SequencerPublisherMetrics;
149
+ lastActions: Partial<Record<Action, bigint>>;
150
+ log?: Logger;
155
151
  },
156
152
  ) {
153
+ this.log = deps.log ?? createLogger('sequencer:publisher');
157
154
  this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
158
155
  this.epochCache = deps.epochCache;
156
+ this.lastActions = deps.lastActions;
159
157
 
160
158
  this.blobSinkClient =
161
159
  deps.blobSinkClient ?? createBlobSinkClient(config, { logger: createLogger('sequencer:blob-sink:client') });
@@ -249,18 +247,21 @@ export class SequencerPublisher {
249
247
  const gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
250
248
  const txTimeoutAts = gasConfigs.map(g => g?.txTimeoutAt).filter((g): g is Date => g !== undefined);
251
249
  const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map(g => g.getTime()))) : undefined; // earliest
252
- const gasConfig: RequestWithExpiry['gasConfig'] = { gasLimit, txTimeoutAt };
250
+ const txConfig: RequestWithExpiry['gasConfig'] = { gasLimit, txTimeoutAt };
253
251
 
254
252
  // Sort the requests so that proposals always go first
255
253
  // This ensures the committee gets precomputed correctly
256
254
  validRequests.sort((a, b) => compareActions(a.action, b.action));
257
255
 
258
256
  try {
259
- this.log.debug('Forwarding transactions', { validRequests: validRequests.map(request => request.action) });
257
+ this.log.debug('Forwarding transactions', {
258
+ validRequests: validRequests.map(request => request.action),
259
+ txConfig,
260
+ });
260
261
  const result = await Multicall3.forward(
261
262
  validRequests.map(request => request.request),
262
263
  this.l1TxUtils,
263
- gasConfig,
264
+ txConfig,
264
265
  blobConfig,
265
266
  this.rollupContract.address,
266
267
  this.log,
@@ -285,7 +286,7 @@ export class SequencerPublisher {
285
286
 
286
287
  private callbackBundledTransactions(
287
288
  requests: RequestWithExpiry[],
288
- result?: { receipt: TransactionReceipt; gasPrice: GasPrice } | FormattedViemError,
289
+ result?: { receipt: TransactionReceipt } | FormattedViemError,
289
290
  ) {
290
291
  const actionsListStr = requests.map(r => r.action).join(', ');
291
292
  if (result instanceof FormattedViemError) {
@@ -338,16 +339,14 @@ export class SequencerPublisher {
338
339
  * It will throw if the block header is invalid.
339
340
  * @param header - The block header to validate
340
341
  */
341
- public async validateBlockHeader(
342
- header: ProposedBlockHeader,
343
- opts?: { forcePendingBlockNumber: number | undefined },
344
- ) {
342
+ public async validateBlockHeader(header: CheckpointHeader, opts?: { forcePendingBlockNumber: number | undefined }) {
345
343
  const flags = { ignoreDA: true, ignoreSignatures: true };
346
344
 
347
345
  const args = [
348
346
  header.toViem(),
349
- RollupContract.packAttestations([]),
347
+ CommitteeAttestationsAndSigners.empty().getPackedAttestations(),
350
348
  [], // no signers
349
+ Signature.empty().toViemSignature(),
351
350
  `0x${'0'.repeat(64)}`, // 32 empty bytes
352
351
  header.contentCommitment.blobsHash.toString(),
353
352
  flags,
@@ -385,11 +384,11 @@ export class SequencerPublisher {
385
384
  }
386
385
 
387
386
  const { reason, block } = validationResult;
388
- const blockNumber = block.block.number;
389
- const logData = { ...block.block.toBlockInfo(), reason };
387
+ const blockNumber = block.blockNumber;
388
+ const logData = { ...block, reason };
390
389
 
391
390
  const currentBlockNumber = await this.rollupContract.getBlockNumber();
392
- if (currentBlockNumber < validationResult.block.block.number) {
391
+ if (currentBlockNumber < validationResult.block.blockNumber) {
393
392
  this.log.verbose(
394
393
  `Skipping block ${blockNumber} invalidation since it has already been removed from the pending chain`,
395
394
  { currentBlockNumber, ...logData },
@@ -398,7 +397,7 @@ export class SequencerPublisher {
398
397
  }
399
398
 
400
399
  const request = this.buildInvalidateBlockRequest(validationResult);
401
- this.log.debug(`Simulating invalidate block ${blockNumber}`, logData);
400
+ this.log.debug(`Simulating invalidate block ${blockNumber}`, { ...logData, request });
402
401
 
403
402
  try {
404
403
  const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, ErrorsAbi);
@@ -443,20 +442,24 @@ export class SequencerPublisher {
443
442
  }
444
443
 
445
444
  const { block, committee, reason } = validationResult;
446
- const logData = { ...block.block.toBlockInfo(), reason };
447
- this.log.debug(`Simulating invalidate block ${block.block.number}`, logData);
445
+ const logData = { ...block, reason };
446
+ this.log.debug(`Simulating invalidate block ${block.blockNumber}`, logData);
447
+
448
+ const attestationsAndSigners = new CommitteeAttestationsAndSigners(
449
+ validationResult.attestations,
450
+ ).getPackedAttestations();
448
451
 
449
452
  if (reason === 'invalid-attestation') {
450
453
  return this.rollupContract.buildInvalidateBadAttestationRequest(
451
- block.block.number,
452
- block.attestations.map(a => a.toViem()),
454
+ block.blockNumber,
455
+ attestationsAndSigners,
453
456
  committee,
454
457
  validationResult.invalidIndex,
455
458
  );
456
459
  } else if (reason === 'insufficient-attestations') {
457
460
  return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
458
- block.block.number,
459
- block.attestations.map(a => a.toViem()),
461
+ block.blockNumber,
462
+ attestationsAndSigners,
460
463
  committee,
461
464
  );
462
465
  } else {
@@ -476,48 +479,42 @@ export class SequencerPublisher {
476
479
  */
477
480
  public async validateBlockForSubmission(
478
481
  block: L2Block,
479
- attestationData: { digest: Buffer; attestations: CommitteeAttestation[] } = {
480
- digest: Buffer.alloc(32),
481
- attestations: [],
482
- },
482
+ attestationsAndSigners: CommitteeAttestationsAndSigners,
483
+ attestationsAndSignersSignature: Signature,
483
484
  options: { forcePendingBlockNumber?: number },
484
485
  ): Promise<bigint> {
485
486
  const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
486
487
 
487
488
  // If we have no attestations, we still need to provide the empty attestations
488
489
  // so that the committee is recalculated correctly
489
- const ignoreSignatures = attestationData.attestations.length === 0;
490
+ const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
490
491
  if (ignoreSignatures) {
491
492
  const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber.toBigInt());
492
493
  if (!committee) {
493
494
  this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber.toBigInt()}`);
494
495
  throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber.toBigInt()}`);
495
496
  }
496
- attestationData.attestations = committee.map(committeeMember =>
497
+ attestationsAndSigners.attestations = committee.map(committeeMember =>
497
498
  CommitteeAttestation.fromAddress(committeeMember),
498
499
  );
499
500
  }
500
501
 
501
- const blobs = await Blob.getBlobsPerBlock(block.body.toBlobFields());
502
- const blobInput = Blob.getPrefixedEthBlobCommitments(blobs);
503
-
504
- const formattedAttestations = attestationData.attestations.map(attest => attest.toViem());
505
- const signers = attestationData.attestations
506
- .filter(attest => !attest.signature.isEmpty())
507
- .map(attest => attest.address.toString());
502
+ const blobFields = block.getCheckpointBlobFields();
503
+ const blobs = getBlobsPerL1Block(blobFields);
504
+ const blobInput = getPrefixedEthBlobCommitments(blobs);
508
505
 
509
506
  const args = [
510
507
  {
511
- header: block.header.toPropose().toViem(),
508
+ header: block.getCheckpointHeader().toViem(),
512
509
  archive: toHex(block.archive.root.toBuffer()),
513
510
  stateReference: block.header.state.toViem(),
514
- txHashes: block.body.txEffects.map(txEffect => txEffect.txHash.toString()),
515
511
  oracleInput: {
516
512
  feeAssetPriceModifier: 0n,
517
513
  },
518
514
  },
519
- RollupContract.packAttestations(formattedAttestations),
520
- signers,
515
+ attestationsAndSigners.getPackedAttestations(),
516
+ attestationsAndSigners.getSigners().map(signer => signer.toString()),
517
+ attestationsAndSignersSignature.toViemSignature(),
521
518
  blobInput,
522
519
  ] as const;
523
520
 
@@ -528,13 +525,14 @@ export class SequencerPublisher {
528
525
  private async enqueueCastSignalHelper(
529
526
  slotNumber: bigint,
530
527
  timestamp: bigint,
531
- signalType: SignalType,
528
+ signalType: GovernanceSignalAction,
532
529
  payload: EthAddress,
533
530
  base: IEmpireBase,
534
531
  signerAddress: EthAddress,
535
532
  signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
536
533
  ): Promise<boolean> {
537
- if (this.myLastSignals[signalType] >= slotNumber) {
534
+ if (this.lastActions[signalType] && this.lastActions[signalType] === slotNumber) {
535
+ this.log.debug(`Skipping duplicate vote cast signal ${signalType} for slot ${slotNumber}`);
538
536
  return false;
539
537
  }
540
538
  if (payload.equals(EthAddress.ZERO)) {
@@ -551,10 +549,9 @@ export class SequencerPublisher {
551
549
  return false;
552
550
  }
553
551
 
554
- const cachedLastVote = this.myLastSignals[signalType];
555
- this.myLastSignals[signalType] = slotNumber;
556
-
557
- const action = signalType === SignalType.GOVERNANCE ? 'governance-signal' : 'empire-slashing-signal';
552
+ const cachedLastVote = this.lastActions[signalType];
553
+ this.lastActions[signalType] = slotNumber;
554
+ const action = signalType;
558
555
 
559
556
  const request = await base.createSignalRequestWithSignature(
560
557
  payload.toString(),
@@ -597,7 +594,7 @@ export class SequencerPublisher {
597
594
  `Signaling in [${action}] for ${payload} at slot ${slotNumber} in round ${round} failed`,
598
595
  logData,
599
596
  );
600
- this.myLastSignals[signalType] = cachedLastVote;
597
+ this.lastActions[signalType] = cachedLastVote;
601
598
  return false;
602
599
  } else {
603
600
  this.log.info(
@@ -627,7 +624,7 @@ export class SequencerPublisher {
627
624
  return this.enqueueCastSignalHelper(
628
625
  slotNumber,
629
626
  timestamp,
630
- SignalType.GOVERNANCE,
627
+ 'governance-signal',
631
628
  governancePayload,
632
629
  this.govProposerContract,
633
630
  signerAddress,
@@ -661,7 +658,7 @@ export class SequencerPublisher {
661
658
  await this.enqueueCastSignalHelper(
662
659
  slotNumber,
663
660
  timestamp,
664
- SignalType.SLASHING,
661
+ 'empire-slashing-signal',
665
662
  action.payload,
666
663
  this.slashingProposerContract,
667
664
  signerAddress,
@@ -766,24 +763,23 @@ export class SequencerPublisher {
766
763
  */
767
764
  public async enqueueProposeL2Block(
768
765
  block: L2Block,
769
- attestations?: CommitteeAttestation[],
770
- txHashes?: TxHash[],
766
+ attestationsAndSigners: CommitteeAttestationsAndSigners,
767
+ attestationsAndSignersSignature: Signature,
771
768
  opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: number } = {},
772
769
  ): Promise<boolean> {
773
- const proposedBlockHeader = block.header.toPropose();
770
+ const checkpointHeader = block.getCheckpointHeader();
774
771
 
775
- const consensusPayload = ConsensusPayload.fromBlock(block);
776
- const digest = getHashedSignaturePayload(consensusPayload, SignatureDomainSeparator.blockAttestation);
772
+ const blobFields = block.getCheckpointBlobFields();
773
+ const blobs = getBlobsPerL1Block(blobFields);
777
774
 
778
- const blobs = await Blob.getBlobsPerBlock(block.body.toBlobFields());
779
775
  const proposeTxArgs = {
780
- header: proposedBlockHeader,
776
+ header: checkpointHeader,
781
777
  archive: block.archive.root.toBuffer(),
782
778
  stateReference: block.header.state,
783
779
  body: block.body.toBuffer(),
784
780
  blobs,
785
- attestations,
786
- txHashes: txHashes ?? [],
781
+ attestationsAndSigners,
782
+ attestationsAndSignersSignature,
787
783
  };
788
784
 
789
785
  let ts: bigint;
@@ -793,9 +789,8 @@ export class SequencerPublisher {
793
789
  // This means that we can avoid the simulation issues in later checks.
794
790
  // By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
795
791
  // make time consistency checks break.
796
- const attestationData = { digest: digest.toBuffer(), attestations: attestations ?? [] };
797
792
  // TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
798
- ts = await this.validateBlockForSubmission(block, attestationData, opts);
793
+ ts = await this.validateBlockForSubmission(block, attestationsAndSigners, attestationsAndSignersSignature, opts);
799
794
  } catch (err: any) {
800
795
  this.log.error(`Block validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
801
796
  ...block.getStats(),
@@ -818,7 +813,8 @@ export class SequencerPublisher {
818
813
  // We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
819
814
  const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(request.gasUsed) * 64) / 63)));
820
815
 
821
- const logData = { ...pick(request, 'gasUsed', 'blockNumber'), gasLimit, opts };
816
+ const { gasUsed, blockNumber } = request;
817
+ const logData = { gasUsed, blockNumber, gasLimit, opts };
822
818
  this.log.verbose(`Enqueuing invalidate block request`, logData);
823
819
  this.addRequest({
824
820
  action: `invalidate-by-${request.reason}`,
@@ -842,16 +838,24 @@ export class SequencerPublisher {
842
838
  }
843
839
 
844
840
  private async simulateAndEnqueueRequest(
845
- action: RequestWithExpiry['action'],
841
+ action: Action,
846
842
  request: L1TxRequest,
847
843
  checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
848
844
  slotNumber: bigint,
849
845
  timestamp: bigint,
850
846
  ) {
851
847
  const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
852
- let gasUsed: bigint;
848
+ if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
849
+ this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
850
+ return false;
851
+ }
852
+
853
+ const cachedLastActionSlot = this.lastActions[action];
854
+ this.lastActions[action] = slotNumber;
853
855
 
854
- this.log.debug(`Simulating ${action}`, logData);
856
+ this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
857
+
858
+ let gasUsed: bigint;
855
859
  try {
856
860
  ({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], ErrorsAbi)); // TODO(palla/slash): Check the timestamp logic
857
861
  this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
@@ -875,6 +879,7 @@ export class SequencerPublisher {
875
879
  const success = result && result.receipt && result.receipt.status === 'success' && checkSuccess(result.receipt);
876
880
  if (!success) {
877
881
  this.log.warn(`Action ${action} at ${slotNumber} failed`, { ...result, ...logData });
882
+ this.lastActions[action] = cachedLastActionSlot;
878
883
  } else {
879
884
  this.log.info(`Action ${action} at ${slotNumber} succeeded`, { ...result, ...logData });
880
885
  }
@@ -907,7 +912,7 @@ export class SequencerPublisher {
907
912
  options: { forcePendingBlockNumber?: number },
908
913
  ) {
909
914
  const kzg = Blob.getViemKzgInstance();
910
- const blobInput = Blob.getPrefixedEthBlobCommitments(encodedData.blobs);
915
+ const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
911
916
  this.log.debug('Validating blob input', { blobInput });
912
917
  const blobEvaluationGas = await this.l1TxUtils
913
918
  .estimateGas(
@@ -932,12 +937,7 @@ export class SequencerPublisher {
932
937
  throw new Error('Failed to validate blobs');
933
938
  });
934
939
 
935
- const attestations = encodedData.attestations ? encodedData.attestations.map(attest => attest.toViem()) : [];
936
- const txHashes = encodedData.txHashes ? encodedData.txHashes.map(txHash => txHash.toString()) : [];
937
-
938
- const signers = encodedData.attestations
939
- ?.filter(attest => !attest.signature.isEmpty())
940
- .map(attest => attest.address.toString());
940
+ const signers = encodedData.attestationsAndSigners.getSigners().map(signer => signer.toString());
941
941
 
942
942
  const args = [
943
943
  {
@@ -948,10 +948,10 @@ export class SequencerPublisher {
948
948
  // We are currently not modifying these. See #9963
949
949
  feeAssetPriceModifier: 0n,
950
950
  },
951
- txHashes,
952
951
  },
953
- RollupContract.packAttestations(attestations),
954
- signers ?? [],
952
+ encodedData.attestationsAndSigners.getPackedAttestations(),
953
+ signers,
954
+ encodedData.attestationsAndSignersSignature.toViemSignature(),
955
955
  blobInput,
956
956
  ] as const;
957
957
 
@@ -972,13 +972,13 @@ export class SequencerPublisher {
972
972
  readonly header: ViemHeader;
973
973
  readonly archive: `0x${string}`;
974
974
  readonly stateReference: ViemStateReference;
975
- readonly txHashes: `0x${string}`[];
976
975
  readonly oracleInput: {
977
976
  readonly feeAssetPriceModifier: 0n;
978
977
  };
979
978
  },
980
979
  ViemCommitteeAttestations,
981
- `0x${string}`[],
980
+ `0x${string}`[], // Signers
981
+ ViemSignature,
982
982
  `0x${string}`,
983
983
  ],
984
984
  timestamp: bigint,
@@ -1084,13 +1084,16 @@ export class SequencerPublisher {
1084
1084
  if (success) {
1085
1085
  const endBlock = receipt.blockNumber;
1086
1086
  const inclusionBlocks = Number(endBlock - startBlock);
1087
+ const { calldataGas, calldataSize, sender } = stats!;
1087
1088
  const publishStats: L1PublishBlockStats = {
1088
1089
  gasPrice: receipt.effectiveGasPrice,
1089
1090
  gasUsed: receipt.gasUsed,
1090
1091
  blobGasUsed: receipt.blobGasUsed ?? 0n,
1091
1092
  blobDataGas: receipt.blobGasPrice ?? 0n,
1092
1093
  transactionHash: receipt.transactionHash,
1093
- ...pick(stats!, 'calldataGas', 'calldataSize', 'sender'),
1094
+ calldataGas,
1095
+ calldataSize,
1096
+ sender,
1094
1097
  ...block.getStats(),
1095
1098
  eventName: 'rollup-published-to-l1',
1096
1099
  blobCount: encodedData.blobs.length,
@@ -1,10 +1,10 @@
1
- import { MerkleTreeId, elapsed } from '@aztec/aztec.js';
2
- import { pick } from '@aztec/foundation/collection';
1
+ import { MerkleTreeId } from '@aztec/aztec.js/trees';
2
+ import { merge, pick } from '@aztec/foundation/collection';
3
3
  import type { Fr } from '@aztec/foundation/fields';
4
4
  import { createLogger } from '@aztec/foundation/log';
5
5
  import { retryUntil } from '@aztec/foundation/retry';
6
6
  import { bufferToHex } from '@aztec/foundation/string';
7
- import { DateProvider, Timer } from '@aztec/foundation/timer';
7
+ import { DateProvider, Timer, elapsed } from '@aztec/foundation/timer';
8
8
  import { getDefaultAllowedSetupFunctions } from '@aztec/p2p/msg_validators';
9
9
  import { LightweightBlockFactory } from '@aztec/prover-client/block-factory';
10
10
  import {
@@ -13,12 +13,12 @@ import {
13
13
  PublicProcessor,
14
14
  TelemetryPublicTxSimulator,
15
15
  } from '@aztec/simulator/server';
16
- import type { ChainConfig, SequencerConfig } from '@aztec/stdlib/config';
17
16
  import type { ContractDataSource } from '@aztec/stdlib/contract';
18
17
  import { type L1RollupConstants, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
19
18
  import { Gas } from '@aztec/stdlib/gas';
20
19
  import type {
21
20
  BuildBlockResult,
21
+ FullNodeBlockBuilderConfig,
22
22
  IFullNodeBlockBuilder,
23
23
  MerkleTreeWriteOperations,
24
24
  PublicProcessorLimits,
@@ -89,9 +89,14 @@ export async function buildBlock(
89
89
  return res;
90
90
  }
91
91
 
92
- type FullNodeBlockBuilderConfig = Pick<L1RollupConstants, 'l1GenesisTime' | 'slotDuration'> &
93
- Pick<ChainConfig, 'l1ChainId' | 'rollupVersion'> &
94
- Pick<SequencerConfig, 'txPublicSetupAllowList' | 'fakeProcessingDelayPerTxMs'>;
92
+ const FullNodeBlockBuilderConfigKeys = [
93
+ 'l1GenesisTime',
94
+ 'slotDuration',
95
+ 'l1ChainId',
96
+ 'rollupVersion',
97
+ 'txPublicSetupAllowList',
98
+ 'fakeProcessingDelayPerTxMs',
99
+ ] as const;
95
100
 
96
101
  export class FullNodeBlockBuilder implements IFullNodeBlockBuilder {
97
102
  constructor(
@@ -103,19 +108,11 @@ export class FullNodeBlockBuilder implements IFullNodeBlockBuilder {
103
108
  ) {}
104
109
 
105
110
  public getConfig(): FullNodeBlockBuilderConfig {
106
- return pick(
107
- this.config,
108
- 'l1GenesisTime',
109
- 'slotDuration',
110
- 'l1ChainId',
111
- 'rollupVersion',
112
- 'txPublicSetupAllowList',
113
- 'fakeProcessingDelayPerTxMs',
114
- );
111
+ return pick(this.config, ...FullNodeBlockBuilderConfigKeys);
115
112
  }
116
113
 
117
- public updateConfig(config: FullNodeBlockBuilderConfig) {
118
- this.config = config;
114
+ public updateConfig(config: Partial<FullNodeBlockBuilderConfig>) {
115
+ this.config = merge(this.config, pick(config, ...FullNodeBlockBuilderConfigKeys));
119
116
  }
120
117
 
121
118
  public async makeBlockBuilderDeps(globalVariables: GlobalVariables, fork: MerkleTreeWriteOperations) {
@@ -127,10 +124,12 @@ export class FullNodeBlockBuilder implements IFullNodeBlockBuilder {
127
124
  guardedFork,
128
125
  contractsDB,
129
126
  globalVariables,
130
- /*doMerkleOperations=*/ true,
131
- /*skipFeeEnforcement=*/ true,
132
- /*clientInitiatedSimulation=*/ false,
133
127
  this.telemetryClient,
128
+ {
129
+ doMerkleOperations: true,
130
+ skipFeeEnforcement: true,
131
+ clientInitiatedSimulation: false,
132
+ },
134
133
  );
135
134
 
136
135
  const processor = new PublicProcessor(
@@ -0,0 +1,21 @@
1
+ import type { SequencerState } from './utils.js';
2
+
3
+ export class SequencerTooSlowError extends Error {
4
+ constructor(
5
+ public readonly proposedState: SequencerState,
6
+ public readonly maxAllowedTime: number,
7
+ public readonly currentTime: number,
8
+ ) {
9
+ super(
10
+ `Too far into slot for ${proposedState} (time into slot ${currentTime}s greater than ${maxAllowedTime}s allowance)`,
11
+ );
12
+ this.name = 'SequencerTooSlowError';
13
+ }
14
+ }
15
+
16
+ export class SequencerInterruptedError extends Error {
17
+ constructor() {
18
+ super(`Sequencer was interrupted`);
19
+ this.name = 'SequencerInterruptedError';
20
+ }
21
+ }