@aztec/sequencer-client 3.0.0-canary.a9708bd → 3.0.0-devnet.2-patch.1

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 (73) hide show
  1. package/dest/client/index.d.ts +1 -1
  2. package/dest/client/sequencer-client.d.ts +7 -6
  3. package/dest/client/sequencer-client.d.ts.map +1 -1
  4. package/dest/client/sequencer-client.js +23 -14
  5. package/dest/config.d.ts +5 -3
  6. package/dest/config.d.ts.map +1 -1
  7. package/dest/config.js +21 -1
  8. package/dest/global_variable_builder/global_builder.d.ts +5 -7
  9. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  10. package/dest/global_variable_builder/global_builder.js +12 -8
  11. package/dest/global_variable_builder/index.d.ts +1 -1
  12. package/dest/index.d.ts +1 -1
  13. package/dest/publisher/config.d.ts +9 -10
  14. package/dest/publisher/config.d.ts.map +1 -1
  15. package/dest/publisher/config.js +19 -17
  16. package/dest/publisher/index.d.ts +2 -2
  17. package/dest/publisher/index.d.ts.map +1 -1
  18. package/dest/publisher/index.js +1 -1
  19. package/dest/publisher/sequencer-publisher-factory.d.ts +9 -3
  20. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  21. package/dest/publisher/sequencer-publisher-factory.js +8 -1
  22. package/dest/publisher/sequencer-publisher-metrics.d.ts +2 -2
  23. package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
  24. package/dest/publisher/sequencer-publisher-metrics.js +1 -1
  25. package/dest/publisher/sequencer-publisher.d.ts +53 -50
  26. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  27. package/dest/publisher/sequencer-publisher.js +187 -132
  28. package/dest/sequencer/block_builder.d.ts +6 -8
  29. package/dest/sequencer/block_builder.d.ts.map +1 -1
  30. package/dest/sequencer/block_builder.js +18 -9
  31. package/dest/sequencer/config.d.ts +2 -2
  32. package/dest/sequencer/config.d.ts.map +1 -1
  33. package/dest/sequencer/errors.d.ts +11 -0
  34. package/dest/sequencer/errors.d.ts.map +1 -0
  35. package/dest/sequencer/errors.js +15 -0
  36. package/dest/sequencer/index.d.ts +1 -1
  37. package/dest/sequencer/metrics.d.ts +17 -20
  38. package/dest/sequencer/metrics.d.ts.map +1 -1
  39. package/dest/sequencer/metrics.js +59 -87
  40. package/dest/sequencer/sequencer.d.ts +54 -32
  41. package/dest/sequencer/sequencer.d.ts.map +1 -1
  42. package/dest/sequencer/sequencer.js +458 -184
  43. package/dest/sequencer/timetable.d.ts +4 -8
  44. package/dest/sequencer/timetable.d.ts.map +1 -1
  45. package/dest/sequencer/timetable.js +3 -10
  46. package/dest/sequencer/utils.d.ts +11 -25
  47. package/dest/sequencer/utils.d.ts.map +1 -1
  48. package/dest/sequencer/utils.js +9 -24
  49. package/dest/test/index.d.ts +2 -2
  50. package/dest/test/index.d.ts.map +1 -1
  51. package/dest/tx_validator/nullifier_cache.d.ts +1 -1
  52. package/dest/tx_validator/nullifier_cache.d.ts.map +1 -1
  53. package/dest/tx_validator/tx_validator_factory.d.ts +4 -3
  54. package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
  55. package/dest/tx_validator/tx_validator_factory.js +12 -9
  56. package/package.json +32 -31
  57. package/src/client/sequencer-client.ts +24 -18
  58. package/src/config.ts +23 -6
  59. package/src/global_variable_builder/global_builder.ts +19 -17
  60. package/src/publisher/config.ts +29 -27
  61. package/src/publisher/index.ts +1 -1
  62. package/src/publisher/sequencer-publisher-factory.ts +16 -3
  63. package/src/publisher/sequencer-publisher-metrics.ts +1 -1
  64. package/src/publisher/sequencer-publisher.ts +257 -183
  65. package/src/sequencer/block_builder.ts +23 -28
  66. package/src/sequencer/config.ts +1 -1
  67. package/src/sequencer/errors.ts +21 -0
  68. package/src/sequencer/metrics.ts +71 -98
  69. package/src/sequencer/sequencer.ts +546 -237
  70. package/src/sequencer/timetable.ts +10 -14
  71. package/src/sequencer/utils.ts +10 -24
  72. package/src/test/index.ts +1 -1
  73. package/src/tx_validator/tx_validator_factory.ts +13 -7
