@aztec/sequencer-client 0.0.1-commit.3f296a7d2 → 0.0.1-commit.42ee6df9b

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.
@@ -1,4 +1,5 @@
1
1
  import type { EpochCache } from '@aztec/epoch-cache';
2
+ import { type FeeHeader, RollupContract } from '@aztec/ethereum/contracts';
2
3
  import {
3
4
  BlockNumber,
4
5
  CheckpointNumber,
@@ -30,8 +31,8 @@ import {
30
31
  type L2BlockSource,
31
32
  MaliciousCommitteeAttestationsAndSigners,
32
33
  } from '@aztec/stdlib/block';
33
- import { type Checkpoint, validateCheckpoint } from '@aztec/stdlib/checkpoint';
34
- import { getSlotStartBuildTimestamp, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
34
+ import { type Checkpoint, type ProposedCheckpointData, validateCheckpoint } from '@aztec/stdlib/checkpoint';
35
+ import { computeQuorum, getSlotStartBuildTimestamp, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
35
36
  import { Gas } from '@aztec/stdlib/gas';
36
37
  import {
37
38
  type BlockBuilderOptions,
@@ -67,6 +68,13 @@ import { SequencerState } from './utils.js';
67
68
  /** How much time to sleep while waiting for min transactions to accumulate for a block */
68
69
  const TXS_POLLING_MS = 500;
69
70
 
71
+ /** Result from proposeCheckpoint when a checkpoint was successfully built and attested. */
72
+ type CheckpointProposalResult = {
73
+ checkpoint: Checkpoint;
74
+ attestations: CommitteeAttestationsAndSigners;
75
+ attestationsSignature: Signature;
76
+ };
77
+
70
78
  /**
71
79
  * Handles the execution of a checkpoint proposal after the initial preparation phase.
72
80
  * This includes building blocks, collecting attestations, and publishing the checkpoint to L1,
@@ -76,10 +84,15 @@ const TXS_POLLING_MS = 500;
76
84
  export class CheckpointProposalJob implements Traceable {
77
85
  protected readonly log: Logger;
78
86
 
87
+ /** Tracks the fire-and-forget L1 submission promise so it can be awaited during shutdown. */
88
+ private pendingL1Submission: Promise<void> | undefined;
89
+
90
+ /** Fee header override computed during proposeCheckpoint, reused in enqueueCheckpointForSubmission. */
91
+ private computedForceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
92
+
79
93
  constructor(
80
94
  private readonly slotNow: SlotNumber,
81
95
  private readonly targetSlot: SlotNumber,
82
- private readonly epochNow: EpochNumber,
83
96
  private readonly targetEpoch: EpochNumber,
84
97
  private readonly checkpointNumber: CheckpointNumber,
85
98
  private readonly syncedToBlockNumber: BlockNumber,
@@ -107,6 +120,7 @@ export class CheckpointProposalJob implements Traceable {
107
120
  private readonly setStateFn: (state: SequencerState, slot?: SlotNumber) => void,
108
121
  public readonly tracer: Tracer,
109
122
  bindings?: LoggerBindings,
123
+ private readonly proposedCheckpointData?: ProposedCheckpointData,
110
124
  ) {
111
125
  this.log = createLogger('sequencer:checkpoint-proposal', {
112
126
  ...bindings,
@@ -114,19 +128,17 @@ export class CheckpointProposalJob implements Traceable {
114
128
  });
115
129
  }
116
130
 
117
- /** The wall-clock slot during which the proposer builds. */
118
- private get slot(): SlotNumber {
119
- return this.slotNow;
120
- }
121
-
122
- /** The wall-clock epoch. */
123
- private get epoch(): EpochNumber {
124
- return this.epochNow;
131
+ /** Awaits the pending L1 submission if one is in progress. Call during shutdown. */
132
+ public async awaitPendingSubmission(): Promise<void> {
133
+ this.log.info('Awaiting pending L1 payload submission');
134
+ await this.pendingL1Submission;
125
135
  }
126
136
 
127
137
  /**
128
138
  * Executes the checkpoint proposal job.
129
- * Returns the published checkpoint if successful, undefined otherwise.
139
+ * Builds blocks, collects attestations, enqueues requests, and schedules L1 submission as a
140
+ * background task so the work loop can return to IDLE immediately.
141
+ * Returns the built checkpoint if successful, undefined otherwise.
130
142
  */
131
143
  @trackSpan('CheckpointProposalJob.execute')
132
144
  public async execute(): Promise<Checkpoint | undefined> {
@@ -145,8 +157,10 @@ export class CheckpointProposalJob implements Traceable {
145
157
  this.log,
146
158
  ).enqueueVotes();
147
159
 
148
- // Build and propose the checkpoint. This will enqueue the request on the publisher if a checkpoint is built.
149
- const checkpoint = await this.proposeCheckpoint();
160
+ // Build and propose the checkpoint. Builds blocks, broadcasts, collects attestations, and signs.
161
+ // Does NOT enqueue to L1 yet — that happens after the pipeline sleep.
162
+ const proposalResult = await this.proposeCheckpoint();
163
+ const checkpoint = proposalResult?.checkpoint;
150
164
 
151
165
  // Wait until the voting promises have resolved, so all requests are enqueued (not sent)
152
166
  await Promise.all(votesPromises);
@@ -161,41 +175,85 @@ export class CheckpointProposalJob implements Traceable {
161
175
  return;
162
176
  }
163
177
 
164
- // If pipelining, wait until the submission slot so L1 recognizes the pipelined proposer
165
- if (this.epochCache.isProposerPipeliningEnabled()) {
166
- const submissionSlotTimestamp =
167
- getTimestampForSlot(this.targetSlot, this.l1Constants) - BigInt(this.l1Constants.ethereumSlotDuration);
168
- this.log.info(`Waiting until submission slot ${this.targetSlot} for L1 submission`, {
169
- slot: this.slot,
170
- submissionSlot: this.targetSlot,
171
- submissionSlotTimestamp,
178
+ // Enqueue the checkpoint for L1 submission
179
+ if (proposalResult) {
180
+ try {
181
+ await this.enqueueCheckpointForSubmission(proposalResult);
182
+ } catch (err) {
183
+ this.log.error(`Failed to enqueue checkpoint for L1 submission at slot ${this.targetSlot}`, err);
184
+ // Continue to sendRequestsAt so votes are still sent
185
+ }
186
+ }
187
+
188
+ // Compute the earliest time to submit: pipeline slot start when pipelining, now otherwise.
189
+ const submitAfter = this.epochCache.isProposerPipeliningEnabled()
190
+ ? new Date(Number(getTimestampForSlot(this.targetSlot, this.l1Constants)) * 1000)
191
+ : new Date(this.dateProvider.now());
192
+
193
+ // TODO(https://github.com/AztecProtocol/aztec-packages/pull/21250): should discard the pending submission if a reorg occurs underneath
194
+
195
+ // Schedule L1 submission in the background so the work loop returns immediately.
196
+ // The publisher will sleep until submitAfter, then send the bundled requests.
197
+ // The promise is stored so it can be awaited during shutdown.
198
+ this.pendingL1Submission = this.publisher
199
+ .sendRequestsAt(submitAfter)
200
+ .then(async l1Response => {
201
+ const proposedAction = l1Response?.successfulActions.find(a => a === 'propose');
202
+ if (proposedAction) {
203
+ this.eventEmitter.emit('checkpoint-published', { checkpoint: this.checkpointNumber, slot: this.targetSlot });
204
+ const coinbase = checkpoint?.header.coinbase;
205
+ await this.metrics.incFilledSlot(this.publisher.getSenderAddress().toString(), coinbase);
206
+ } else if (checkpoint) {
207
+ this.eventEmitter.emit('checkpoint-publish-failed', { ...l1Response, slot: this.targetSlot });
208
+
209
+ if (this.epochCache.isProposerPipeliningEnabled()) {
210
+ this.metrics.recordPipelineDiscard();
211
+ }
212
+ }
213
+ })
214
+ .catch(err => {
215
+ this.log.error(`Background L1 submission failed for slot ${this.targetSlot}`, err);
216
+ if (checkpoint) {
217
+ this.eventEmitter.emit('checkpoint-publish-failed', { slot: this.targetSlot });
218
+
219
+ if (this.epochCache.isProposerPipeliningEnabled()) {
220
+ this.metrics.recordPipelineDiscard();
221
+ }
222
+ }
172
223
  });
173
- await sleepUntil(new Date(Number(submissionSlotTimestamp) * 1000), this.dateProvider.nowAsDate());
174
224
 
175
- // After waking, verify the parent checkpoint wasn't pruned during the sleep.
176
- // We check L1's pending tip directly instead of canProposeAt, which also validates the proposer
177
- // identity and would fail because the timestamp resolves to a different slot's proposer.
178
- const l1Tips = await this.publisher.rollupContract.getTips();
179
- if (l1Tips.pending < this.checkpointNumber - 1) {
225
+ // Return the built checkpoint immediately the work loop is now unblocked
226
+ return checkpoint;
227
+ }
228
+
229
+ /** Enqueues the checkpoint for L1 submission. Called after pipeline sleep in execute(). */
230
+ private async enqueueCheckpointForSubmission(result: CheckpointProposalResult): Promise<void> {
231
+ const { checkpoint, attestations, attestationsSignature } = result;
232
+
233
+ this.setStateFn(SequencerState.PUBLISHING_CHECKPOINT, this.targetSlot);
234
+ const aztecSlotDuration = this.l1Constants.slotDuration;
235
+ const submissionSlotStart = Number(getTimestampForSlot(this.targetSlot, this.l1Constants));
236
+ const txTimeoutAt = new Date((submissionSlotStart + aztecSlotDuration) * 1000);
237
+
238
+ // If we have been configured to potentially skip publishing checkpoint then roll the dice here
239
+ if (
240
+ this.config.skipPublishingCheckpointsPercent !== undefined &&
241
+ this.config.skipPublishingCheckpointsPercent > 0
242
+ ) {
243
+ const roll = Math.max(0, randomInt(100));
244
+ if (roll < this.config.skipPublishingCheckpointsPercent) {
180
245
  this.log.warn(
181
- `Parent checkpoint was pruned during pipelining sleep (L1 pending=${l1Tips.pending}, expected>=${this.checkpointNumber - 1}), skipping L1 submission for checkpoint ${this.checkpointNumber}`,
246
+ `Skipping publishing proposal for checkpoint ${checkpoint.number}. Configured percentage: ${this.config.skipPublishingCheckpointsPercent}, generated value: ${roll}`,
182
247
  );
183
- return undefined;
248
+ return;
184
249
  }
185
250
  }
186
251
 
187
- // Then send everything to L1
188
- const l1Response = await this.publisher.sendRequests();
189
- const proposedAction = l1Response?.successfulActions.find(a => a === 'propose');
190
- if (proposedAction) {
191
- this.eventEmitter.emit('checkpoint-published', { checkpoint: this.checkpointNumber, slot: this.slot });
192
- const coinbase = checkpoint?.header.coinbase;
193
- await this.metrics.incFilledSlot(this.publisher.getSenderAddress().toString(), coinbase);
194
- return checkpoint;
195
- } else if (checkpoint) {
196
- this.eventEmitter.emit('checkpoint-publish-failed', { ...l1Response, slot: this.slot });
197
- return undefined;
198
- }
252
+ await this.publisher.enqueueProposeCheckpoint(checkpoint, attestations, attestationsSignature, {
253
+ txTimeoutAt,
254
+ forcePendingCheckpointNumber: this.invalidateCheckpoint?.forcePendingCheckpointNumber,
255
+ forceProposedFeeHeader: this.computedForceProposedFeeHeader,
256
+ });
199
257
  }
200
258
 
201
259
  @trackSpan('CheckpointProposalJob.proposeCheckpoint', function () {
@@ -205,7 +263,7 @@ export class CheckpointProposalJob implements Traceable {
205
263
  [Attributes.SLOT_NUMBER]: this.targetSlot,
206
264
  };
207
265
  })
208
- private async proposeCheckpoint(): Promise<Checkpoint | undefined> {
266
+ private async proposeCheckpoint(): Promise<CheckpointProposalResult | undefined> {
209
267
  try {
210
268
  // Get operator configured coinbase and fee recipient for this attestor
211
269
  const coinbase = this.validatorClient.getCoinbaseForAttestor(this.attestorAddress);
@@ -214,7 +272,7 @@ export class CheckpointProposalJob implements Traceable {
214
272
  // Start the checkpoint
215
273
  this.setStateFn(SequencerState.INITIALIZING_CHECKPOINT, this.targetSlot);
216
274
  this.log.info(`Starting checkpoint proposal`, {
217
- buildSlot: this.slot,
275
+ buildSlot: this.slotNow,
218
276
  submissionSlot: this.targetSlot,
219
277
  pipelining: this.epochCache.isProposerPipeliningEnabled(),
220
278
  proposer: this.proposer?.toString(),
@@ -227,11 +285,25 @@ export class CheckpointProposalJob implements Traceable {
227
285
  this.publisher.enqueueInvalidateCheckpoint(this.invalidateCheckpoint);
228
286
  }
229
287
 
230
- // Create checkpoint builder for the slot
288
+ // Create checkpoint builder for the slot.
289
+ // When pipelining, force the proposed checkpoint number and fee header to our parent so the
290
+ // fee computation sees the same chain tip that L1 will see once the previous pipelined checkpoint lands.
291
+ const isPipelining = this.epochCache.isProposerPipeliningEnabled();
292
+ const parentCheckpointNumber = isPipelining ? CheckpointNumber(this.checkpointNumber - 1) : undefined;
293
+
294
+ // Compute the parent's fee header override when pipelining
295
+ if (isPipelining && this.proposedCheckpointData) {
296
+ this.computedForceProposedFeeHeader = await this.computeForceProposedFeeHeader(parentCheckpointNumber!);
297
+ }
298
+
231
299
  const checkpointGlobalVariables = await this.globalsBuilder.buildCheckpointGlobalVariables(
232
300
  coinbase,
233
301
  feeRecipient,
234
302
  this.targetSlot,
303
+ {
304
+ forcePendingCheckpointNumber: parentCheckpointNumber,
305
+ forceProposedFeeHeader: this.computedForceProposedFeeHeader,
306
+ },
235
307
  );
236
308
 
237
309
  // Collect L1 to L2 messages for the checkpoint and compute their hash
@@ -326,7 +398,7 @@ export class CheckpointProposalJob implements Traceable {
326
398
  maxTxsPerCheckpoint: this.config.maxTxsPerCheckpoint,
327
399
  });
328
400
  } catch (err) {
329
- this.log.error(`Built an invalid checkpoint at slot ${this.slot} (skipping proposal)`, err, {
401
+ this.log.error(`Built an invalid checkpoint at slot ${this.slotNow} (skipping proposal)`, err, {
330
402
  checkpoint: checkpoint.header.toInspect(),
331
403
  });
332
404
  return undefined;
@@ -352,7 +424,11 @@ export class CheckpointProposalJob implements Traceable {
352
424
  },
353
425
  );
354
426
  this.metrics.recordCheckpointSuccess();
355
- return checkpoint;
427
+ return {
428
+ checkpoint,
429
+ attestations: CommitteeAttestationsAndSigners.empty(),
430
+ attestationsSignature: Signature.empty(),
431
+ };
356
432
  }
357
433
 
358
434
  // Include the block pending broadcast in the checkpoint proposal if any
@@ -400,39 +476,15 @@ export class CheckpointProposalJob implements Traceable {
400
476
  throw err;
401
477
  }
402
478
 
403
- // Enqueue publishing the checkpoint to L1
404
- this.setStateFn(SequencerState.PUBLISHING_CHECKPOINT, this.targetSlot);
405
- const aztecSlotDuration = this.l1Constants.slotDuration;
406
- const submissionSlotStart = Number(getTimestampForSlot(this.targetSlot, this.l1Constants));
407
- const txTimeoutAt = new Date((submissionSlotStart + aztecSlotDuration) * 1000);
408
-
409
- // If we have been configured to potentially skip publishing checkpoint then roll the dice here
410
- if (
411
- this.config.skipPublishingCheckpointsPercent !== undefined &&
412
- this.config.skipPublishingCheckpointsPercent > 0
413
- ) {
414
- const result = Math.max(0, randomInt(100));
415
- if (result < this.config.skipPublishingCheckpointsPercent) {
416
- this.log.warn(
417
- `Skipping publishing proposal for checkpoint ${checkpoint.number}. Configured percentage: ${this.config.skipPublishingCheckpointsPercent}, generated value: ${result}`,
418
- );
419
- return checkpoint;
420
- }
421
- }
422
-
423
- await this.publisher.enqueueProposeCheckpoint(checkpoint, attestations, attestationsSignature, {
424
- txTimeoutAt,
425
- forcePendingCheckpointNumber: this.invalidateCheckpoint?.forcePendingCheckpointNumber,
426
- });
427
-
428
- return checkpoint;
479
+ // Return the result for the caller to enqueue after the pipeline sleep
480
+ return { checkpoint, attestations, attestationsSignature };
429
481
  } catch (err) {
430
482
  if (err && (err instanceof DutyAlreadySignedError || err instanceof SlashingProtectionError)) {
431
483
  // swallow this error. It's already been logged by a function deeper in the stack
432
484
  return undefined;
433
485
  }
434
486
 
435
- this.log.error(`Error building checkpoint at slot ${this.slot}`, err);
487
+ this.log.error(`Error building checkpoint at slot ${this.targetSlot}`, err);
436
488
  return undefined;
437
489
  }
438
490
  }
@@ -702,6 +754,8 @@ export class CheckpointProposalJob implements Traceable {
702
754
  { blockHash, txHashes, manaPerSec, ...blockStats },
703
755
  );
704
756
 
757
+ // `slot` is the target/submission slot (may be one ahead when pipelining),
758
+ // `buildSlot` is the wall-clock slot during which the block was actually built.
705
759
  this.eventEmitter.emit('block-proposed', {
706
760
  blockNumber: block.number,
707
761
  slot: this.targetSlot,
@@ -810,7 +864,7 @@ export class CheckpointProposalJob implements Traceable {
810
864
  this.log.debug(`Attesting committee length is ${committee.length}`, { committee });
811
865
  }
812
866
 
813
- const numberOfRequiredAttestations = Math.floor((committee.length * 2) / 3) + 1;
867
+ const numberOfRequiredAttestations = computeQuorum(committee.length);
814
868
 
815
869
  if (this.config.skipCollectingAttestations) {
816
870
  this.log.warn('Skipping attestation collection as per config (attesting with own keys only)');
@@ -960,7 +1014,7 @@ export class CheckpointProposalJob implements Traceable {
960
1014
  * would never receive its own block without this explicit sync.
961
1015
  */
962
1016
  private async syncProposedBlockToArchiver(block: L2Block): Promise<void> {
963
- if (this.config.skipPushProposedBlocksToArchiver !== false) {
1017
+ if (this.config.skipPushProposedBlocksToArchiver) {
964
1018
  this.log.warn(`Skipping push of proposed block ${block.number} to archiver`, {
965
1019
  blockNumber: block.number,
966
1020
  slot: block.header.globalVariables.slotNumber,
@@ -1021,6 +1075,56 @@ export class CheckpointProposalJob implements Traceable {
1021
1075
  return false;
1022
1076
  }
1023
1077
 
1078
+ /**
1079
+ * In times of congestion we need to simulate using the correct fee header override for the previous block
1080
+ * We calculate the correct fee header values.
1081
+ *
1082
+ * If we are in block 1, or the checkpoint we are querying does not exist, we return undefined. However
1083
+ * If we are pipelining - where this function is called, the grandparentCheckpointNumber should always exist
1084
+ * @param parentCheckpointNumber
1085
+ * @returns
1086
+ */
1087
+ protected async computeForceProposedFeeHeader(parentCheckpointNumber: CheckpointNumber): Promise<
1088
+ | {
1089
+ checkpointNumber: CheckpointNumber;
1090
+ feeHeader: FeeHeader;
1091
+ }
1092
+ | undefined
1093
+ > {
1094
+ if (!this.proposedCheckpointData) {
1095
+ return undefined;
1096
+ }
1097
+
1098
+ const rollup = this.publisher.rollupContract;
1099
+ const grandparentCheckpointNumber = CheckpointNumber(this.checkpointNumber - 2);
1100
+ try {
1101
+ const [grandparentCheckpoint, manaTarget] = await Promise.all([
1102
+ rollup.getCheckpoint(grandparentCheckpointNumber),
1103
+ rollup.getManaTarget(),
1104
+ ]);
1105
+
1106
+ if (!grandparentCheckpoint || !grandparentCheckpoint.feeHeader) {
1107
+ this.log.error(
1108
+ `Grandparent checkpoint or its feeHeader is undefined for checkpointNumber=${grandparentCheckpointNumber.toString()}`,
1109
+ );
1110
+ return undefined;
1111
+ } else {
1112
+ const parentFeeHeader = RollupContract.computeChildFeeHeader(
1113
+ grandparentCheckpoint.feeHeader,
1114
+ this.proposedCheckpointData.totalManaUsed,
1115
+ this.proposedCheckpointData.feeAssetPriceModifier,
1116
+ manaTarget,
1117
+ );
1118
+ return { checkpointNumber: parentCheckpointNumber, feeHeader: parentFeeHeader };
1119
+ }
1120
+ } catch (err) {
1121
+ this.log.error(
1122
+ `Failed to fetch grandparent checkpoint or mana target for checkpointNumber=${grandparentCheckpointNumber.toString()}: ${err}`,
1123
+ );
1124
+ return undefined;
1125
+ }
1126
+ }
1127
+
1024
1128
  /** Waits until a specific time within the current slot */
1025
1129
  @trackSpan('CheckpointProposalJob.waitUntilTimeInSlot')
1026
1130
  protected async waitUntilTimeInSlot(targetSecondsIntoSlot: number): Promise<void> {
@@ -1035,7 +1139,7 @@ export class CheckpointProposalJob implements Traceable {
1035
1139
  }
1036
1140
 
1037
1141
  private getSlotStartBuildTimestamp(): number {
1038
- return getSlotStartBuildTimestamp(this.slot, this.l1Constants);
1142
+ return getSlotStartBuildTimestamp(this.slotNow, this.l1Constants);
1039
1143
  }
1040
1144
 
1041
1145
  private getSecondsIntoSlot(): number {
@@ -2,7 +2,6 @@ import type { SlotNumber } from '@aztec/foundation/branded-types';
2
2
  import type { EthAddress } from '@aztec/foundation/eth-address';
3
3
  import type { Logger } from '@aztec/foundation/log';
4
4
  import type { SlasherClientInterface } from '@aztec/slasher';
5
- import { getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
6
5
  import type { ResolvedSequencerConfig } from '@aztec/stdlib/interfaces/server';
7
6
  import type { ValidatorClient } from '@aztec/validator-client';
8
7
  import { DutyAlreadySignedError } from '@aztec/validator-ha-signer/errors';
@@ -18,7 +17,6 @@ import type { SequencerRollupConstants } from './types.js';
18
17
  * Handles governance and slashing voting for a given slot.
19
18
  */
20
19
  export class CheckpointVoter {
21
- private slotTimestamp: bigint;
22
20
  private governanceSigner: (msg: TypedDataDefinition) => Promise<`0x${string}`>;
23
21
  private slashingSigner: (msg: TypedDataDefinition) => Promise<`0x${string}`>;
24
22
 
@@ -33,8 +31,6 @@ export class CheckpointVoter {
33
31
  private readonly metrics: SequencerMetrics,
34
32
  private readonly log: Logger,
35
33
  ) {
36
- this.slotTimestamp = getTimestampForSlot(this.slot, this.l1Constants);
37
-
38
34
  // Create separate signers with appropriate duty contexts for governance and slashing votes
39
35
  // These use HA protection to ensure only one node signs per slot/duty
40
36
  const governanceContext: SigningContext = { slot: this.slot, dutyType: DutyType.GOVERNANCE_VOTE };
@@ -77,7 +73,6 @@ export class CheckpointVoter {
77
73
  return await this.publisher.enqueueGovernanceCastSignal(
78
74
  governanceProposerPayload,
79
75
  this.slot,
80
- this.slotTimestamp,
81
76
  this.attestorAddress,
82
77
  this.governanceSigner,
83
78
  );
@@ -108,13 +103,7 @@ export class CheckpointVoter {
108
103
 
109
104
  this.metrics.recordSlashingAttempt(actions.length);
110
105
 
111
- return await this.publisher.enqueueSlashingActions(
112
- actions,
113
- this.slot,
114
- this.slotTimestamp,
115
- this.attestorAddress,
116
- this.slashingSigner,
117
- );
106
+ return await this.publisher.enqueueSlashingActions(actions, this.slot, this.attestorAddress, this.slashingSigner);
118
107
  } catch (err) {
119
108
  if (err instanceof DutyAlreadySignedError) {
120
109
  this.log.info(`Slashing vote already signed by another node`, {