@aztec/sequencer-client 0.0.1-commit.1142ef1 → 0.0.1-commit.1bea0213

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 (39) hide show
  1. package/dest/config.d.ts +1 -1
  2. package/dest/config.d.ts.map +1 -1
  3. package/dest/config.js +1 -3
  4. package/dest/global_variable_builder/global_builder.js +2 -2
  5. package/dest/index.d.ts +2 -2
  6. package/dest/index.d.ts.map +1 -1
  7. package/dest/index.js +1 -1
  8. package/dest/sequencer/checkpoint_proposal_job.d.ts +6 -4
  9. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  10. package/dest/sequencer/checkpoint_proposal_job.js +90 -14
  11. package/dest/sequencer/checkpoint_voter.d.ts +3 -2
  12. package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
  13. package/dest/sequencer/checkpoint_voter.js +34 -10
  14. package/dest/sequencer/index.d.ts +1 -2
  15. package/dest/sequencer/index.d.ts.map +1 -1
  16. package/dest/sequencer/index.js +0 -1
  17. package/dest/sequencer/sequencer.d.ts +17 -9
  18. package/dest/sequencer/sequencer.d.ts.map +1 -1
  19. package/dest/sequencer/sequencer.js +67 -11
  20. package/dest/test/mock_checkpoint_builder.d.ts +17 -13
  21. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  22. package/dest/test/mock_checkpoint_builder.js +28 -8
  23. package/dest/test/utils.d.ts +8 -8
  24. package/dest/test/utils.d.ts.map +1 -1
  25. package/dest/test/utils.js +7 -7
  26. package/package.json +30 -28
  27. package/src/config.ts +1 -3
  28. package/src/global_variable_builder/global_builder.ts +2 -2
  29. package/src/index.ts +1 -6
  30. package/src/sequencer/checkpoint_proposal_job.ts +127 -24
  31. package/src/sequencer/checkpoint_voter.ts +32 -7
  32. package/src/sequencer/index.ts +0 -1
  33. package/src/sequencer/sequencer.ts +81 -9
  34. package/src/test/mock_checkpoint_builder.ts +65 -33
  35. package/src/test/utils.ts +19 -12
  36. package/dest/sequencer/block_builder.d.ts +0 -26
  37. package/dest/sequencer/block_builder.d.ts.map +0 -1
  38. package/dest/sequencer/block_builder.js +0 -129
  39. package/src/sequencer/block_builder.ts +0 -216
@@ -1,10 +1,10 @@
1
1
  import { Body } from '@aztec/aztec.js/block';
2
- import { CheckpointNumber } from '@aztec/foundation/branded-types';
2
+ import { CheckpointNumber, IndexWithinCheckpoint } from '@aztec/foundation/branded-types';
3
3
  import { times } from '@aztec/foundation/collection';
4
4
  import { Fr } from '@aztec/foundation/curves/bn254';
5
5
  import { Signature } from '@aztec/foundation/eth-signature';
6
6
  import { PublicDataWrite } from '@aztec/stdlib/avm';
7
- import { CommitteeAttestation, L2BlockNew } from '@aztec/stdlib/block';
7
+ import { CommitteeAttestation, L2Block } from '@aztec/stdlib/block';
8
8
  import { BlockProposal, CheckpointAttestation, CheckpointProposal, ConsensusPayload } from '@aztec/stdlib/p2p';
9
9
  import { CheckpointHeader } from '@aztec/stdlib/rollup';
10
10
  import { makeAppendOnlyTreeSnapshot, mockTxForRollup } from '@aztec/stdlib/testing';
@@ -21,7 +21,7 @@ export { MockCheckpointBuilder, MockCheckpointsBuilder } from './mock_checkpoint
21
21
  return tx;
22
22
  }
23
23
  /**
24
- * Creates an L2BlockNew from transactions and global variables
24
+ * Creates an L2Block from transactions and global variables
25
25
  */ export async function makeBlock(txs, globalVariables) {
26
26
  const processedTxs = await Promise.all(txs.map((tx)=>makeProcessedTxFromPrivateOnlyTx(tx, Fr.ZERO, new PublicDataWrite(Fr.random(), Fr.random()), globalVariables)));
27
27
  const body = new Body(processedTxs.map((tx)=>tx.txEffect));
@@ -29,7 +29,7 @@ export { MockCheckpointBuilder, MockCheckpointsBuilder } from './mock_checkpoint
29
29
  globalVariables
30
30
  });