@@ -1,25 +1,22 @@
1
- import { Blob } from '@aztec/blob-lib';
1
+ import { Blob, getBlobsPerL1Block, getPrefixedEthBlobCommitments } from '@aztec/blob-lib';
2
2
  import { createBlobSinkClient } from '@aztec/blob-sink/client';
3
- import { FormattedViemError, MULTI_CALL_3_ADDRESS, Multicall3, RollupContract, formatViemError, tryExtractEvent } from '@aztec/ethereum';
3
+ import { MULTI_CALL_3_ADDRESS, Multicall3, RollupContract } from '@aztec/ethereum/contracts';
4
+ import { WEI_CONST } from '@aztec/ethereum/l1-tx-utils';
5
+ import { FormattedViemError, formatViemError, tryExtractEvent } from '@aztec/ethereum/utils';
4
6
  import { sumBigint } from '@aztec/foundation/bigint';
5
7
  import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
8
+ import { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
6
9
  import { EthAddress } from '@aztec/foundation/eth-address';
10
+ import { Signature } from '@aztec/foundation/eth-signature';
7
11
  import { createLogger } from '@aztec/foundation/log';
8
12
  import { bufferToHex } from '@aztec/foundation/string';
9
13
  import { Timer } from '@aztec/foundation/timer';
10
14
  import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
11
15
  import { encodeSlashConsensusVotes } from '@aztec/slasher';
12
- import { CommitteeAttestation } from '@aztec/stdlib/block';
13
- import { ConsensusPayload, SignatureDomainSeparator, getHashedSignaturePayload } from '@aztec/stdlib/p2p';
16
+ import { CommitteeAttestation, CommitteeAttestationsAndSigners } from '@aztec/stdlib/block';
14
17
  import { getTelemetryClient } from '@aztec/telemetry-client';
15
- import pick from 'lodash.pick';
16
18
  import { encodeFunctionData, toHex } from 'viem';
17
19
  import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
18
- export var SignalType = /*#__PURE__*/ function(SignalType) {
19
- SignalType[SignalType["GOVERNANCE"] = 0] = "GOVERNANCE";
20
- SignalType[SignalType["SLASHING"] = 1] = "SLASHING";
21
- return SignalType;
22
- }({});
23
20
  export const Actions = [
24
21
  'invalidate-by-invalid-attestation',
25
22
  'invalidate-by-insufficient-attestations',
@@ -40,10 +37,11 @@ export class SequencerPublisher {
40
37
  epochCache;
41
38
  governanceLog;
42
39
  slashingLog;
43
- myLastSignals;
40
+ lastActions;
44
41
  log;
45
42
  ethereumSlotDuration;
46
43
  blobSinkClient;
44
+ /** Address to use for simulations in fisherman mode (actual proposer's address) */ proposerAddressForSimulation;
47
45
  // @note - with blobs, the below estimate seems too large.
48
46
  // Total used for full block from int_l1_pub e2e test: 1m (of which 86k is 1x blob)
49
47
  // Total used for emptier block from above test: 429k (of which 84k is 1x blob)
@@ -63,14 +61,12 @@ export class SequencerPublisher {
63
61
  this.interrupted = false;
64
62
  this.governanceLog = createLogger('sequencer:publisher:governance');
65
63
  this.slashingLog = createLogger('sequencer:publisher:slashing');
66
- this.myLastSignals = {
67
- [0]: 0n,
68
- [1]: 0n
69
- };
70
- this.log = createLogger('sequencer:publisher');
64
+ this.lastActions = {};
71
65
  this.requests = [];
66
+ this.log = deps.log ?? createLogger('sequencer:publisher');
72
67
  this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
73
68
  this.epochCache = deps.epochCache;
69
+ this.lastActions = deps.lastActions;
74
70
  this.blobSinkClient = deps.blobSinkClient ?? createBlobSinkClient(config, {
75
71
  logger: createLogger('sequencer:blob-sink:client')
76
72
  });
@@ -93,6 +89,12 @@ export class SequencerPublisher {
93
89
  getSenderAddress() {
94
90
  return this.l1TxUtils.getSenderAddress();
95
91
  }
92
+ /**
93
+ * Sets the proposer address to use for simulations in fisherman mode.
94
+ * @param proposerAddress - The actual proposer's address to use for balance lookups in simulations
95
+ */ setProposerAddressForSimulation(proposerAddress) {
96
+ this.proposerAddressForSimulation = proposerAddress;
97
+ }
96
98
  addRequest(request) {
97
99
  this.requests.push(request);
98
100
  }
@@ -100,6 +102,15 @@ export class SequencerPublisher {
100
102
  return this.epochCache.getEpochAndSlotNow().slot;
101
103
  }
102
104
  /**
105
+ * Clears all pending requests without sending them.
106
+ */ clearPendingRequests() {
107
+ const count = this.requests.length;
108
+ this.requests = [];
109
+ if (count > 0) {
110
+ this.log.debug(`Cleared ${count} pending request(s)`);
111
+ }
112
+ }
113
+ /**
103
114
  * Sends all requests that are still valid.
104
115
  * @returns one of:
105
116
  * - A receipt and stats if the tx succeeded
@@ -148,7 +159,7 @@ export class SequencerPublisher {
148
159
  const gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
149
160
  const txTimeoutAts = gasConfigs.map((g)=>g?.txTimeoutAt).filter((g)=>g !== undefined);
150
161
  const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map((g)=>g.getTime()))) : undefined; // earliest
151
- const gasConfig = {
162
+ const txConfig = {
152
163
  gasLimit,
153
164
  txTimeoutAt
154
165
  };
@@ -157,9 +168,10 @@ export class SequencerPublisher {
157
168
  validRequests.sort((a, b)=>compareActions(a.action, b.action));
158
169
  try {
159
170
  this.log.debug('Forwarding transactions', {
160
- validRequests: validRequests.map((request)=>request.action)
171
+ validRequests: validRequests.map((request)=>request.action),
172
+ txConfig
161
173
  });
162
- const result = await Multicall3.forward(validRequests.map((request)=>request.request), this.l1TxUtils, gasConfig, blobConfig, this.rollupContract.address, this.log);
174
+ const result = await Multicall3.forward(validRequests.map((request)=>request.request), this.l1TxUtils, txConfig, blobConfig, this.rollupContract.address, this.log);
163
175
  const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(validRequests, result);
164
176
  return {
165
177
  result,
@@ -218,7 +230,9 @@ export class SequencerPublisher {
218
230
  'InvalidProposer',
219
231
  'InvalidArchive'
220
232
  ];
221
- return this.rollupContract.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), this.ethereumSlotDuration, opts).catch((err)=>{
233
+ return this.rollupContract.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
234
+ forcePendingCheckpointNumber: opts.forcePendingBlockNumber !== undefined ? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber) : undefined
235
+ }).catch((err)=>{
222
236
  if (err instanceof FormattedViemError && ignoredErrors.find((e)=>err.message.includes(e))) {
223
237
  this.log.warn(`Failed canProposeAtTime check with ${ignoredErrors.find((e)=>err.message.includes(e))}`, {
224
238
  error: err.message
@@ -241,16 +255,28 @@ export class SequencerPublisher {
241
255
  };
242
256
  const args = [
243
257
  header.toViem(),
244
- RollupContract.packAttestations([]),
258
+ CommitteeAttestationsAndSigners.empty().getPackedAttestations(),
245
259
  [],
260
+ Signature.empty().toViemSignature(),
246
261
  `0x${'0'.repeat(64)}`,
247
262
  header.contentCommitment.blobsHash.toString(),
248
263
  flags
249
264
  ];
250
265
  const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
251
- // use sender balance to simulate
252
- const balance = await this.l1TxUtils.getSenderBalance();
253
- this.log.debug(`Simulating validateHeader with balance: ${balance}`);
266
+ const optsForcePendingCheckpointNumber = opts?.forcePendingBlockNumber !== undefined ? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber) : undefined;
267
+ const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(optsForcePendingCheckpointNumber);
268
+ let balance = 0n;
269
+ if (this.config.fishermanMode) {
270
+ // In fisherman mode, we can't know where the proposer is publishing from
271
+ // so we just add sufficient balance to the multicall3 address
272
+ balance = 10n * WEI_CONST * WEI_CONST; // 10 ETH
273
+ } else {
274
+ balance = await this.l1TxUtils.getSenderBalance();
275
+ }
276
+ stateOverrides.push({
277
+ address: MULTI_CALL_3_ADDRESS,
278
+ balance
279
+ });
254
280
  await this.l1TxUtils.simulate({
255
281
  to: this.rollupContract.address,
256
282
  data: encodeFunctionData({
@@ -261,13 +287,7 @@ export class SequencerPublisher {
261
287
  from: MULTI_CALL_3_ADDRESS
262
288
  }, {
263
289
  time: ts + 1n
264
- }, [
265
- {
266
- address: MULTI_CALL_3_ADDRESS,
267
- balance
268
- },
269
- ...await this.rollupContract.makePendingBlockNumberOverride(opts?.forcePendingBlockNumber)
270
- ]);
290
+ }, stateOverrides);
271
291
  this.log.debug(`Simulated validateHeader`);
272
292
  }
273
293
  /**
@@ -278,13 +298,13 @@ export class SequencerPublisher {
278
298
  return undefined;
279
299
  }
280
300
  const { reason, block } = validationResult;
281
- const blockNumber = block.block.number;
301
+ const blockNumber = block.blockNumber;
282
302
  const logData = {
283
- ...block.block.toBlockInfo(),
303
+ ...block,
284
304
  reason
285
305
  };
286
- const currentBlockNumber = await this.rollupContract.getBlockNumber();
287
- if (currentBlockNumber < validationResult.block.block.number) {
306
+ const currentBlockNumber = await this.rollupContract.getCheckpointNumber();
307
+ if (currentBlockNumber < validationResult.block.blockNumber) {
288
308
  this.log.verbose(`Skipping block ${blockNumber} invalidation since it has already been removed from the pending chain`, {
289
309
  currentBlockNumber,
290
310
  ...logData
@@ -292,7 +312,10 @@ export class SequencerPublisher {
292
312
  return undefined;
293
313
  }
294
314
  const request = this.buildInvalidateBlockRequest(validationResult);
295
- this.log.debug(`Simulating invalidate block ${blockNumber}`, logData);
315
+ this.log.debug(`Simulating invalidate block ${blockNumber}`, {
316
+ ...logData,
317
+ request
318
+ });
296
319
  try {
297
320
  const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, ErrorsAbi);
298
321
  this.log.verbose(`Simulation for invalidate block ${blockNumber} succeeded`, {
@@ -304,7 +327,7 @@ export class SequencerPublisher {
304
327
  request,
305
328
  gasUsed,
306
329
  blockNumber,
307
- forcePendingBlockNumber: blockNumber - 1,
330
+ forcePendingBlockNumber: BlockNumber(blockNumber - 1),
308
331
  reason
309
332
  };
310
333
  } catch (err) {
@@ -317,7 +340,7 @@ export class SequencerPublisher {
317
340
  request,
318
341
  error: viemError.message
319
342
  });
320
- const latestPendingBlockNumber = await this.rollupContract.getBlockNumber();
343
+ const latestPendingBlockNumber = await this.rollupContract.getCheckpointNumber();
321
344
  if (latestPendingBlockNumber < blockNumber) {
322
345
  this.log.verbose(`Block number ${blockNumber} has already been invalidated`, {
323
346
  ...logData
@@ -343,14 +366,15 @@ export class SequencerPublisher {
343
366
  }
344
367
  const { block, committee, reason } = validationResult;
345
368
  const logData = {
346
- ...block.block.toBlockInfo(),
369
+ ...block,
347
370
  reason
348
371
  };
349
- this.log.debug(`Simulating invalidate block ${block.block.number}`, logData);
372
+ this.log.debug(`Simulating invalidate block ${block.blockNumber}`, logData);
373
+ const attestationsAndSigners = new CommitteeAttestationsAndSigners(validationResult.attestations).getPackedAttestations();
350
374
  if (reason === 'invalid-attestation') {
351
- return this.rollupContract.buildInvalidateBadAttestationRequest(block.block.number, block.attestations.map((a)=>a.toViem()), committee, validationResult.invalidIndex);
375
+ return this.rollupContract.buildInvalidateBadAttestationRequest(CheckpointNumber.fromBlockNumber(block.blockNumber), attestationsAndSigners, committee, validationResult.invalidIndex);
352
376
  } else if (reason === 'insufficient-attestations') {
353
- return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(block.block.number, block.attestations.map((a)=>a.toViem()), committee);
377
+ return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(CheckpointNumber.fromBlockNumber(block.blockNumber), attestationsAndSigners, committee);
354
378
  } else {
355
379
  const _ = reason;
356
380
  throw new Error(`Unknown reason for invalidation`);
@@ -364,45 +388,41 @@ export class SequencerPublisher {
364
388
  * @param block - The block to propose
365
389
  * @param attestationData - The block's attestation data
366
390
  *
367
- */ async validateBlockForSubmission(block, attestationData = {
368
- digest: Buffer.alloc(32),
369
- attestations: []
370
- }, options) {
391
+ */ async validateBlockForSubmission(block, attestationsAndSigners, attestationsAndSignersSignature, options) {
371
392
  const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
372
393
  // If we have no attestations, we still need to provide the empty attestations
373
394
  // so that the committee is recalculated correctly
374
- const ignoreSignatures = attestationData.attestations.length === 0;
395
+ const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
375
396
  if (ignoreSignatures) {
376
- const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber.toBigInt());
397
+ const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
377
398
  if (!committee) {
378
- this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber.toBigInt()}`);
379
- throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber.toBigInt()}`);
399
+ this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
400
+ throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
380
401
  }
381
- attestationData.attestations = committee.map((committeeMember)=>CommitteeAttestation.fromAddress(committeeMember));
402
+ attestationsAndSigners.attestations = committee.map((committeeMember)=>CommitteeAttestation.fromAddress(committeeMember));
382
403
  }
383
- const blobs = await Blob.getBlobsPerBlock(block.body.toBlobFields());
384
- const blobInput = Blob.getPrefixedEthBlobCommitments(blobs);
385
- const formattedAttestations = attestationData.attestations.map((attest)=>attest.toViem());
386
- const signers = attestationData.attestations.filter((attest)=>!attest.signature.isEmpty()).map((attest)=>attest.address.toString());
404
+ const blobFields = block.getCheckpointBlobFields();
405
+ const blobs = getBlobsPerL1Block(blobFields);
406
+ const blobInput = getPrefixedEthBlobCommitments(blobs);
387
407
  const args = [
388
408
  {
389
- header: block.header.toPropose().toViem(),
409
+ header: block.getCheckpointHeader().toViem(),
390
410
  archive: toHex(block.archive.root.toBuffer()),
391
- stateReference: block.header.state.toViem(),
392
- txHashes: block.body.txEffects.map((txEffect)=>txEffect.txHash.toString()),
393
411
  oracleInput: {
394
412
  feeAssetPriceModifier: 0n
395
413
  }
396
414
  },
397
- RollupContract.packAttestations(formattedAttestations),
398
- signers,
415
+ attestationsAndSigners.getPackedAttestations(),
416
+ attestationsAndSigners.getSigners().map((signer)=>signer.toString()),
417
+ attestationsAndSignersSignature.toViemSignature(),
399
418
  blobInput
400
419
  ];
401
420
  await this.simulateProposeTx(args, ts, options);
402
421
  return ts;
403
422
  }
404
423
  async enqueueCastSignalHelper(slotNumber, timestamp, signalType, payload, base, signerAddress, signer) {
405
- if (this.myLastSignals[signalType] >= slotNumber) {
424
+ if (this.lastActions[signalType] && this.lastActions[signalType] === slotNumber) {
425
+ this.log.debug(`Skipping duplicate vote cast signal ${signalType} for slot ${slotNumber}`);
406
426
  return false;
407
427
  }
408
428
  if (payload.equals(EthAddress.ZERO)) {
@@ -417,9 +437,9 @@ export class SequencerPublisher {
417
437
  if (roundInfo.lastSignalSlot >= slotNumber) {
418
438
  return false;
419
439
  }
420
- const cachedLastVote = this.myLastSignals[signalType];
421
- this.myLastSignals[signalType] = slotNumber;
422
- const action = signalType === 0 ? 'governance-signal' : 'empire-slashing-signal';
440
+ const cachedLastVote = this.lastActions[signalType];
441
+ this.lastActions[signalType] = slotNumber;
442
+ const action = signalType;
423
443
  const request = await base.createSignalRequestWithSignature(payload.toString(), slotNumber, this.config.l1ChainId, signerAddress.toString(), signer);
424
444
  this.log.debug(`Created ${action} request with signature`, {
425
445
  request,
@@ -456,7 +476,7 @@ export class SequencerPublisher {
456
476
  };
457
477
  if (!success) {
458
478
  this.log.error(`Signaling in [${action}] for ${payload} at slot ${slotNumber} in round ${round} failed`, logData);
459
- this.myLastSignals[signalType] = cachedLastVote;
479
+ this.lastActions[signalType] = cachedLastVote;
460
480
  return false;
461
481
  } else {
462
482
  this.log.info(`Signaling in [${action}] for ${payload} at slot ${slotNumber} in round ${round} succeeded`, logData);
@@ -472,7 +492,7 @@ export class SequencerPublisher {
472
492
  * @param timestamp - The timestamp of the slot to cast a signal for.
473
493
  * @returns True if the signal was successfully enqueued, false otherwise.
474
494
  */ enqueueGovernanceCastSignal(governancePayload, slotNumber, timestamp, signerAddress, signer) {
475
- return this.enqueueCastSignalHelper(slotNumber, timestamp, 0, governancePayload, this.govProposerContract, signerAddress, signer);
495
+ return this.enqueueCastSignalHelper(slotNumber, timestamp, 'governance-signal', governancePayload, this.govProposerContract, signerAddress, signer);
476
496
  }
477
497
  /** Enqueues all slashing actions as returned by the slasher client. */ async enqueueSlashingActions(actions, slotNumber, timestamp, signerAddress, signer) {
478
498
  if (actions.length === 0) {
@@ -490,7 +510,7 @@ export class SequencerPublisher {
490
510
  this.log.debug(`Enqueuing slashing vote for payload ${action.payload} at slot ${slotNumber}`, {
491
511
  signerAddress
492
512
  });
493
- await this.enqueueCastSignalHelper(slotNumber, timestamp, 1, action.payload, this.slashingProposerContract, signerAddress, signer);
513
+ await this.enqueueCastSignalHelper(slotNumber, timestamp, 'empire-slashing-signal', action.payload, this.slashingProposerContract, signerAddress, signer);
494
514
  break;
495
515
  }
496
516
  case 'create-empire-payload':
@@ -566,19 +586,17 @@ export class SequencerPublisher {
566
586
  *
567
587
  * @param block - L2 block to propose.
568
588
  * @returns True if the tx has been enqueued, throws otherwise. See #9315
569
- */ async enqueueProposeL2Block(block, attestations, txHashes, opts = {}) {
570
- const proposedBlockHeader = block.header.toPropose();
571
- const consensusPayload = ConsensusPayload.fromBlock(block);
572
- const digest = getHashedSignaturePayload(consensusPayload, SignatureDomainSeparator.blockAttestation);
573
- const blobs = await Blob.getBlobsPerBlock(block.body.toBlobFields());
589
+ */ async enqueueProposeL2Block(block, attestationsAndSigners, attestationsAndSignersSignature, opts = {}) {
590
+ const checkpointHeader = block.getCheckpointHeader();
591
+ const blobFields = block.getCheckpointBlobFields();
592
+ const blobs = getBlobsPerL1Block(blobFields);
574
593
  const proposeTxArgs = {
575
- header: proposedBlockHeader,
594
+ header: checkpointHeader,
576
595
  archive: block.archive.root.toBuffer(),
577
- stateReference: block.header.state,
578
596
  body: block.body.toBuffer(),
579
597
  blobs,
580
- attestations,
581
- txHashes: txHashes ?? []
598
+ attestationsAndSigners,
599
+ attestationsAndSignersSignature
582
600
  };
583
601
  let ts;
584
602
  try {
@@ -586,16 +604,12 @@ export class SequencerPublisher {
586
604
  // This means that we can avoid the simulation issues in later checks.
587
605
  // By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
588
606
  // make time consistency checks break.
589
- const attestationData = {
590
- digest: digest.toBuffer(),
591
- attestations: attestations ?? []
592
- };
593
607
  // TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
594
- ts = await this.validateBlockForSubmission(block, attestationData, opts);
608
+ ts = await this.validateBlockForSubmission(block, attestationsAndSigners, attestationsAndSignersSignature, opts);
595
609
  } catch (err) {
596
610
  this.log.error(`Block validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
597
611
  ...block.getStats(),
598
- slotNumber: block.header.globalVariables.slotNumber.toBigInt(),
612
+ slotNumber: block.header.globalVariables.slotNumber,
599
613
  forcePendingBlockNumber: opts.forcePendingBlockNumber
600
614
  });
601
615
  throw err;
@@ -613,8 +627,10 @@ export class SequencerPublisher {
613
627
  }
614
628
  // We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
615
629
  const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil(Number(request.gasUsed) * 64 / 63)));
630
+ const { gasUsed, blockNumber } = request;
616
631
  const logData = {
617
- ...pick(request, 'gasUsed', 'blockNumber'),
632
+ gasUsed,
633
+ blockNumber,
618
634
  gasLimit,
619
635
  opts
620
636
  };
@@ -626,9 +642,9 @@ export class SequencerPublisher {
626
642
  gasLimit,
627
643
  txTimeoutAt: opts.txTimeoutAt
628
644
  },
629
- lastValidL2Slot: this.getCurrentL2Slot() + 2n,
645
+ lastValidL2Slot: SlotNumber(this.getCurrentL2Slot() + 2),
630
646
  checkSuccess: (_req, result)=>{
631
- const success = result && result.receipt && result.receipt.status === 'success' && tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'BlockInvalidated');
647
+ const success = result && result.receipt && result.receipt.status === 'success' && tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointInvalidated');
632
648
  if (!success) {
633
649
  this.log.warn(`Invalidate block ${request.blockNumber} failed`, {
634
650
  ...result,
@@ -650,8 +666,14 @@ export class SequencerPublisher {
650
666
  timestamp,
651
667
  gasLimit: undefined
652
668
  };
669
+ if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
670
+ this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
671
+ return false;
672
+ }
673
+ const cachedLastActionSlot = this.lastActions[action];
674
+ this.lastActions[action] = slotNumber;
675
+ this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
653
676
  let gasUsed;
654
- this.log.debug(`Simulating ${action}`, logData);
655
677
  try {
656
678
  ({ gasUsed } = await this.l1TxUtils.simulate(request, {
657
679
  time: timestamp
@@ -684,6 +706,7 @@ export class SequencerPublisher {
684
706
  ...result,
685
707
  ...logData
686
708
  });
709
+ this.lastActions[action] = cachedLastActionSlot;
687
710
  } else {
688
711
  this.log.info(`Action ${action} at ${slotNumber} succeeded`, {
689
712
  ...result,
@@ -710,45 +733,52 @@ export class SequencerPublisher {
710
733
  }
711
734
  async prepareProposeTx(encodedData, timestamp, options) {
712
735
  const kzg = Blob.getViemKzgInstance();
713
- const blobInput = Blob.getPrefixedEthBlobCommitments(encodedData.blobs);
736
+ const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
714
737
  this.log.debug('Validating blob input', {
715
738
  blobInput
716
739
  });
717
- const blobEvaluationGas = await this.l1TxUtils.estimateGas(this.getSenderAddress().toString(), {
718
- to: this.rollupContract.address,
719
- data: encodeFunctionData({
720
- abi: RollupAbi,
721
- functionName: 'validateBlobs',
722
- args: [
723
- blobInput
724
- ]
725
- })
726
- }, {}, {
727
- blobs: encodedData.blobs.map((b)=>b.data),
728
- kzg
729
- }).catch((err)=>{
730
- const { message, metaMessages } = formatViemError(err);
731
- this.log.error(`Failed to validate blobs`, message, {
732
- metaMessages
740
+ // Get blob evaluation gas
741
+ let blobEvaluationGas;
742
+ if (this.config.fishermanMode) {
743
+ // In fisherman mode, we can't estimate blob gas because estimateGas doesn't support state overrides
744
+ // Use a fixed estimate.
745
+ blobEvaluationGas = BigInt(encodedData.blobs.length) * 21_000n;
746
+ this.log.debug(`Using fixed blob evaluation gas estimate in fisherman mode: ${blobEvaluationGas}`);
747
+ } else {
748
+ // Normal mode - use estimateGas with blob inputs
749
+ blobEvaluationGas = await this.l1TxUtils.estimateGas(this.getSenderAddress().toString(), {
750
+ to: this.rollupContract.address,
751
+ data: encodeFunctionData({
752
+ abi: RollupAbi,
753
+ functionName: 'validateBlobs',
754
+ args: [
755
+ blobInput
756
+ ]
757
+ })
758
+ }, {}, {
759
+ blobs: encodedData.blobs.map((b)=>b.data),
760
+ kzg
761
+ }).catch((err)=>{
762
+ const { message, metaMessages } = formatViemError(err);
763
+ this.log.error(`Failed to validate blobs`, message, {
764
+ metaMessages
765
+ });
766
+ throw new Error('Failed to validate blobs');
733
767
  });
734
- throw new Error('Failed to validate blobs');
735
- });
736
- const attestations = encodedData.attestations ? encodedData.attestations.map((attest)=>attest.toViem()) : [];
737
- const txHashes = encodedData.txHashes ? encodedData.txHashes.map((txHash)=>txHash.toString()) : [];
738
- const signers = encodedData.attestations?.filter((attest)=>!attest.signature.isEmpty()).map((attest)=>attest.address.toString());
768
+ }
769
+ const signers = encodedData.attestationsAndSigners.getSigners().map((signer)=>signer.toString());
739
770
  const args = [
740
771
  {
741
772
  header: encodedData.header.toViem(),
742
773
  archive: toHex(encodedData.archive),
743
- stateReference: encodedData.stateReference.toViem(),
744
774
  oracleInput: {
745
775
  // We are currently not modifying these. See #9963
746
776
  feeAssetPriceModifier: 0n
747
- },
748
- txHashes
777
+ }
749
778
  },
750
- RollupContract.packAttestations(attestations),
751
- signers ?? [],
779
+ encodedData.attestationsAndSigners.getPackedAttestations(),
780
+ signers,
781
+ encodedData.attestationsAndSignersSignature.toViemSignature(),
752
782
  blobInput
753
783
  ];
754
784
  const { rollupData, simulationResult } = await this.simulateProposeTx(args, timestamp, options);
@@ -770,18 +800,10 @@ export class SequencerPublisher {
770
800
  functionName: 'propose',
771
801
  args
772
802
  });
773
- // override the pending block number if requested
774
- const forcePendingBlockNumberStateDiff = (options.forcePendingBlockNumber !== undefined ? await this.rollupContract.makePendingBlockNumberOverride(options.forcePendingBlockNumber) : []).flatMap((override)=>override.stateDiff ?? []);
775
- const simulationResult = await this.l1TxUtils.simulate({
776
- to: this.rollupContract.address,
777
- data: rollupData,
778
- gas: SequencerPublisher.PROPOSE_GAS_GUESS
779
- }, {
780
- // @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
781
- time: timestamp + 1n,
782
- // @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
783
- gasLimit: SequencerPublisher.PROPOSE_GAS_GUESS * 2n
784
- }, [
803
+ // override the pending checkpoint number if requested
804
+ const optsForcePendingCheckpointNumber = options.forcePendingBlockNumber !== undefined ? CheckpointNumber.fromBlockNumber(options.forcePendingBlockNumber) : undefined;
805
+ const forcePendingCheckpointNumberStateDiff = (optsForcePendingCheckpointNumber !== undefined ? await this.rollupContract.makePendingCheckpointNumberOverride(optsForcePendingCheckpointNumber) : []).flatMap((override)=>override.stateDiff ?? []);
806
+ const stateOverrides = [
785
807
  {
786
808
  address: this.rollupContract.address,
787
809
  // @note we override checkBlob to false since blobs are not part simulate()
@@ -790,14 +812,44 @@ export class SequencerPublisher {
790
812
  slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true),
791
813
  value: toPaddedHex(0n, true)
792
814
  },
793
- ...forcePendingBlockNumberStateDiff
815
+ ...forcePendingCheckpointNumberStateDiff
794
816
  ]
795
817
  }
796
- ], RollupAbi, {
818
+ ];
819
+ // In fisherman mode, simulate as the proposer but with sufficient balance
820
+ if (this.proposerAddressForSimulation) {
821
+ stateOverrides.push({
822
+ address: this.proposerAddressForSimulation.toString(),
823
+ balance: 10n * WEI_CONST * WEI_CONST
824
+ });
825
+ }
826
+ const simulationResult = await this.l1TxUtils.simulate({
827
+ to: this.rollupContract.address,
828
+ data: rollupData,
829
+ gas: SequencerPublisher.PROPOSE_GAS_GUESS,
830
+ ...this.proposerAddressForSimulation && {
831
+ from: this.proposerAddressForSimulation.toString()
832
+ }
833
+ }, {
834
+ // @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
835
+ time: timestamp + 1n,
836
+ // @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
837
+ gasLimit: SequencerPublisher.PROPOSE_GAS_GUESS * 2n
838
+ }, stateOverrides, RollupAbi, {
797
839
  // @note fallback gas estimate to use if the node doesn't support simulation API
798
840
  fallbackGasEstimate: SequencerPublisher.PROPOSE_GAS_GUESS
799
841
  }).catch((err)=>{
800
- this.log.error(`Failed to simulate propose tx`, err);
842
+ // In fisherman mode, we expect ValidatorSelection__MissingProposerSignature since fisherman doesn't have proposer signature
843
+ const viemError = formatViemError(err);
844
+ if (this.config.fishermanMode && viemError.message?.includes('ValidatorSelection__MissingProposerSignature')) {
845
+ this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
846
+ // Return a minimal simulation result with the fallback gas estimate
847
+ return {
848
+ gasUsed: SequencerPublisher.PROPOSE_GAS_GUESS,
849
+ logs: []
850
+ };
851
+ }
852
+ this.log.error(`Failed to simulate propose tx`, viemError);
801
853
  throw err;
802
854
  });
803
855
  return {
@@ -822,7 +874,7 @@ export class SequencerPublisher {
822
874
  to: this.rollupContract.address,
823
875
  data: rollupData
824
876
  },
825
- lastValidL2Slot: block.header.globalVariables.slotNumber.toBigInt(),
877
+ lastValidL2Slot: block.header.globalVariables.slotNumber,
826
878
  gasConfig: {
827
879
  ...opts,
828
880
  gasLimit
@@ -836,17 +888,20 @@ export class SequencerPublisher {
836
888
  return false;
837
889
  }
838
890
  const { receipt, stats, errorMsg } = result;
839
- const success = receipt && receipt.status === 'success' && tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'L2BlockProposed');
891
+ const success = receipt && receipt.status === 'success' && tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointProposed');
840
892
  if (success) {
841
893
  const endBlock = receipt.blockNumber;
842
894
  const inclusionBlocks = Number(endBlock - startBlock);
895
+ const { calldataGas, calldataSize, sender } = stats;
843
896
  const publishStats = {
844
897
  gasPrice: receipt.effectiveGasPrice,
845
898
  gasUsed: receipt.gasUsed,
846
899
  blobGasUsed: receipt.blobGasUsed ?? 0n,
847
900
  blobDataGas: receipt.blobGasPrice ?? 0n,
848
901
  transactionHash: receipt.transactionHash,
849
- ...pick(stats, 'calldataGas', 'calldataSize', 'sender'),
902
+ calldataGas,
903
+ calldataSize,
904
+ sender,
850
905
  ...block.getStats(),
851
906
  eventName: 'rollup-published-to-l1',
852
907
  blobCount: encodedData.blobs.length,
@@ -865,7 +920,7 @@ export class SequencerPublisher {
865
920
  ...block.getStats(),
866
921
  receipt,
867
922
  txHash: receipt.transactionHash,
868
- slotNumber: block.header.globalVariables.slotNumber.toBigInt()
923
+ slotNumber: block.header.globalVariables.slotNumber
869
924
  });
870
925
  return false;
871
926
  }