@aztec/sequencer-client 0.0.1-commit.54489865 → 0.0.1-commit.5914bae
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/client/sequencer-client.d.ts +16 -7
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +56 -23
- package/dest/config.d.ts +25 -6
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +46 -28
- package/dest/global_variable_builder/global_builder.d.ts +14 -10
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +22 -21
- package/dest/global_variable_builder/index.d.ts +2 -2
- package/dest/global_variable_builder/index.d.ts.map +1 -1
- package/dest/publisher/config.d.ts +31 -17
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +101 -42
- package/dest/publisher/sequencer-publisher-factory.d.ts +11 -3
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +13 -2
- package/dest/publisher/sequencer-publisher.d.ts +29 -14
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +144 -74
- package/dest/sequencer/checkpoint_proposal_job.d.ts +30 -7
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +191 -113
- package/dest/sequencer/checkpoint_voter.d.ts +1 -2
- package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_voter.js +2 -5
- package/dest/sequencer/metrics.d.ts +17 -5
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +86 -15
- package/dest/sequencer/sequencer.d.ts +29 -13
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +47 -43
- package/dest/sequencer/timetable.d.ts +4 -6
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +7 -11
- package/dest/sequencer/types.d.ts +2 -2
- package/dest/sequencer/types.d.ts.map +1 -1
- package/dest/test/index.d.ts +3 -5
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts +12 -12
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +47 -36
- package/dest/test/utils.d.ts +3 -3
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +5 -4
- package/package.json +28 -28
- package/src/client/sequencer-client.ts +78 -21
- package/src/config.ts +61 -38
- package/src/global_variable_builder/global_builder.ts +23 -24
- package/src/global_variable_builder/index.ts +1 -1
- package/src/publisher/config.ts +112 -43
- package/src/publisher/sequencer-publisher-factory.ts +23 -6
- package/src/publisher/sequencer-publisher.ts +155 -87
- package/src/sequencer/checkpoint_proposal_job.ts +285 -159
- package/src/sequencer/checkpoint_voter.ts +1 -12
- package/src/sequencer/metrics.ts +92 -18
- package/src/sequencer/sequencer.ts +60 -49
- package/src/sequencer/timetable.ts +13 -12
- package/src/sequencer/types.ts +1 -1
- package/src/test/index.ts +2 -4
- package/src/test/mock_checkpoint_builder.ts +67 -53
- package/src/test/utils.ts +5 -2
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
import { NUM_CHECKPOINT_END_MARKER_FIELDS, getNumBlockEndBlobFields } from '@aztec/blob-lib/encoding';
|
|
2
|
-
import { BLOBS_PER_CHECKPOINT, FIELDS_PER_BLOB } from '@aztec/constants';
|
|
3
1
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
4
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
BlockNumber,
|
|
4
|
+
CheckpointNumber,
|
|
5
|
+
EpochNumber,
|
|
6
|
+
IndexWithinCheckpoint,
|
|
7
|
+
SlotNumber,
|
|
8
|
+
} from '@aztec/foundation/branded-types';
|
|
5
9
|
import { randomInt } from '@aztec/foundation/crypto/random';
|
|
10
|
+
import {
|
|
11
|
+
flipSignature,
|
|
12
|
+
generateRecoverableSignature,
|
|
13
|
+
generateUnrecoverableSignature,
|
|
14
|
+
} from '@aztec/foundation/crypto/secp256k1-signer';
|
|
6
15
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
7
16
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
8
17
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
@@ -10,7 +19,7 @@ import { filter } from '@aztec/foundation/iterator';
|
|
|
10
19
|
import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
|
|
11
20
|
import { sleep, sleepUntil } from '@aztec/foundation/sleep';
|
|
12
21
|
import { type DateProvider, Timer } from '@aztec/foundation/timer';
|
|
13
|
-
import { type TypedEventEmitter, unfreeze } from '@aztec/foundation/types';
|
|
22
|
+
import { type TypedEventEmitter, isErrorClass, unfreeze } from '@aztec/foundation/types';
|
|
14
23
|
import type { P2P } from '@aztec/p2p';
|
|
15
24
|
import type { SlasherClientInterface } from '@aztec/slasher';
|
|
16
25
|
import {
|
|
@@ -21,17 +30,23 @@ import {
|
|
|
21
30
|
type L2BlockSource,
|
|
22
31
|
MaliciousCommitteeAttestationsAndSigners,
|
|
23
32
|
} from '@aztec/stdlib/block';
|
|
24
|
-
import type
|
|
33
|
+
import { type Checkpoint, validateCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
25
34
|
import { getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
26
35
|
import { Gas } from '@aztec/stdlib/gas';
|
|
27
|
-
import
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
import {
|
|
37
|
+
type BlockBuilderOptions,
|
|
38
|
+
InsufficientValidTxsError,
|
|
39
|
+
type ResolvedSequencerConfig,
|
|
40
|
+
type WorldStateSynchronizer,
|
|
31
41
|
} from '@aztec/stdlib/interfaces/server';
|
|
32
42
|
import { type L1ToL2MessageSource, computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
|
|
33
|
-
import type {
|
|
34
|
-
|
|
43
|
+
import type {
|
|
44
|
+
BlockProposal,
|
|
45
|
+
BlockProposalOptions,
|
|
46
|
+
CheckpointProposal,
|
|
47
|
+
CheckpointProposalOptions,
|
|
48
|
+
} from '@aztec/stdlib/p2p';
|
|
49
|
+
import { orderAttestations, trimAttestations } from '@aztec/stdlib/p2p';
|
|
35
50
|
import type { L2BlockBuiltStats } from '@aztec/stdlib/stats';
|
|
36
51
|
import { type FailedTx, Tx } from '@aztec/stdlib/tx';
|
|
37
52
|
import { AttestationTimeoutError } from '@aztec/stdlib/validators';
|
|
@@ -122,7 +137,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
122
137
|
await Promise.all(votesPromises);
|
|
123
138
|
|
|
124
139
|
if (checkpoint) {
|
|
125
|
-
this.metrics.
|
|
140
|
+
this.metrics.recordCheckpointProposalSuccess();
|
|
126
141
|
}
|
|
127
142
|
|
|
128
143
|
// Do not post anything to L1 if we are fishermen, but do perform L1 fee analysis
|
|
@@ -179,18 +194,21 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
179
194
|
const inHash = computeInHashFromL1ToL2Messages(l1ToL2Messages);
|
|
180
195
|
|
|
181
196
|
// Collect the out hashes of all the checkpoints before this one in the same epoch
|
|
182
|
-
const
|
|
183
|
-
c => c.
|
|
184
|
-
|
|
185
|
-
|
|
197
|
+
const previousCheckpointOutHashes = (await this.l2BlockSource.getCheckpointsDataForEpoch(this.epoch))
|
|
198
|
+
.filter(c => c.checkpointNumber < this.checkpointNumber)
|
|
199
|
+
.map(c => c.checkpointOutHash);
|
|
200
|
+
|
|
201
|
+
// Get the fee asset price modifier from the oracle
|
|
202
|
+
const feeAssetPriceModifier = await this.publisher.getFeeAssetPriceModifier();
|
|
186
203
|
|
|
187
204
|
// Create a long-lived forked world state for the checkpoint builder
|
|
188
|
-
using fork = await this.worldState.fork(this.syncedToBlockNumber, { closeDelayMs: 12_000 });
|
|
205
|
+
await using fork = await this.worldState.fork(this.syncedToBlockNumber, { closeDelayMs: 12_000 });
|
|
189
206
|
|
|
190
207
|
// Create checkpoint builder for the entire slot
|
|
191
208
|
const checkpointBuilder = await this.checkpointsBuilder.startCheckpoint(
|
|
192
209
|
this.checkpointNumber,
|
|
193
210
|
checkpointGlobalVariables,
|
|
211
|
+
feeAssetPriceModifier,
|
|
194
212
|
l1ToL2Messages,
|
|
195
213
|
previousCheckpointOutHashes,
|
|
196
214
|
fork,
|
|
@@ -210,6 +228,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
210
228
|
|
|
211
229
|
let blocksInCheckpoint: L2Block[] = [];
|
|
212
230
|
let blockPendingBroadcast: { block: L2Block; txs: Tx[] } | undefined = undefined;
|
|
231
|
+
const checkpointBuildTimer = new Timer();
|
|
213
232
|
|
|
214
233
|
try {
|
|
215
234
|
// Main loop: build blocks for the checkpoint
|
|
@@ -225,19 +244,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
225
244
|
// These errors are expected in HA mode, so we yield and let another HA node handle the slot
|
|
226
245
|
// The only distinction between the 2 errors is SlashingProtectionError throws when the payload is different,
|
|
227
246
|
// which is normal for block building (may have picked different txs)
|
|
228
|
-
if (err
|
|
229
|
-
this.log.info(`Checkpoint proposal for slot ${this.slot} already signed by another HA node, yielding`, {
|
|
230
|
-
slot: this.slot,
|
|
231
|
-
signedByNode: err.signedByNode,
|
|
232
|
-
});
|
|
233
|
-
return undefined;
|
|
234
|
-
}
|
|
235
|
-
if (err instanceof SlashingProtectionError) {
|
|
236
|
-
this.log.info(`Checkpoint proposal for slot ${this.slot} blocked by slashing protection, yielding`, {
|
|
237
|
-
slot: this.slot,
|
|
238
|
-
existingMessageHash: err.existingMessageHash,
|
|
239
|
-
attemptedMessageHash: err.attemptedMessageHash,
|
|
240
|
-
});
|
|
247
|
+
if (this.handleHASigningError(err, 'Block proposal')) {
|
|
241
248
|
return undefined;
|
|
242
249
|
}
|
|
243
250
|
throw err;
|
|
@@ -249,11 +256,45 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
249
256
|
return undefined;
|
|
250
257
|
}
|
|
251
258
|
|
|
259
|
+
const minBlocksForCheckpoint = this.config.minBlocksForCheckpoint;
|
|
260
|
+
if (minBlocksForCheckpoint !== undefined && blocksInCheckpoint.length < minBlocksForCheckpoint) {
|
|
261
|
+
this.log.warn(
|
|
262
|
+
`Checkpoint has fewer blocks than minimum (${blocksInCheckpoint.length} < ${minBlocksForCheckpoint}), skipping proposal`,
|
|
263
|
+
{ slot: this.slot, blocksBuilt: blocksInCheckpoint.length, minBlocksForCheckpoint },
|
|
264
|
+
);
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
267
|
+
|
|
252
268
|
// Assemble and broadcast the checkpoint proposal, including the last block that was not
|
|
253
269
|
// broadcasted yet, and wait to collect the committee attestations.
|
|
254
270
|
this.setStateFn(SequencerState.ASSEMBLING_CHECKPOINT, this.slot);
|
|
255
271
|
const checkpoint = await checkpointBuilder.completeCheckpoint();
|
|
256
272
|
|
|
273
|
+
// Final validation: per-block limits are only checked if the operator set them explicitly.
|
|
274
|
+
// Otherwise, checkpoint-level budgets were already enforced by the redistribution logic.
|
|
275
|
+
try {
|
|
276
|
+
validateCheckpoint(checkpoint, {
|
|
277
|
+
rollupManaLimit: this.l1Constants.rollupManaLimit,
|
|
278
|
+
maxL2BlockGas: this.config.maxL2BlockGas,
|
|
279
|
+
maxDABlockGas: this.config.maxDABlockGas,
|
|
280
|
+
maxTxsPerBlock: this.config.maxTxsPerBlock,
|
|
281
|
+
maxTxsPerCheckpoint: this.config.maxTxsPerCheckpoint,
|
|
282
|
+
});
|
|
283
|
+
} catch (err) {
|
|
284
|
+
this.log.error(`Built an invalid checkpoint at slot ${this.slot} (skipping proposal)`, err, {
|
|
285
|
+
checkpoint: checkpoint.header.toInspect(),
|
|
286
|
+
});
|
|
287
|
+
return undefined;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Record checkpoint-level build metrics
|
|
291
|
+
this.metrics.recordCheckpointBuild(
|
|
292
|
+
checkpointBuildTimer.ms(),
|
|
293
|
+
blocksInCheckpoint.length,
|
|
294
|
+
checkpoint.getStats().txCount,
|
|
295
|
+
Number(checkpoint.header.totalManaUsed.toBigInt()),
|
|
296
|
+
);
|
|
297
|
+
|
|
257
298
|
// Do not collect attestations nor publish to L1 in fisherman mode
|
|
258
299
|
if (this.config.fishermanMode) {
|
|
259
300
|
this.log.info(
|
|
@@ -280,6 +321,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
280
321
|
const proposal = await this.validatorClient.createCheckpointProposal(
|
|
281
322
|
checkpoint.header,
|
|
282
323
|
checkpoint.archive.root,
|
|
324
|
+
feeAssetPriceModifier,
|
|
283
325
|
lastBlock,
|
|
284
326
|
this.proposer,
|
|
285
327
|
checkpointProposalOptions,
|
|
@@ -306,20 +348,8 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
306
348
|
);
|
|
307
349
|
} catch (err) {
|
|
308
350
|
// We shouldn't really get here since we yield to another HA node
|
|
309
|
-
// as soon as we see these errors when creating block proposals.
|
|
310
|
-
if (err
|
|
311
|
-
this.log.info(`Attestations signature for slot ${this.slot} already signed by another HA node, yielding`, {
|
|
312
|
-
slot: this.slot,
|
|
313
|
-
signedByNode: err.signedByNode,
|
|
314
|
-
});
|
|
315
|
-
return undefined;
|
|
316
|
-
}
|
|
317
|
-
if (err instanceof SlashingProtectionError) {
|
|
318
|
-
this.log.info(`Attestations signature for slot ${this.slot} blocked by slashing protection, yielding`, {
|
|
319
|
-
slot: this.slot,
|
|
320
|
-
existingMessageHash: err.existingMessageHash,
|
|
321
|
-
attemptedMessageHash: err.attemptedMessageHash,
|
|
322
|
-
});
|
|
351
|
+
// as soon as we see these errors when creating block or checkpoint proposals.
|
|
352
|
+
if (this.handleHASigningError(err, 'Attestations signature')) {
|
|
323
353
|
return undefined;
|
|
324
354
|
}
|
|
325
355
|
throw err;
|
|
@@ -330,6 +360,21 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
330
360
|
const aztecSlotDuration = this.l1Constants.slotDuration;
|
|
331
361
|
const slotStartBuildTimestamp = this.getSlotStartBuildTimestamp();
|
|
332
362
|
const txTimeoutAt = new Date((slotStartBuildTimestamp + aztecSlotDuration) * 1000);
|
|
363
|
+
|
|
364
|
+
// If we have been configured to potentially skip publishing checkpoint then roll the dice here
|
|
365
|
+
if (
|
|
366
|
+
this.config.skipPublishingCheckpointsPercent !== undefined &&
|
|
367
|
+
this.config.skipPublishingCheckpointsPercent > 0
|
|
368
|
+
) {
|
|
369
|
+
const result = Math.max(0, randomInt(100));
|
|
370
|
+
if (result < this.config.skipPublishingCheckpointsPercent) {
|
|
371
|
+
this.log.warn(
|
|
372
|
+
`Skipping publishing proposal for checkpoint ${checkpoint.number}. Configured percentage: ${this.config.skipPublishingCheckpointsPercent}, generated value: ${result}`,
|
|
373
|
+
);
|
|
374
|
+
return checkpoint;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
333
378
|
await this.publisher.enqueueProposeCheckpoint(checkpoint, attestations, attestationsSignature, {
|
|
334
379
|
txTimeoutAt,
|
|
335
380
|
forcePendingCheckpointNumber: this.invalidateCheckpoint?.forcePendingCheckpointNumber,
|
|
@@ -363,27 +408,21 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
363
408
|
const blocksInCheckpoint: L2Block[] = [];
|
|
364
409
|
const txHashesAlreadyIncluded = new Set<string>();
|
|
365
410
|
const initialBlockNumber = BlockNumber(this.syncedToBlockNumber + 1);
|
|
366
|
-
|
|
367
|
-
// Remaining blob fields available for blocks (checkpoint end marker already subtracted)
|
|
368
|
-
let remainingBlobFields = BLOBS_PER_CHECKPOINT * FIELDS_PER_BLOB - NUM_CHECKPOINT_END_MARKER_FIELDS;
|
|
411
|
+
const slot = this.slot;
|
|
369
412
|
|
|
370
413
|
// Last block in the checkpoint will usually be flagged as pending broadcast, so we send it along with the checkpoint proposal
|
|
371
414
|
let blockPendingBroadcast: { block: L2Block; txs: Tx[] } | undefined = undefined;
|
|
372
415
|
|
|
373
416
|
while (true) {
|
|
374
417
|
const blocksBuilt = blocksInCheckpoint.length;
|
|
375
|
-
const indexWithinCheckpoint = blocksBuilt;
|
|
418
|
+
const indexWithinCheckpoint = IndexWithinCheckpoint(blocksBuilt);
|
|
376
419
|
const blockNumber = BlockNumber(initialBlockNumber + blocksBuilt);
|
|
377
420
|
|
|
378
421
|
const secondsIntoSlot = this.getSecondsIntoSlot();
|
|
379
422
|
const timingInfo = this.timetable.canStartNextBlock(secondsIntoSlot);
|
|
380
423
|
|
|
381
424
|
if (!timingInfo.canStart) {
|
|
382
|
-
this.log.debug(`Not enough time left in slot to start another block`, {
|
|
383
|
-
slot: this.slot,
|
|
384
|
-
blocksBuilt,
|
|
385
|
-
secondsIntoSlot,
|
|
386
|
-
});
|
|
425
|
+
this.log.debug(`Not enough time left in slot to start another block`, { slot, blocksBuilt, secondsIntoSlot });
|
|
387
426
|
break;
|
|
388
427
|
}
|
|
389
428
|
|
|
@@ -399,9 +438,9 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
399
438
|
blockNumber,
|
|
400
439
|
indexWithinCheckpoint,
|
|
401
440
|
txHashesAlreadyIncluded,
|
|
402
|
-
remainingBlobFields,
|
|
403
441
|
});
|
|
404
442
|
|
|
443
|
+
// TODO(palla/mbps): Review these conditions. We may want to keep trying in some scenarios.
|
|
405
444
|
if (!buildResult && timingInfo.isLastBlock) {
|
|
406
445
|
// If no block was produced due to not enough txs and this was the last subslot, exit
|
|
407
446
|
break;
|
|
@@ -415,56 +454,37 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
415
454
|
} else if ('error' in buildResult) {
|
|
416
455
|
// If there was an error building the block, just exit the loop and give up the rest of the slot
|
|
417
456
|
if (!(buildResult.error instanceof SequencerInterruptedError)) {
|
|
418
|
-
this.log.warn(`Halting block building for slot ${
|
|
419
|
-
slot: this.slot,
|
|
420
|
-
blocksBuilt,
|
|
421
|
-
error: buildResult.error,
|
|
422
|
-
});
|
|
457
|
+
this.log.warn(`Halting block building for slot ${slot}`, { slot, blocksBuilt, error: buildResult.error });
|
|
423
458
|
}
|
|
424
459
|
break;
|
|
425
460
|
}
|
|
426
461
|
|
|
427
|
-
const { block, usedTxs
|
|
462
|
+
const { block, usedTxs } = buildResult;
|
|
428
463
|
blocksInCheckpoint.push(block);
|
|
429
|
-
|
|
430
|
-
// Update remaining blob fields for the next block
|
|
431
|
-
remainingBlobFields = newRemainingBlobFields;
|
|
432
|
-
|
|
433
|
-
// Sync the proposed block to the archiver to make it available
|
|
434
|
-
// Note that the checkpoint builder uses its own fork so it should not need to wait for this syncing
|
|
435
|
-
// Eventually we should refactor the checkpoint builder to not need a separate long-lived fork
|
|
436
|
-
// Fire and forget - don't block the critical path, but log errors
|
|
437
|
-
this.syncProposedBlockToArchiver(block).catch(err => {
|
|
438
|
-
this.log.error(`Failed to sync proposed block ${block.number} to archiver`, { blockNumber: block.number, err });
|
|
439
|
-
});
|
|
440
|
-
|
|
441
464
|
usedTxs.forEach(tx => txHashesAlreadyIncluded.add(tx.txHash.toString()));
|
|
442
465
|
|
|
443
|
-
// If this is the last block,
|
|
466
|
+
// If this is the last block, send the proposed block to the archiver,
|
|
467
|
+
// and exit the loop now so we can build the checkpoint and start collecting attestations.
|
|
444
468
|
if (timingInfo.isLastBlock) {
|
|
445
|
-
this.
|
|
446
|
-
|
|
447
|
-
blockNumber,
|
|
448
|
-
blocksBuilt,
|
|
449
|
-
});
|
|
469
|
+
await this.syncProposedBlockToArchiver(block);
|
|
470
|
+
this.log.verbose(`Completed final block ${blockNumber} for slot ${slot}`, { slot, blockNumber, blocksBuilt });
|
|
450
471
|
blockPendingBroadcast = { block, txs: usedTxs };
|
|
451
472
|
break;
|
|
452
473
|
}
|
|
453
474
|
|
|
454
|
-
//
|
|
455
|
-
//
|
|
456
|
-
if
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
}
|
|
475
|
+
// Broadcast the block proposal (unless we're in fisherman mode) unless the block is the last one,
|
|
476
|
+
// in which case we'll broadcast it along with the checkpoint at the end of the loop.
|
|
477
|
+
// Note that we only send the block to the archiver if we manage to create the proposal, so if there's
|
|
478
|
+
// a HA error we don't pollute our archiver with a block that won't make it to the chain.
|
|
479
|
+
const proposal = await this.createBlockProposal(block, inHash, usedTxs, blockProposalOptions);
|
|
480
|
+
|
|
481
|
+
// Sync the proposed block to the archiver to make it available, only after we've managed to sign the proposal.
|
|
482
|
+
// We wait for the sync to succeed, as this helps catch consistency errors, even if it means we lose some time for block-building.
|
|
483
|
+
// If this throws, we abort the entire checkpoint.
|
|
484
|
+
await this.syncProposedBlockToArchiver(block);
|
|
485
|
+
|
|
486
|
+
// Once we have a signed proposal and the archiver agreed with our proposed block, then we broadcast it.
|
|
487
|
+
proposal && (await this.p2pClient.broadcastProposal(proposal));
|
|
468
488
|
|
|
469
489
|
// Wait until the next block's start time
|
|
470
490
|
await this.waitUntilNextSubslot(timingInfo.deadline);
|
|
@@ -478,6 +498,28 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
478
498
|
return { blocksInCheckpoint, blockPendingBroadcast };
|
|
479
499
|
}
|
|
480
500
|
|
|
501
|
+
/** Creates a block proposal for a given block via the validator client (unless in fisherman mode) */
|
|
502
|
+
private createBlockProposal(
|
|
503
|
+
block: L2Block,
|
|
504
|
+
inHash: Fr,
|
|
505
|
+
usedTxs: Tx[],
|
|
506
|
+
blockProposalOptions: BlockProposalOptions,
|
|
507
|
+
): Promise<BlockProposal | undefined> {
|
|
508
|
+
if (this.config.fishermanMode) {
|
|
509
|
+
this.log.info(`Skipping block proposal for block ${block.number} in fisherman mode`);
|
|
510
|
+
return Promise.resolve(undefined);
|
|
511
|
+
}
|
|
512
|
+
return this.validatorClient.createBlockProposal(
|
|
513
|
+
block.header,
|
|
514
|
+
block.indexWithinCheckpoint,
|
|
515
|
+
inHash,
|
|
516
|
+
block.archive.root,
|
|
517
|
+
usedTxs,
|
|
518
|
+
this.proposer,
|
|
519
|
+
blockProposalOptions,
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
|
|
481
523
|
/** Sleeps until it is time to produce the next block in the slot */
|
|
482
524
|
@trackSpan('CheckpointProposalJob.waitUntilNextSubslot')
|
|
483
525
|
private async waitUntilNextSubslot(nextSubslotStart: number) {
|
|
@@ -488,27 +530,19 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
488
530
|
|
|
489
531
|
/** Builds a single block. Called from the main block building loop. */
|
|
490
532
|
@trackSpan('CheckpointProposalJob.buildSingleBlock')
|
|
491
|
-
|
|
533
|
+
protected async buildSingleBlock(
|
|
492
534
|
checkpointBuilder: CheckpointBuilder,
|
|
493
535
|
opts: {
|
|
494
536
|
forceCreate?: boolean;
|
|
495
537
|
blockTimestamp: bigint;
|
|
496
538
|
blockNumber: BlockNumber;
|
|
497
|
-
indexWithinCheckpoint:
|
|
539
|
+
indexWithinCheckpoint: IndexWithinCheckpoint;
|
|
498
540
|
buildDeadline: Date | undefined;
|
|
499
541
|
txHashesAlreadyIncluded: Set<string>;
|
|
500
|
-
remainingBlobFields: number;
|
|
501
542
|
},
|
|
502
|
-
): Promise<{ block: L2Block; usedTxs: Tx[]
|
|
503
|
-
const {
|
|
504
|
-
|
|
505
|
-
forceCreate,
|
|
506
|
-
blockNumber,
|
|
507
|
-
indexWithinCheckpoint,
|
|
508
|
-
buildDeadline,
|
|
509
|
-
txHashesAlreadyIncluded,
|
|
510
|
-
remainingBlobFields,
|
|
511
|
-
} = opts;
|
|
543
|
+
): Promise<{ block: L2Block; usedTxs: Tx[] } | { error: Error } | undefined> {
|
|
544
|
+
const { blockTimestamp, forceCreate, blockNumber, indexWithinCheckpoint, buildDeadline, txHashesAlreadyIncluded } =
|
|
545
|
+
opts;
|
|
512
546
|
|
|
513
547
|
this.log.verbose(
|
|
514
548
|
`Preparing block ${blockNumber} index ${indexWithinCheckpoint} at checkpoint ${this.checkpointNumber} for slot ${this.slot}`,
|
|
@@ -517,8 +551,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
517
551
|
|
|
518
552
|
try {
|
|
519
553
|
// Wait until we have enough txs to build the block
|
|
520
|
-
const minTxs = this.
|
|
521
|
-
const { availableTxs, canStartBuilding } = await this.waitForMinTxs(opts);
|
|
554
|
+
const { availableTxs, canStartBuilding, minTxs } = await this.waitForMinTxs(opts);
|
|
522
555
|
if (!canStartBuilding) {
|
|
523
556
|
this.log.warn(
|
|
524
557
|
`Not enough txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.slot} (got ${availableTxs} txs but needs ${minTxs})`,
|
|
@@ -532,7 +565,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
532
565
|
// Create iterator to pending txs. We filter out txs already included in previous blocks in the checkpoint
|
|
533
566
|
// just in case p2p failed to sync the provisional block and didn't get to remove those txs from the mempool yet.
|
|
534
567
|
const pendingTxs = filter(
|
|
535
|
-
this.p2pClient.
|
|
568
|
+
this.p2pClient.iterateEligiblePendingTxs(),
|
|
536
569
|
tx => !txHashesAlreadyIncluded.has(tx.txHash.toString()),
|
|
537
570
|
);
|
|
538
571
|
|
|
@@ -542,64 +575,66 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
542
575
|
);
|
|
543
576
|
this.setStateFn(SequencerState.CREATING_BLOCK, this.slot);
|
|
544
577
|
|
|
545
|
-
//
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
const blockBuilderOptions:
|
|
578
|
+
// Per-block limits are operator overrides (from SEQ_MAX_L2_BLOCK_GAS etc.) further capped
|
|
579
|
+
// by remaining checkpoint-level budgets inside CheckpointBuilder before each block is built.
|
|
580
|
+
// minValidTxs is passed into the builder so it can reject the block *before* updating state.
|
|
581
|
+
const minValidTxs = forceCreate ? 0 : (this.config.minValidTxsPerBlock ?? minTxs);
|
|
582
|
+
const blockBuilderOptions: BlockBuilderOptions = {
|
|
550
583
|
maxTransactions: this.config.maxTxsPerBlock,
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
584
|
+
maxBlockGas:
|
|
585
|
+
this.config.maxL2BlockGas !== undefined || this.config.maxDABlockGas !== undefined
|
|
586
|
+
? new Gas(this.config.maxDABlockGas ?? Infinity, this.config.maxL2BlockGas ?? Infinity)
|
|
587
|
+
: undefined,
|
|
554
588
|
deadline: buildDeadline,
|
|
589
|
+
isBuildingProposal: true,
|
|
590
|
+
minValidTxs,
|
|
591
|
+
maxBlocksPerCheckpoint: this.timetable.maxNumberOfBlocks,
|
|
592
|
+
perBlockAllocationMultiplier: this.config.perBlockAllocationMultiplier,
|
|
555
593
|
};
|
|
556
594
|
|
|
557
|
-
// Actually build the block by executing txs
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
usedTxBlobFields,
|
|
568
|
-
} = await checkpointBuilder.buildBlock(pendingTxs, blockNumber, blockTimestamp, blockBuilderOptions);
|
|
569
|
-
const blockBuildDuration = workTimer.ms();
|
|
595
|
+
// Actually build the block by executing txs. The builder throws InsufficientValidTxsError
|
|
596
|
+
// if the number of successfully processed txs is below minValidTxs, ensuring state is not
|
|
597
|
+
// updated for blocks that will be discarded.
|
|
598
|
+
const buildResult = await this.buildSingleBlockWithCheckpointBuilder(
|
|
599
|
+
checkpointBuilder,
|
|
600
|
+
pendingTxs,
|
|
601
|
+
blockNumber,
|
|
602
|
+
blockTimestamp,
|
|
603
|
+
blockBuilderOptions,
|
|
604
|
+
);
|
|
570
605
|
|
|
571
606
|
// If any txs failed during execution, drop them from the mempool so we don't pick them up again
|
|
572
|
-
await this.dropFailedTxsFromP2P(failedTxs);
|
|
607
|
+
await this.dropFailedTxsFromP2P(buildResult.failedTxs);
|
|
573
608
|
|
|
574
|
-
|
|
575
|
-
// too long, then we may not get to minTxsPerBlock after executing public functions.
|
|
576
|
-
const minValidTxs = this.config.minValidTxsPerBlock ?? minTxs;
|
|
577
|
-
if (!forceCreate && numTxs < minValidTxs) {
|
|
609
|
+
if (buildResult.status === 'insufficient-valid-txs') {
|
|
578
610
|
this.log.warn(
|
|
579
|
-
`Block ${blockNumber} at index ${indexWithinCheckpoint} on slot ${this.slot} has too few valid txs to be proposed
|
|
580
|
-
{
|
|
611
|
+
`Block ${blockNumber} at index ${indexWithinCheckpoint} on slot ${this.slot} has too few valid txs to be proposed`,
|
|
612
|
+
{
|
|
613
|
+
slot: this.slot,
|
|
614
|
+
blockNumber,
|
|
615
|
+
numTxs: buildResult.processedCount,
|
|
616
|
+
indexWithinCheckpoint,
|
|
617
|
+
minValidTxs,
|
|
618
|
+
},
|
|
581
619
|
);
|
|
582
|
-
this.eventEmitter.emit('block-
|
|
583
|
-
minTxs: minValidTxs,
|
|
584
|
-
availableTxs: numTxs,
|
|
585
|
-
slot: this.slot,
|
|
586
|
-
});
|
|
620
|
+
this.eventEmitter.emit('block-build-failed', { reason: `Insufficient valid txs`, slot: this.slot });
|
|
587
621
|
this.metrics.recordBlockProposalFailed('insufficient_valid_txs');
|
|
588
622
|
return undefined;
|
|
589
623
|
}
|
|
590
624
|
|
|
591
625
|
// Block creation succeeded, emit stats and metrics
|
|
626
|
+
const { block, publicProcessorDuration, usedTxs, blockBuildDuration, numTxs } = buildResult;
|
|
627
|
+
|
|
592
628
|
const blockStats = {
|
|
593
629
|
eventName: 'l2-block-built',
|
|
594
630
|
duration: blockBuildDuration,
|
|
595
631
|
publicProcessDuration: publicProcessorDuration,
|
|
596
|
-
rollupCircuitsDuration: blockBuildingTimer.ms(),
|
|
597
632
|
...block.getStats(),
|
|
598
633
|
} satisfies L2BlockBuiltStats;
|
|
599
634
|
|
|
600
635
|
const blockHash = await block.hash();
|
|
601
636
|
const txHashes = block.body.txEffects.map(tx => tx.txHash);
|
|
602
|
-
const manaPerSec =
|
|
637
|
+
const manaPerSec = block.header.totalManaUsed.toNumberUnsafe() / (blockBuildDuration / 1000);
|
|
603
638
|
|
|
604
639
|
this.log.info(
|
|
605
640
|
`Built block ${block.number} at checkpoint ${this.checkpointNumber} for slot ${this.slot} with ${numTxs} txs`,
|
|
@@ -607,9 +642,9 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
607
642
|
);
|
|
608
643
|
|
|
609
644
|
this.eventEmitter.emit('block-proposed', { blockNumber: block.number, slot: this.slot });
|
|
610
|
-
this.metrics.recordBuiltBlock(blockBuildDuration,
|
|
645
|
+
this.metrics.recordBuiltBlock(blockBuildDuration, block.header.totalManaUsed.toNumberUnsafe());
|
|
611
646
|
|
|
612
|
-
return { block, usedTxs
|
|
647
|
+
return { block, usedTxs };
|
|
613
648
|
} catch (err: any) {
|
|
614
649
|
this.eventEmitter.emit('block-build-failed', { reason: err.message, slot: this.slot });
|
|
615
650
|
this.log.error(`Error building block`, err, { blockNumber, slot: this.slot });
|
|
@@ -619,17 +654,44 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
619
654
|
}
|
|
620
655
|
}
|
|
621
656
|
|
|
657
|
+
/** Uses the checkpoint builder to build a block, catching InsufficientValidTxsError. */
|
|
658
|
+
private async buildSingleBlockWithCheckpointBuilder(
|
|
659
|
+
checkpointBuilder: CheckpointBuilder,
|
|
660
|
+
pendingTxs: AsyncIterable<Tx>,
|
|
661
|
+
blockNumber: BlockNumber,
|
|
662
|
+
blockTimestamp: bigint,
|
|
663
|
+
blockBuilderOptions: BlockBuilderOptions,
|
|
664
|
+
) {
|
|
665
|
+
try {
|
|
666
|
+
const workTimer = new Timer();
|
|
667
|
+
const result = await checkpointBuilder.buildBlock(pendingTxs, blockNumber, blockTimestamp, blockBuilderOptions);
|
|
668
|
+
const blockBuildDuration = workTimer.ms();
|
|
669
|
+
return { ...result, blockBuildDuration, status: 'success' as const };
|
|
670
|
+
} catch (err: unknown) {
|
|
671
|
+
if (isErrorClass(err, InsufficientValidTxsError)) {
|
|
672
|
+
return {
|
|
673
|
+
failedTxs: err.failedTxs,
|
|
674
|
+
processedCount: err.processedCount,
|
|
675
|
+
status: 'insufficient-valid-txs' as const,
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
throw err;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
622
682
|
/** Waits until minTxs are available on the pool for building a block. */
|
|
623
683
|
@trackSpan('CheckpointProposalJob.waitForMinTxs')
|
|
624
684
|
private async waitForMinTxs(opts: {
|
|
625
685
|
forceCreate?: boolean;
|
|
626
686
|
blockNumber: BlockNumber;
|
|
627
|
-
indexWithinCheckpoint:
|
|
687
|
+
indexWithinCheckpoint: IndexWithinCheckpoint;
|
|
628
688
|
buildDeadline: Date | undefined;
|
|
629
|
-
}): Promise<{ canStartBuilding: boolean; availableTxs: number }> {
|
|
630
|
-
const minTxs = this.config.minTxsPerBlock;
|
|
689
|
+
}): Promise<{ canStartBuilding: boolean; availableTxs: number; minTxs: number }> {
|
|
631
690
|
const { indexWithinCheckpoint, blockNumber, buildDeadline, forceCreate } = opts;
|
|
632
691
|
|
|
692
|
+
// We only allow a block with 0 txs in the first block of the checkpoint
|
|
693
|
+
const minTxs = indexWithinCheckpoint > 0 && this.config.minTxsPerBlock === 0 ? 1 : this.config.minTxsPerBlock;
|
|
694
|
+
|
|
633
695
|
// Deadline is undefined if we are not enforcing the timetable, meaning we'll exit immediately when out of time
|
|
634
696
|
const startBuildingDeadline = buildDeadline
|
|
635
697
|
? new Date(buildDeadline.getTime() - this.timetable.minExecutionTime * 1000)
|
|
@@ -641,7 +703,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
641
703
|
// If we're past deadline, or we have no deadline, give up
|
|
642
704
|
const now = this.dateProvider.nowAsDate();
|
|
643
705
|
if (startBuildingDeadline === undefined || now >= startBuildingDeadline) {
|
|
644
|
-
return { canStartBuilding: false, availableTxs
|
|
706
|
+
return { canStartBuilding: false, availableTxs, minTxs };
|
|
645
707
|
}
|
|
646
708
|
|
|
647
709
|
// Wait a bit before checking again
|
|
@@ -650,11 +712,11 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
650
712
|
`Waiting for enough txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.slot} (have ${availableTxs} but need ${minTxs})`,
|
|
651
713
|
{ blockNumber, slot: this.slot, indexWithinCheckpoint },
|
|
652
714
|
);
|
|
653
|
-
await
|
|
715
|
+
await this.waitForTxsPollingInterval();
|
|
654
716
|
availableTxs = await this.p2pClient.getPendingTxCount();
|
|
655
717
|
}
|
|
656
718
|
|
|
657
|
-
return { canStartBuilding: true, availableTxs };
|
|
719
|
+
return { canStartBuilding: true, availableTxs, minTxs };
|
|
658
720
|
}
|
|
659
721
|
|
|
660
722
|
/**
|
|
@@ -706,11 +768,28 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
706
768
|
|
|
707
769
|
collectedAttestationsCount = attestations.length;
|
|
708
770
|
|
|
771
|
+
// Trim attestations to minimum required to save L1 calldata gas
|
|
772
|
+
const localAddresses = this.validatorClient.getValidatorAddresses();
|
|
773
|
+
const trimmed = trimAttestations(
|
|
774
|
+
attestations,
|
|
775
|
+
numberOfRequiredAttestations,
|
|
776
|
+
this.attestorAddress,
|
|
777
|
+
localAddresses,
|
|
778
|
+
);
|
|
779
|
+
if (trimmed.length < attestations.length) {
|
|
780
|
+
this.log.debug(`Trimmed attestations from ${attestations.length} to ${trimmed.length} for L1 submission`);
|
|
781
|
+
}
|
|
782
|
+
|
|
709
783
|
// Rollup contract requires that the signatures are provided in the order of the committee
|
|
710
|
-
const sorted = orderAttestations(
|
|
784
|
+
const sorted = orderAttestations(trimmed, committee);
|
|
711
785
|
|
|
712
786
|
// Manipulate the attestations if we've been configured to do so
|
|
713
|
-
if (
|
|
787
|
+
if (
|
|
788
|
+
this.config.injectFakeAttestation ||
|
|
789
|
+
this.config.injectHighSValueAttestation ||
|
|
790
|
+
this.config.injectUnrecoverableSignatureAttestation ||
|
|
791
|
+
this.config.shuffleAttestationOrdering
|
|
792
|
+
) {
|
|
714
793
|
return this.manipulateAttestations(proposal.slotNumber, epoch, seed, committee, sorted);
|
|
715
794
|
}
|
|
716
795
|
|
|
@@ -739,7 +818,11 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
739
818
|
this.epochCache.computeProposerIndex(slotNumber, epoch, seed, BigInt(committee.length)),
|
|
740
819
|
);
|
|
741
820
|
|
|
742
|
-
if (
|
|
821
|
+
if (
|
|
822
|
+
this.config.injectFakeAttestation ||
|
|
823
|
+
this.config.injectHighSValueAttestation ||
|
|
824
|
+
this.config.injectUnrecoverableSignatureAttestation
|
|
825
|
+
) {
|
|
743
826
|
// Find non-empty attestations that are not from the proposer
|
|
744
827
|
const nonProposerIndices: number[] = [];
|
|
745
828
|
for (let i = 0; i < attestations.length; i++) {
|
|
@@ -749,8 +832,20 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
749
832
|
}
|
|
750
833
|
if (nonProposerIndices.length > 0) {
|
|
751
834
|
const targetIndex = nonProposerIndices[randomInt(nonProposerIndices.length)];
|
|
752
|
-
this.
|
|
753
|
-
|
|
835
|
+
if (this.config.injectHighSValueAttestation) {
|
|
836
|
+
this.log.warn(
|
|
837
|
+
`Injecting high-s value attestation in checkpoint for slot ${slotNumber} at index ${targetIndex}`,
|
|
838
|
+
);
|
|
839
|
+
unfreeze(attestations[targetIndex]).signature = flipSignature(attestations[targetIndex].signature);
|
|
840
|
+
} else if (this.config.injectUnrecoverableSignatureAttestation) {
|
|
841
|
+
this.log.warn(
|
|
842
|
+
`Injecting unrecoverable signature attestation in checkpoint for slot ${slotNumber} at index ${targetIndex}`,
|
|
843
|
+
);
|
|
844
|
+
unfreeze(attestations[targetIndex]).signature = generateUnrecoverableSignature();
|
|
845
|
+
} else {
|
|
846
|
+
this.log.warn(`Injecting fake attestation in checkpoint for slot ${slotNumber} at index ${targetIndex}`);
|
|
847
|
+
unfreeze(attestations[targetIndex]).signature = generateRecoverableSignature();
|
|
848
|
+
}
|
|
754
849
|
}
|
|
755
850
|
return new CommitteeAttestationsAndSigners(attestations);
|
|
756
851
|
}
|
|
@@ -779,16 +874,20 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
779
874
|
const failedTxData = failedTxs.map(fail => fail.tx);
|
|
780
875
|
const failedTxHashes = failedTxData.map(tx => tx.getTxHash());
|
|
781
876
|
this.log.verbose(`Dropping failed txs ${failedTxHashes.join(', ')}`);
|
|
782
|
-
await this.p2pClient.
|
|
877
|
+
await this.p2pClient.handleFailedExecution(failedTxHashes);
|
|
783
878
|
}
|
|
784
879
|
|
|
785
880
|
/**
|
|
786
881
|
* Adds the proposed block to the archiver so it's available via P2P.
|
|
787
882
|
* Gossip doesn't echo messages back to the sender, so the proposer's archiver/world-state
|
|
788
883
|
* would never receive its own block without this explicit sync.
|
|
884
|
+
*
|
|
885
|
+
* In fisherman mode we skip this push: the fisherman builds blocks locally for validation
|
|
886
|
+
* and fee analysis only, and pushing them to the archiver causes spurious reorg cascades
|
|
887
|
+
* whenever the real proposer's block arrives from L1.
|
|
789
888
|
*/
|
|
790
889
|
private async syncProposedBlockToArchiver(block: L2Block): Promise<void> {
|
|
791
|
-
if (this.config.skipPushProposedBlocksToArchiver !== false) {
|
|
890
|
+
if (this.config.skipPushProposedBlocksToArchiver !== false || this.config.fishermanMode) {
|
|
792
891
|
this.log.warn(`Skipping push of proposed block ${block.number} to archiver`, {
|
|
793
892
|
blockNumber: block.number,
|
|
794
893
|
slot: block.header.globalVariables.slotNumber,
|
|
@@ -821,12 +920,34 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
821
920
|
slot: this.slot,
|
|
822
921
|
feeAnalysisId: feeAnalysis?.id,
|
|
823
922
|
});
|
|
824
|
-
this.metrics.
|
|
923
|
+
this.metrics.recordCheckpointProposalFailed('block_build_failed');
|
|
825
924
|
}
|
|
826
925
|
|
|
827
926
|
this.publisher.clearPendingRequests();
|
|
828
927
|
}
|
|
829
928
|
|
|
929
|
+
/**
|
|
930
|
+
* Helper to handle HA double-signing errors. Returns true if the error was handled (caller should yield).
|
|
931
|
+
*/
|
|
932
|
+
private handleHASigningError(err: any, errorContext: string): boolean {
|
|
933
|
+
if (err instanceof DutyAlreadySignedError) {
|
|
934
|
+
this.log.info(`${errorContext} for slot ${this.slot} already signed by another HA node, yielding`, {
|
|
935
|
+
slot: this.slot,
|
|
936
|
+
signedByNode: err.signedByNode,
|
|
937
|
+
});
|
|
938
|
+
return true;
|
|
939
|
+
}
|
|
940
|
+
if (err instanceof SlashingProtectionError) {
|
|
941
|
+
this.log.info(`${errorContext} for slot ${this.slot} blocked by slashing protection, yielding`, {
|
|
942
|
+
slot: this.slot,
|
|
943
|
+
existingMessageHash: err.existingMessageHash,
|
|
944
|
+
attemptedMessageHash: err.attemptedMessageHash,
|
|
945
|
+
});
|
|
946
|
+
return true;
|
|
947
|
+
}
|
|
948
|
+
return false;
|
|
949
|
+
}
|
|
950
|
+
|
|
830
951
|
/** Waits until a specific time within the current slot */
|
|
831
952
|
@trackSpan('CheckpointProposalJob.waitUntilTimeInSlot')
|
|
832
953
|
protected async waitUntilTimeInSlot(targetSecondsIntoSlot: number): Promise<void> {
|
|
@@ -835,6 +956,11 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
835
956
|
await sleepUntil(new Date(targetTimestamp * 1000), this.dateProvider.nowAsDate());
|
|
836
957
|
}
|
|
837
958
|
|
|
959
|
+
/** Waits the polling interval for transactions. Extracted for test overriding. */
|
|
960
|
+
protected async waitForTxsPollingInterval(): Promise<void> {
|
|
961
|
+
await sleep(TXS_POLLING_MS);
|
|
962
|
+
}
|
|
963
|
+
|
|
838
964
|
private getSlotStartBuildTimestamp(): number {
|
|
839
965
|
return getSlotStartBuildTimestamp(this.slot, this.l1Constants);
|
|
840
966
|
}
|