31
31
  const archive = makeAppendOnlyTreeSnapshot(globalVariables.blockNumber + 1);
32
- return new L2BlockNew(archive, header, body, CheckpointNumber(globalVariables.blockNumber), 0);
32
+ return new L2Block(archive, header, body, CheckpointNumber.fromBlockNumber(globalVariables.blockNumber), IndexWithinCheckpoint(0));
33
33
  }
34
34
  /**
35
35
  * Mocks the P2P client to return specific pending transactions
@@ -53,11 +53,11 @@ export { MockCheckpointBuilder, MockCheckpointsBuilder } from './mock_checkpoint
53
53
  ];
54
54
  }
55
55
  /**
56
- * Creates a CheckpointHeader from an L2BlockNew for testing purposes.
57
- * Uses mock values for blockHeadersHash, blobsHash and inHash since L2BlockNew doesn't have these fields.
56
+ * Creates a CheckpointHeader from an L2Block for testing purposes.
57
+ * Uses mock values for blockHeadersHash, blobsHash and inHash since L2Block doesn't have these fields.
58
58
  */ function createCheckpointHeaderFromBlock(block) {
59
59
  const gv = block.header.globalVariables;
60
- return new CheckpointHeader(block.header.lastArchive.root, Fr.random(), Fr.random(), Fr.random(), gv.slotNumber, gv.timestamp, gv.coinbase, gv.feeRecipient, gv.gasFees, block.header.totalManaUsed);
60
+ return new CheckpointHeader(block.header.lastArchive.root, Fr.random(), Fr.random(), Fr.random(), Fr.random(), gv.slotNumber, gv.timestamp, gv.coinbase, gv.feeRecipient, gv.gasFees, block.header.totalManaUsed);
61
61
  }
62
62
  /**
63
63
  * Creates a block proposal from a block and signature
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/sequencer-client",
3
- "version": "0.0.1-commit.1142ef1",
3
+ "version": "0.0.1-commit.1bea0213",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dest/index.js",
@@ -26,43 +26,45 @@
26
26
  "test:integration:run": "NODE_NO_WARNINGS=1 node --experimental-vm-modules $(yarn bin jest) --no-cache --config jest.integration.config.json"
27
27
  },
28
28
  "dependencies": {
29
- "@aztec/aztec.js": "0.0.1-commit.1142ef1",
30
- "@aztec/bb-prover": "0.0.1-commit.1142ef1",
31
- "@aztec/blob-client": "0.0.1-commit.1142ef1",
32
- "@aztec/blob-lib": "0.0.1-commit.1142ef1",
33
- "@aztec/constants": "0.0.1-commit.1142ef1",
34
- "@aztec/epoch-cache": "0.0.1-commit.1142ef1",
35
- "@aztec/ethereum": "0.0.1-commit.1142ef1",
36
- "@aztec/foundation": "0.0.1-commit.1142ef1",
37
- "@aztec/l1-artifacts": "0.0.1-commit.1142ef1",
38
- "@aztec/merkle-tree": "0.0.1-commit.1142ef1",
39
- "@aztec/node-keystore": "0.0.1-commit.1142ef1",
40
- "@aztec/noir-acvm_js": "0.0.1-commit.1142ef1",
41
- "@aztec/noir-contracts.js": "0.0.1-commit.1142ef1",
42
- "@aztec/noir-protocol-circuits-types": "0.0.1-commit.1142ef1",
43
- "@aztec/noir-types": "0.0.1-commit.1142ef1",
44
- "@aztec/p2p": "0.0.1-commit.1142ef1",
45
- "@aztec/protocol-contracts": "0.0.1-commit.1142ef1",
46
- "@aztec/prover-client": "0.0.1-commit.1142ef1",
47
- "@aztec/simulator": "0.0.1-commit.1142ef1",
48
- "@aztec/slasher": "0.0.1-commit.1142ef1",
49
- "@aztec/stdlib": "0.0.1-commit.1142ef1",
50
- "@aztec/telemetry-client": "0.0.1-commit.1142ef1",
51
- "@aztec/validator-client": "0.0.1-commit.1142ef1",
52
- "@aztec/world-state": "0.0.1-commit.1142ef1",
29
+ "@aztec/aztec.js": "0.0.1-commit.1bea0213",
30
+ "@aztec/bb-prover": "0.0.1-commit.1bea0213",
31
+ "@aztec/blob-client": "0.0.1-commit.1bea0213",
32
+ "@aztec/blob-lib": "0.0.1-commit.1bea0213",
33
+ "@aztec/constants": "0.0.1-commit.1bea0213",
34
+ "@aztec/epoch-cache": "0.0.1-commit.1bea0213",
35
+ "@aztec/ethereum": "0.0.1-commit.1bea0213",
36
+ "@aztec/foundation": "0.0.1-commit.1bea0213",
37
+ "@aztec/l1-artifacts": "0.0.1-commit.1bea0213",
38
+ "@aztec/merkle-tree": "0.0.1-commit.1bea0213",
39
+ "@aztec/node-keystore": "0.0.1-commit.1bea0213",
40
+ "@aztec/noir-acvm_js": "0.0.1-commit.1bea0213",
41
+ "@aztec/noir-contracts.js": "0.0.1-commit.1bea0213",
42
+ "@aztec/noir-protocol-circuits-types": "0.0.1-commit.1bea0213",
43
+ "@aztec/noir-types": "0.0.1-commit.1bea0213",
44
+ "@aztec/p2p": "0.0.1-commit.1bea0213",
45
+ "@aztec/protocol-contracts": "0.0.1-commit.1bea0213",
46
+ "@aztec/prover-client": "0.0.1-commit.1bea0213",
47
+ "@aztec/simulator": "0.0.1-commit.1bea0213",
48
+ "@aztec/slasher": "0.0.1-commit.1bea0213",
49
+ "@aztec/stdlib": "0.0.1-commit.1bea0213",
50
+ "@aztec/telemetry-client": "0.0.1-commit.1bea0213",
51
+ "@aztec/validator-client": "0.0.1-commit.1bea0213",
52
+ "@aztec/validator-ha-signer": "0.0.1-commit.1bea0213",
53
+ "@aztec/world-state": "0.0.1-commit.1bea0213",
53
54
  "lodash.chunk": "^4.2.0",
54
55
  "tslib": "^2.4.0",
55
56
  "viem": "npm:@aztec/viem@2.38.2"
56
57
  },
57
58
  "devDependencies": {
58
- "@aztec/archiver": "0.0.1-commit.1142ef1",
59
- "@aztec/kv-store": "0.0.1-commit.1142ef1",
59
+ "@aztec/archiver": "0.0.1-commit.1bea0213",
60
+ "@aztec/kv-store": "0.0.1-commit.1bea0213",
61
+ "@electric-sql/pglite": "^0.3.14",
60
62
  "@jest/globals": "^30.0.0",
61
63
  "@types/jest": "^30.0.0",
62
64
  "@types/lodash.chunk": "^4.2.7",
63
65
  "@types/lodash.pick": "^4.4.7",
64
66
  "@types/node": "^22.15.17",
65
- "@typescript/native-preview": "7.0.0-dev.20251126.1",
67
+ "@typescript/native-preview": "7.0.0-dev.20260113.1",
66
68
  "concurrently": "^7.6.0",
67
69
  "eslint": "^9.26.0",
68
70
  "express": "^4.21.2",
package/src/config.ts CHANGED
@@ -50,8 +50,7 @@ export const DefaultSequencerConfig: ResolvedSequencerConfig = {
50
50
  injectFakeAttestation: false,
51
51
  fishermanMode: false,
52
52
  shuffleAttestationOrdering: false,
53
- // TODO(palla/mbps): Change default to false once block sync is stable
54
- skipPushProposedBlocksToArchiver: true,
53
+ skipPushProposedBlocksToArchiver: false,
55
54
  };
56
55
 
57
56
  /**
@@ -204,7 +203,6 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
204
203
  description: 'Have sequencer build and publish an empty checkpoint if there are no txs',
205
204
  ...booleanConfigHelper(DefaultSequencerConfig.buildCheckpointIfEmpty),
206
205
  },
207
- // TODO(palla/mbps): Change default to false once block sync is stable
208
206
  skipPushProposedBlocksToArchiver: {
209
207
  description: 'Skip pushing proposed blocks to archiver (default: true)',
210
208
  ...booleanConfigHelper(DefaultSequencerConfig.skipPushProposedBlocksToArchiver),
@@ -69,9 +69,9 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
69
69
  // we need to fetch the last block written, and estimate the earliest timestamp for the next block.
70
70
  // The timestamp of that last block will act as a lower bound for the next block.
71
71
 
72
- const lastBlock = await this.rollupContract.getPendingCheckpoint();
72
+ const lastCheckpoint = await this.rollupContract.getPendingCheckpoint();
73
73
  const earliestTimestamp = await this.rollupContract.getTimestampForSlot(
74
- SlotNumber.fromBigInt(BigInt(lastBlock.slotNumber) + 1n),
74
+ SlotNumber.fromBigInt(BigInt(lastCheckpoint.slotNumber) + 1n),
75
75
  );
76
76
  const nextEthTimestamp = BigInt((await this.publicClient.getBlock()).timestamp + BigInt(this.ethereumSlotDuration));
77
77
  const timestamp = earliestTimestamp > nextEthTimestamp ? earliestTimestamp : nextEthTimestamp;
package/src/index.ts CHANGED
@@ -1,12 +1,7 @@
1
1
  export * from './client/index.js';
2
2
  export * from './config.js';
3
3
  export * from './publisher/index.js';
4
- export {
5
- FullNodeBlockBuilder as BlockBuilder,
6
- Sequencer,
7
- SequencerState,
8
- type SequencerEvents,
9
- } from './sequencer/index.js';
4
+ export { Sequencer, SequencerState, type SequencerEvents } from './sequencer/index.js';
10
5
 
11
6
  // Used by the node to simulate public parts of transactions. Should these be moved to a shared library?
12
7
  // ISSUE(#9832)
@@ -1,3 +1,4 @@
1
+ import { NUM_CHECKPOINT_END_MARKER_FIELDS, getNumBlockEndBlobFields } from '@aztec/blob-lib/encoding';
1
2
  import { BLOBS_PER_CHECKPOINT, FIELDS_PER_BLOB } from '@aztec/constants';
2
3
  import type { EpochCache } from '@aztec/epoch-cache';
3
4
  import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
@@ -15,8 +16,9 @@ import type { SlasherClientInterface } from '@aztec/slasher';
15
16
  import {
16
17
  CommitteeAttestation,
17
18
  CommitteeAttestationsAndSigners,
18
- L2BlockNew,
19
+ L2Block,
19
20
  type L2BlockSink,
21
+ type L2BlockSource,
20
22
  MaliciousCommitteeAttestationsAndSigners,
21
23
  } from '@aztec/stdlib/block';
22
24
  import type { Checkpoint } from '@aztec/stdlib/checkpoint';
@@ -35,6 +37,7 @@ import { type FailedTx, Tx } from '@aztec/stdlib/tx';
35
37
  import { AttestationTimeoutError } from '@aztec/stdlib/validators';
36
38
  import { Attributes, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
37
39
  import { CheckpointBuilder, type FullNodeCheckpointsBuilder, type ValidatorClient } from '@aztec/validator-client';
40
+ import { DutyAlreadySignedError, SlashingProtectionError } from '@aztec/validator-ha-signer/errors';
38
41
 
39
42
  import type { GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
40
43
  import type { InvalidateCheckpointRequest, SequencerPublisher } from '../publisher/sequencer-publisher.js';
@@ -57,6 +60,7 @@ const TXS_POLLING_MS = 500;
57
60
  */
58
61
  export class CheckpointProposalJob implements Traceable {
59
62
  constructor(
63
+ private readonly epoch: EpochNumber,
60
64
  private readonly slot: SlotNumber,
61
65
  private readonly checkpointNumber: CheckpointNumber,
62
66
  private readonly syncedToBlockNumber: BlockNumber,
@@ -70,6 +74,7 @@ export class CheckpointProposalJob implements Traceable {
70
74
  private readonly p2pClient: P2P,
71
75
  private readonly worldState: WorldStateSynchronizer,
72
76
  private readonly l1ToL2MessageSource: L1ToL2MessageSource,
77
+ private readonly l2BlockSource: L2BlockSource,
73
78
  private readonly checkpointsBuilder: FullNodeCheckpointsBuilder,
74
79
  private readonly blockSink: L2BlockSink,
75
80
  private readonly l1Constants: SequencerRollupConstants,
@@ -169,6 +174,12 @@ export class CheckpointProposalJob implements Traceable {
169
174
  const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(this.checkpointNumber);
170
175
  const inHash = computeInHashFromL1ToL2Messages(l1ToL2Messages);
171
176
 
177
+ // Collect the out hashes of all the checkpoints before this one in the same epoch
178
+ const previousCheckpoints = (await this.l2BlockSource.getCheckpointsForEpoch(this.epoch)).filter(
179
+ c => c.number < this.checkpointNumber,
180
+ );
181
+ const previousCheckpointOutHashes = previousCheckpoints.map(c => c.getCheckpointOutHash());
182
+
172
183
  // Create a long-lived forked world state for the checkpoint builder
173
184
  using fork = await this.worldState.fork(this.syncedToBlockNumber, { closeDelayMs: 12_000 });
174
185
 
@@ -177,6 +188,7 @@ export class CheckpointProposalJob implements Traceable {
177
188
  this.checkpointNumber,
178
189
  checkpointGlobalVariables,
179
190
  l1ToL2Messages,
191
+ previousCheckpointOutHashes,
180
192
  fork,
181
193
  );
182
194
 
@@ -191,13 +203,40 @@ export class CheckpointProposalJob implements Traceable {
191
203
  broadcastInvalidCheckpointProposal: this.config.broadcastInvalidBlockProposal,
192
204
  };
193
205
 
194
- // Main loop: build blocks for the checkpoint
195
- const { blocksInCheckpoint, blockPendingBroadcast } = await this.buildBlocksForCheckpoint(
196
- checkpointBuilder,
197
- checkpointGlobalVariables.timestamp,
198
- inHash,
199
- blockProposalOptions,
200
- );
206
+ let blocksInCheckpoint: L2Block[] = [];
207
+ let blockPendingBroadcast: { block: L2Block; txs: Tx[] } | undefined = undefined;
208
+
209
+ try {
210
+ // Main loop: build blocks for the checkpoint
211
+ const result = await this.buildBlocksForCheckpoint(
212
+ checkpointBuilder,
213
+ checkpointGlobalVariables.timestamp,
214
+ inHash,
215
+ blockProposalOptions,
216
+ );
217
+ blocksInCheckpoint = result.blocksInCheckpoint;
218
+ blockPendingBroadcast = result.blockPendingBroadcast;
219
+ } catch (err) {
220
+ // These errors are expected in HA mode, so we yield and let another HA node handle the slot
221
+ // The only distinction between the 2 errors is SlashingProtectionError throws when the payload is different,
222
+ // which is normal for block building (may have picked different txs)
223
+ if (err instanceof DutyAlreadySignedError) {
224
+ this.log.info(`Checkpoint proposal for slot ${this.slot} already signed by another HA node, yielding`, {
225
+ slot: this.slot,
226
+ signedByNode: err.signedByNode,
227
+ });
228
+ return undefined;
229
+ }
230
+ if (err instanceof SlashingProtectionError) {
231
+ this.log.info(`Checkpoint proposal for slot ${this.slot} blocked by slashing protection, yielding`, {
232
+ slot: this.slot,
233
+ existingMessageHash: err.existingMessageHash,
234
+ attemptedMessageHash: err.attemptedMessageHash,
235
+ });
236
+ return undefined;
237
+ }
238
+ throw err;
239
+ }
201
240
 
202
241
  if (blocksInCheckpoint.length === 0) {
203
242
  this.log.warn(`No blocks were built for slot ${this.slot}`, { slot: this.slot });
@@ -252,7 +291,34 @@ export class CheckpointProposalJob implements Traceable {
252
291
 
253
292
  // Proposer must sign over the attestations before pushing them to L1
254
293
  const signer = this.proposer ?? this.publisher.getSenderAddress();
255
- const attestationsSignature = await this.validatorClient.signAttestationsAndSigners(attestations, signer);
294
+ let attestationsSignature: Signature;
295
+ try {
296
+ attestationsSignature = await this.validatorClient.signAttestationsAndSigners(
297
+ attestations,
298
+ signer,
299
+ this.slot,
300
+ this.checkpointNumber,
301
+ );
302
+ } catch (err) {
303
+ // We shouldn't really get here since we yield to another HA node
304
+ // as soon as we see these errors when creating block proposals.
305
+ if (err instanceof DutyAlreadySignedError) {
306
+ this.log.info(`Attestations signature for slot ${this.slot} already signed by another HA node, yielding`, {
307
+ slot: this.slot,
308
+ signedByNode: err.signedByNode,
309
+ });
310
+ return undefined;
311
+ }
312
+ if (err instanceof SlashingProtectionError) {
313
+ this.log.info(`Attestations signature for slot ${this.slot} blocked by slashing protection, yielding`, {
314
+ slot: this.slot,
315
+ existingMessageHash: err.existingMessageHash,
316
+ attemptedMessageHash: err.attemptedMessageHash,
317
+ });
318
+ return undefined;
319
+ }
320
+ throw err;
321
+ }
256
322
 
257
323
  // Enqueue publishing the checkpoint to L1
258
324
  this.setStateFn(SequencerState.PUBLISHING_CHECKPOINT, this.slot);
@@ -266,6 +332,11 @@ export class CheckpointProposalJob implements Traceable {
266
332
 
267
333
  return checkpoint;
268
334
  } catch (err) {
335
+ if (err && (err instanceof DutyAlreadySignedError || err instanceof SlashingProtectionError)) {
336
+ // swallow this error. It's already been logged by a function deeper in the stack
337
+ return undefined;
338
+ }
339
+
269
340
  this.log.error(`Error building checkpoint at slot ${this.slot}`, err);
270
341
  return undefined;
271
342
  }
@@ -281,15 +352,18 @@ export class CheckpointProposalJob implements Traceable {
281
352
  inHash: Fr,
282
353
  blockProposalOptions: BlockProposalOptions,
283
354
  ): Promise<{
284
- blocksInCheckpoint: L2BlockNew[];
285
- blockPendingBroadcast: { block: L2BlockNew; txs: Tx[] } | undefined;
355
+ blocksInCheckpoint: L2Block[];
356
+ blockPendingBroadcast: { block: L2Block; txs: Tx[] } | undefined;
286
357
  }> {
287
- const blocksInCheckpoint: L2BlockNew[] = [];
358
+ const blocksInCheckpoint: L2Block[] = [];
288
359
  const txHashesAlreadyIncluded = new Set<string>();
289
360
  const initialBlockNumber = BlockNumber(this.syncedToBlockNumber + 1);
290
361
 
362
+ // Remaining blob fields available for blocks (checkpoint end marker already subtracted)
363
+ let remainingBlobFields = BLOBS_PER_CHECKPOINT * FIELDS_PER_BLOB - NUM_CHECKPOINT_END_MARKER_FIELDS;
364
+
291
365
  // Last block in the checkpoint will usually be flagged as pending broadcast, so we send it along with the checkpoint proposal
292
- let blockPendingBroadcast: { block: L2BlockNew; txs: Tx[] } | undefined = undefined;
366
+ let blockPendingBroadcast: { block: L2Block; txs: Tx[] } | undefined = undefined;
293
367
 
294
368
  while (true) {
295
369
  const blocksBuilt = blocksInCheckpoint.length;
@@ -320,6 +394,7 @@ export class CheckpointProposalJob implements Traceable {
320
394
  blockNumber,
321
395
  indexWithinCheckpoint,
322
396
  txHashesAlreadyIncluded,
397
+ remainingBlobFields,
323
398
  });
324
399
 
325
400
  if (!buildResult && timingInfo.isLastBlock) {
@@ -344,13 +419,21 @@ export class CheckpointProposalJob implements Traceable {
344
419
  break;
345
420
  }
346
421
 
347
- const { block, usedTxs } = buildResult;
422
+ const { block, usedTxs, remainingBlobFields: newRemainingBlobFields } = buildResult;
348
423
  blocksInCheckpoint.push(block);
349
424
 
425
+ // Update remaining blob fields for the next block
426
+ remainingBlobFields = newRemainingBlobFields;
427
+
350
428
  // Sync the proposed block to the archiver to make it available
351
429
  // Note that the checkpoint builder uses its own fork so it should not need to wait for this syncing
352
430
  // Eventually we should refactor the checkpoint builder to not need a separate long-lived fork
353
- await this.syncProposedBlockToArchiver(block);
431
+ // Fire and forget - don't block the critical path, but log errors
432
+ this.syncProposedBlockToArchiver(block).catch(err => {
433
+ this.log.error(`Failed to sync proposed block ${block.number} to archiver`, { blockNumber: block.number, err });
434
+ });
435
+
436
+ usedTxs.forEach(tx => txHashesAlreadyIncluded.add(tx.txHash.toString()));
354
437
 
355
438
  // If this is the last block, exit the loop now so we start collecting attestations
356
439
  if (timingInfo.isLastBlock) {
@@ -409,10 +492,18 @@ export class CheckpointProposalJob implements Traceable {
409
492
  indexWithinCheckpoint: number;
410
493
  buildDeadline: Date | undefined;
411
494
  txHashesAlreadyIncluded: Set<string>;
495
+ remainingBlobFields: number;
412
496
  },
413
- ): Promise<{ block: L2BlockNew; usedTxs: Tx[] } | { error: Error } | undefined> {
414
- const { blockTimestamp, forceCreate, blockNumber, indexWithinCheckpoint, buildDeadline, txHashesAlreadyIncluded } =
415
- opts;
497
+ ): Promise<{ block: L2Block; usedTxs: Tx[]; remainingBlobFields: number } | { error: Error } | undefined> {
498
+ const {
499
+ blockTimestamp,
500
+ forceCreate,
501
+ blockNumber,
502
+ indexWithinCheckpoint,
503
+ buildDeadline,
504
+ txHashesAlreadyIncluded,
505
+ remainingBlobFields,
506
+ } = opts;
416
507
 
417
508
  this.log.verbose(
418
509
  `Preparing block ${blockNumber} index ${indexWithinCheckpoint} at checkpoint ${this.checkpointNumber} for slot ${this.slot}`,
@@ -445,18 +536,31 @@ export class CheckpointProposalJob implements Traceable {
445
536
  { slot: this.slot, blockNumber, indexWithinCheckpoint },
446
537
  );
447
538
  this.setStateFn(SequencerState.CREATING_BLOCK, this.slot);
539
+
540
+ // Calculate blob fields limit for txs (remaining capacity - this block's end overhead)
541
+ const blockEndOverhead = getNumBlockEndBlobFields(indexWithinCheckpoint === 0);
542
+ const maxBlobFieldsForTxs = remainingBlobFields - blockEndOverhead;
543
+
448
544
  const blockBuilderOptions: PublicProcessorLimits = {
449
545
  maxTransactions: this.config.maxTxsPerBlock,
450
546
  maxBlockSize: this.config.maxBlockSizeInBytes,
451
547
  maxBlockGas: new Gas(this.config.maxDABlockGas, this.config.maxL2BlockGas),
452
- maxBlobFields: BLOBS_PER_CHECKPOINT * FIELDS_PER_BLOB,
548
+ maxBlobFields: maxBlobFieldsForTxs,
453
549
  deadline: buildDeadline,
454
550
  };
455
551
 
456
552
  // Actually build the block by executing txs
457
553
  const workTimer = new Timer();
458
- const { publicGas, block, publicProcessorDuration, numTxs, blockBuildingTimer, usedTxs, failedTxs } =
459
- await checkpointBuilder.buildBlock(pendingTxs, blockNumber, blockTimestamp, blockBuilderOptions);
554
+ const {
555
+ publicGas,
556
+ block,
557
+ publicProcessorDuration,
558
+ numTxs,
559
+ blockBuildingTimer,
560
+ usedTxs,
561
+ failedTxs,
562
+ usedTxBlobFields,
563
+ } = await checkpointBuilder.buildBlock(pendingTxs, blockNumber, blockTimestamp, blockBuilderOptions);
460
564
  const blockBuildDuration = workTimer.ms();
461
565
 
462
566
  // If any txs failed during execution, drop them from the mempool so we don't pick them up again
@@ -500,7 +604,7 @@ export class CheckpointProposalJob implements Traceable {
500
604
  this.eventEmitter.emit('block-proposed', { blockNumber: block.number, slot: this.slot });
501
605
  this.metrics.recordBuiltBlock(blockBuildDuration, publicGas.l2Gas);
502
606
 
503
- return { block, usedTxs };
607
+ return { block, usedTxs, remainingBlobFields: maxBlobFieldsForTxs - usedTxBlobFields };
504
608
  } catch (err: any) {
505
609
  this.eventEmitter.emit('block-build-failed', { reason: err.message, slot: this.slot });
506
610
  this.log.error(`Error building block`, err, { blockNumber, slot: this.slot });
@@ -678,8 +782,7 @@ export class CheckpointProposalJob implements Traceable {
678
782
  * Gossip doesn't echo messages back to the sender, so the proposer's archiver/world-state
679
783
  * would never receive its own block without this explicit sync.
680
784
  */
681
- private async syncProposedBlockToArchiver(block: L2BlockNew): Promise<void> {
682
- // TODO(palla/mbps): Change default to false once block sync is stable.
785
+ private async syncProposedBlockToArchiver(block: L2Block): Promise<void> {
683
786
  if (this.config.skipPushProposedBlocksToArchiver !== false) {
684
787
  this.log.warn(`Skipping push of proposed block ${block.number} to archiver`, {
685
788
  blockNumber: block.number,
@@ -5,6 +5,8 @@ import type { SlasherClientInterface } from '@aztec/slasher';
5
5
  import { getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
6
6
  import type { ResolvedSequencerConfig } from '@aztec/stdlib/interfaces/server';
7
7
  import type { ValidatorClient } from '@aztec/validator-client';
8
+ import { DutyAlreadySignedError } from '@aztec/validator-ha-signer/errors';
9
+ import { DutyType, type SigningContext } from '@aztec/validator-ha-signer/types';
8
10
 
9
11
  import type { TypedDataDefinition } from 'viem';
10
12
 
@@ -17,7 +19,8 @@ import type { SequencerRollupConstants } from './types.js';
17
19
  */
18
20
  export class CheckpointVoter {
19
21
  private slotTimestamp: bigint;
20
- private signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>;
22
+ private governanceSigner: (msg: TypedDataDefinition) => Promise<`0x${string}`>;
23
+ private slashingSigner: (msg: TypedDataDefinition) => Promise<`0x${string}`>;
21
24
 
22
25
  constructor(
23
26
  private readonly slot: SlotNumber,
@@ -31,8 +34,16 @@ export class CheckpointVoter {
31
34
  private readonly log: Logger,
32
35
  ) {
33
36
  this.slotTimestamp = getTimestampForSlot(this.slot, this.l1Constants);
34
- this.signer = (msg: TypedDataDefinition) =>
35
- this.validatorClient.signWithAddress(this.attestorAddress, msg).then(s => s.toString());
37
+
38
+ // Create separate signers with appropriate duty contexts for governance and slashing votes
39
+ // These use HA protection to ensure only one node signs per slot/duty
40
+ const governanceContext: SigningContext = { slot: this.slot, dutyType: DutyType.GOVERNANCE_VOTE };
41
+ this.governanceSigner = (msg: TypedDataDefinition) =>
42
+ this.validatorClient.signWithAddress(this.attestorAddress, msg, governanceContext).then(s => s.toString());
43
+
44
+ const slashingContext: SigningContext = { slot: this.slot, dutyType: DutyType.SLASHING_VOTE };
45
+ this.slashingSigner = (msg: TypedDataDefinition) =>
46
+ this.validatorClient.signWithAddress(this.attestorAddress, msg, slashingContext).then(s => s.toString());
36
47
  }
37
48
 
38
49
  /**
@@ -68,10 +79,17 @@ export class CheckpointVoter {
68
79
  this.slot,
69
80
  this.slotTimestamp,
70
81
  this.attestorAddress,
71
- this.signer,
82
+ this.governanceSigner,
72
83
  );
73
84
  } catch (err) {
74
- this.log.error(`Error enqueuing governance vote`, err, { slot: this.slot });
85
+ if (err instanceof DutyAlreadySignedError) {
86
+ this.log.info(`Governance vote already signed by another node`, {
87
+ slot: this.slot,
88
+ signedByNode: err.signedByNode,
89
+ });
90
+ } else {
91
+ this.log.error(`Error enqueueing governance vote`, err);
92
+ }
75
93
  return false;
76
94
  }
77
95
  }
@@ -95,10 +113,17 @@ export class CheckpointVoter {
95
113
  this.slot,
96
114
  this.slotTimestamp,
97
115
  this.attestorAddress,
98
- this.signer,
116
+ this.slashingSigner,
99
117
  );
100
118
  } catch (err) {
101
- this.log.error(`Error enqueuing slashing vote`, err, { slot: this.slot });
119
+ if (err instanceof DutyAlreadySignedError) {
120
+ this.log.info(`Slashing vote already signed by another node`, {
121
+ slot: this.slot,
122
+ signedByNode: err.signedByNode,
123
+ });
124
+ } else {
125
+ this.log.error(`Error enqueueing slashing vote`, err);
126
+ }
102
127
  return false;
103
128
  }
104
129
  }
@@ -1,4 +1,3 @@
1
- export * from './block_builder.js';
2
1
  export * from './checkpoint_proposal_job.js';
3
2
  export * from './checkpoint_voter.js';
4
3
  export * from './config.js';