@aztec/sequencer-client 0.0.1-commit.0c875d939 → 0.0.1-commit.10bd49492
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 +23 -7
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +99 -16
- package/dest/config.d.ts +24 -5
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +36 -20
- package/dest/global_variable_builder/global_builder.d.ts +2 -4
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/publisher/config.d.ts +35 -17
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +106 -42
- package/dest/publisher/index.d.ts +2 -1
- package/dest/publisher/index.d.ts.map +1 -1
- package/dest/publisher/l1_tx_failed_store/factory.d.ts +11 -0
- package/dest/publisher/l1_tx_failed_store/factory.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/factory.js +22 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +59 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +1 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +15 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +34 -0
- package/dest/publisher/l1_tx_failed_store/index.d.ts +4 -0
- package/dest/publisher/l1_tx_failed_store/index.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/index.js +2 -0
- 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 +27 -2
- package/dest/publisher/sequencer-publisher.d.ts +19 -7
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +285 -25
- package/dest/sequencer/checkpoint_proposal_job.d.ts +2 -4
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +99 -47
- package/dest/sequencer/metrics.d.ts +14 -5
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +61 -15
- package/dest/sequencer/sequencer.d.ts +24 -13
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +35 -39
- package/dest/sequencer/timetable.d.ts +4 -3
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +6 -7
- 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 +4 -6
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +39 -30
- package/package.json +28 -28
- package/src/client/sequencer-client.ts +135 -18
- package/src/config.ts +45 -27
- package/src/global_variable_builder/global_builder.ts +1 -1
- package/src/publisher/config.ts +121 -43
- package/src/publisher/index.ts +3 -0
- package/src/publisher/l1_tx_failed_store/factory.ts +32 -0
- package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +55 -0
- package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +46 -0
- package/src/publisher/l1_tx_failed_store/index.ts +3 -0
- package/src/publisher/sequencer-publisher-factory.ts +38 -6
- package/src/publisher/sequencer-publisher.ts +273 -38
- package/src/sequencer/checkpoint_proposal_job.ts +133 -56
- package/src/sequencer/metrics.ts +68 -18
- package/src/sequencer/sequencer.ts +44 -45
- package/src/sequencer/timetable.ts +7 -7
- package/src/sequencer/types.ts +1 -1
- package/src/test/index.ts +2 -4
- package/src/test/mock_checkpoint_builder.ts +48 -45
|
@@ -1,5 +1,3 @@
|
|
|
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
2
|
import {
|
|
5
3
|
BlockNumber,
|
|
@@ -9,6 +7,11 @@ import {
|
|
|
9
7
|
SlotNumber,
|
|
10
8
|
} from '@aztec/foundation/branded-types';
|
|
11
9
|
import { randomInt } from '@aztec/foundation/crypto/random';
|
|
10
|
+
import {
|
|
11
|
+
flipSignature,
|
|
12
|
+
generateRecoverableSignature,
|
|
13
|
+
generateUnrecoverableSignature,
|
|
14
|
+
} from '@aztec/foundation/crypto/secp256k1-signer';
|
|
12
15
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
13
16
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
14
17
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
@@ -27,7 +30,7 @@ import {
|
|
|
27
30
|
type L2BlockSource,
|
|
28
31
|
MaliciousCommitteeAttestationsAndSigners,
|
|
29
32
|
} from '@aztec/stdlib/block';
|
|
30
|
-
import type
|
|
33
|
+
import { type Checkpoint, validateCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
31
34
|
import { getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
32
35
|
import { Gas } from '@aztec/stdlib/gas';
|
|
33
36
|
import {
|
|
@@ -38,7 +41,7 @@ import {
|
|
|
38
41
|
} from '@aztec/stdlib/interfaces/server';
|
|
39
42
|
import { type L1ToL2MessageSource, computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
|
|
40
43
|
import type { BlockProposalOptions, CheckpointProposal, CheckpointProposalOptions } from '@aztec/stdlib/p2p';
|
|
41
|
-
import { orderAttestations } from '@aztec/stdlib/p2p';
|
|
44
|
+
import { orderAttestations, trimAttestations } from '@aztec/stdlib/p2p';
|
|
42
45
|
import type { L2BlockBuiltStats } from '@aztec/stdlib/stats';
|
|
43
46
|
import { type FailedTx, Tx } from '@aztec/stdlib/tx';
|
|
44
47
|
import { AttestationTimeoutError } from '@aztec/stdlib/validators';
|
|
@@ -129,7 +132,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
129
132
|
await Promise.all(votesPromises);
|
|
130
133
|
|
|
131
134
|
if (checkpoint) {
|
|
132
|
-
this.metrics.
|
|
135
|
+
this.metrics.recordCheckpointProposalSuccess();
|
|
133
136
|
}
|
|
134
137
|
|
|
135
138
|
// Do not post anything to L1 if we are fishermen, but do perform L1 fee analysis
|
|
@@ -186,16 +189,15 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
186
189
|
const inHash = computeInHashFromL1ToL2Messages(l1ToL2Messages);
|
|
187
190
|
|
|
188
191
|
// Collect the out hashes of all the checkpoints before this one in the same epoch
|
|
189
|
-
const
|
|
190
|
-
c => c.
|
|
191
|
-
|
|
192
|
-
const previousCheckpointOutHashes = previousCheckpoints.map(c => c.getCheckpointOutHash());
|
|
192
|
+
const previousCheckpointOutHashes = (await this.l2BlockSource.getCheckpointsDataForEpoch(this.epoch))
|
|
193
|
+
.filter(c => c.checkpointNumber < this.checkpointNumber)
|
|
194
|
+
.map(c => c.checkpointOutHash);
|
|
193
195
|
|
|
194
196
|
// Get the fee asset price modifier from the oracle
|
|
195
197
|
const feeAssetPriceModifier = await this.publisher.getFeeAssetPriceModifier();
|
|
196
198
|
|
|
197
199
|
// Create a long-lived forked world state for the checkpoint builder
|
|
198
|
-
using fork = await this.worldState.fork(this.syncedToBlockNumber, { closeDelayMs: 12_000 });
|
|
200
|
+
await using fork = await this.worldState.fork(this.syncedToBlockNumber, { closeDelayMs: 12_000 });
|
|
199
201
|
|
|
200
202
|
// Create checkpoint builder for the entire slot
|
|
201
203
|
const checkpointBuilder = await this.checkpointsBuilder.startCheckpoint(
|
|
@@ -221,6 +223,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
221
223
|
|
|
222
224
|
let blocksInCheckpoint: L2Block[] = [];
|
|
223
225
|
let blockPendingBroadcast: { block: L2Block; txs: Tx[] } | undefined = undefined;
|
|
226
|
+
const checkpointBuildTimer = new Timer();
|
|
224
227
|
|
|
225
228
|
try {
|
|
226
229
|
// Main loop: build blocks for the checkpoint
|
|
@@ -248,11 +251,44 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
248
251
|
return undefined;
|
|
249
252
|
}
|
|
250
253
|
|
|
254
|
+
const minBlocksForCheckpoint = this.config.minBlocksForCheckpoint;
|
|
255
|
+
if (minBlocksForCheckpoint !== undefined && blocksInCheckpoint.length < minBlocksForCheckpoint) {
|
|
256
|
+
this.log.warn(
|
|
257
|
+
`Checkpoint has fewer blocks than minimum (${blocksInCheckpoint.length} < ${minBlocksForCheckpoint}), skipping proposal`,
|
|
258
|
+
{ slot: this.slot, blocksBuilt: blocksInCheckpoint.length, minBlocksForCheckpoint },
|
|
259
|
+
);
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
|
|
251
263
|
// Assemble and broadcast the checkpoint proposal, including the last block that was not
|
|
252
264
|
// broadcasted yet, and wait to collect the committee attestations.
|
|
253
265
|
this.setStateFn(SequencerState.ASSEMBLING_CHECKPOINT, this.slot);
|
|
254
266
|
const checkpoint = await checkpointBuilder.completeCheckpoint();
|
|
255
267
|
|
|
268
|
+
// Final validation round for the checkpoint before we propose it, just for safety
|
|
269
|
+
try {
|
|
270
|
+
validateCheckpoint(checkpoint, {
|
|
271
|
+
rollupManaLimit: this.l1Constants.rollupManaLimit,
|
|
272
|
+
maxL2BlockGas: this.config.maxL2BlockGas,
|
|
273
|
+
maxDABlockGas: this.config.maxDABlockGas,
|
|
274
|
+
maxTxsPerBlock: this.config.maxTxsPerBlock,
|
|
275
|
+
maxTxsPerCheckpoint: this.config.maxTxsPerCheckpoint,
|
|
276
|
+
});
|
|
277
|
+
} catch (err) {
|
|
278
|
+
this.log.error(`Built an invalid checkpoint at slot ${this.slot} (skipping proposal)`, err, {
|
|
279
|
+
checkpoint: checkpoint.header.toInspect(),
|
|
280
|
+
});
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Record checkpoint-level build metrics
|
|
285
|
+
this.metrics.recordCheckpointBuild(
|
|
286
|
+
checkpointBuildTimer.ms(),
|
|
287
|
+
blocksInCheckpoint.length,
|
|
288
|
+
checkpoint.getStats().txCount,
|
|
289
|
+
Number(checkpoint.header.totalManaUsed.toBigInt()),
|
|
290
|
+
);
|
|
291
|
+
|
|
256
292
|
// Do not collect attestations nor publish to L1 in fisherman mode
|
|
257
293
|
if (this.config.fishermanMode) {
|
|
258
294
|
this.log.info(
|
|
@@ -318,6 +354,21 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
318
354
|
const aztecSlotDuration = this.l1Constants.slotDuration;
|
|
319
355
|
const slotStartBuildTimestamp = this.getSlotStartBuildTimestamp();
|
|
320
356
|
const txTimeoutAt = new Date((slotStartBuildTimestamp + aztecSlotDuration) * 1000);
|
|
357
|
+
|
|
358
|
+
// If we have been configured to potentially skip publishing checkpoint then roll the dice here
|
|
359
|
+
if (
|
|
360
|
+
this.config.skipPublishingCheckpointsPercent !== undefined &&
|
|
361
|
+
this.config.skipPublishingCheckpointsPercent > 0
|
|
362
|
+
) {
|
|
363
|
+
const result = Math.max(0, randomInt(100));
|
|
364
|
+
if (result < this.config.skipPublishingCheckpointsPercent) {
|
|
365
|
+
this.log.warn(
|
|
366
|
+
`Skipping publishing proposal for checkpoint ${checkpoint.number}. Configured percentage: ${this.config.skipPublishingCheckpointsPercent}, generated value: ${result}`,
|
|
367
|
+
);
|
|
368
|
+
return checkpoint;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
321
372
|
await this.publisher.enqueueProposeCheckpoint(checkpoint, attestations, attestationsSignature, {
|
|
322
373
|
txTimeoutAt,
|
|
323
374
|
forcePendingCheckpointNumber: this.invalidateCheckpoint?.forcePendingCheckpointNumber,
|
|
@@ -352,9 +403,6 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
352
403
|
const txHashesAlreadyIncluded = new Set<string>();
|
|
353
404
|
const initialBlockNumber = BlockNumber(this.syncedToBlockNumber + 1);
|
|
354
405
|
|
|
355
|
-
// Remaining blob fields available for blocks (checkpoint end marker already subtracted)
|
|
356
|
-
let remainingBlobFields = BLOBS_PER_CHECKPOINT * FIELDS_PER_BLOB - NUM_CHECKPOINT_END_MARKER_FIELDS;
|
|
357
|
-
|
|
358
406
|
// Last block in the checkpoint will usually be flagged as pending broadcast, so we send it along with the checkpoint proposal
|
|
359
407
|
let blockPendingBroadcast: { block: L2Block; txs: Tx[] } | undefined = undefined;
|
|
360
408
|
|
|
@@ -387,7 +435,6 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
387
435
|
blockNumber,
|
|
388
436
|
indexWithinCheckpoint,
|
|
389
437
|
txHashesAlreadyIncluded,
|
|
390
|
-
remainingBlobFields,
|
|
391
438
|
});
|
|
392
439
|
|
|
393
440
|
// TODO(palla/mbps): Review these conditions. We may want to keep trying in some scenarios.
|
|
@@ -413,12 +460,9 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
413
460
|
break;
|
|
414
461
|
}
|
|
415
462
|
|
|
416
|
-
const { block, usedTxs
|
|
463
|
+
const { block, usedTxs } = buildResult;
|
|
417
464
|
blocksInCheckpoint.push(block);
|
|
418
465
|
|
|
419
|
-
// Update remaining blob fields for the next block
|
|
420
|
-
remainingBlobFields = newRemainingBlobFields;
|
|
421
|
-
|
|
422
466
|
// Sync the proposed block to the archiver to make it available
|
|
423
467
|
// Note that the checkpoint builder uses its own fork so it should not need to wait for this syncing
|
|
424
468
|
// Eventually we should refactor the checkpoint builder to not need a separate long-lived fork
|
|
@@ -486,18 +530,10 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
486
530
|
indexWithinCheckpoint: IndexWithinCheckpoint;
|
|
487
531
|
buildDeadline: Date | undefined;
|
|
488
532
|
txHashesAlreadyIncluded: Set<string>;
|
|
489
|
-
remainingBlobFields: number;
|
|
490
533
|
},
|
|
491
|
-
): Promise<{ block: L2Block; usedTxs: Tx[]
|
|
492
|
-
const {
|
|
493
|
-
|
|
494
|
-
forceCreate,
|
|
495
|
-
blockNumber,
|
|
496
|
-
indexWithinCheckpoint,
|
|
497
|
-
buildDeadline,
|
|
498
|
-
txHashesAlreadyIncluded,
|
|
499
|
-
remainingBlobFields,
|
|
500
|
-
} = opts;
|
|
534
|
+
): Promise<{ block: L2Block; usedTxs: Tx[] } | { error: Error } | undefined> {
|
|
535
|
+
const { blockTimestamp, forceCreate, blockNumber, indexWithinCheckpoint, buildDeadline, txHashesAlreadyIncluded } =
|
|
536
|
+
opts;
|
|
501
537
|
|
|
502
538
|
this.log.verbose(
|
|
503
539
|
`Preparing block ${blockNumber} index ${indexWithinCheckpoint} at checkpoint ${this.checkpointNumber} for slot ${this.slot}`,
|
|
@@ -506,8 +542,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
506
542
|
|
|
507
543
|
try {
|
|
508
544
|
// Wait until we have enough txs to build the block
|
|
509
|
-
const minTxs = this.
|
|
510
|
-
const { availableTxs, canStartBuilding } = await this.waitForMinTxs(opts);
|
|
545
|
+
const { availableTxs, canStartBuilding, minTxs } = await this.waitForMinTxs(opts);
|
|
511
546
|
if (!canStartBuilding) {
|
|
512
547
|
this.log.warn(
|
|
513
548
|
`Not enough txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.slot} (got ${availableTxs} txs but needs ${minTxs})`,
|
|
@@ -531,16 +566,16 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
531
566
|
);
|
|
532
567
|
this.setStateFn(SequencerState.CREATING_BLOCK, this.slot);
|
|
533
568
|
|
|
534
|
-
//
|
|
535
|
-
|
|
536
|
-
const maxBlobFieldsForTxs = remainingBlobFields - blockEndOverhead;
|
|
537
|
-
|
|
569
|
+
// Per-block limits derived at startup by computeBlockLimits(), further capped
|
|
570
|
+
// by remaining checkpoint-level budgets inside CheckpointBuilder before each block is built.
|
|
538
571
|
const blockBuilderOptions: PublicProcessorLimits = {
|
|
539
572
|
maxTransactions: this.config.maxTxsPerBlock,
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
573
|
+
maxBlockGas:
|
|
574
|
+
this.config.maxL2BlockGas !== undefined || this.config.maxDABlockGas !== undefined
|
|
575
|
+
? new Gas(this.config.maxDABlockGas ?? Infinity, this.config.maxL2BlockGas ?? Infinity)
|
|
576
|
+
: undefined,
|
|
543
577
|
deadline: buildDeadline,
|
|
578
|
+
isBuildingProposal: true,
|
|
544
579
|
};
|
|
545
580
|
|
|
546
581
|
// Actually build the block by executing txs
|
|
@@ -570,7 +605,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
570
605
|
}
|
|
571
606
|
|
|
572
607
|
// Block creation succeeded, emit stats and metrics
|
|
573
|
-
const {
|
|
608
|
+
const { block, publicProcessorDuration, usedTxs, blockBuildDuration } = buildResult;
|
|
574
609
|
|
|
575
610
|
const blockStats = {
|
|
576
611
|
eventName: 'l2-block-built',
|
|
@@ -581,7 +616,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
581
616
|
|
|
582
617
|
const blockHash = await block.hash();
|
|
583
618
|
const txHashes = block.body.txEffects.map(tx => tx.txHash);
|
|
584
|
-
const manaPerSec =
|
|
619
|
+
const manaPerSec = block.header.totalManaUsed.toNumberUnsafe() / (blockBuildDuration / 1000);
|
|
585
620
|
|
|
586
621
|
this.log.info(
|
|
587
622
|
`Built block ${block.number} at checkpoint ${this.checkpointNumber} for slot ${this.slot} with ${numTxs} txs`,
|
|
@@ -589,9 +624,9 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
589
624
|
);
|
|
590
625
|
|
|
591
626
|
this.eventEmitter.emit('block-proposed', { blockNumber: block.number, slot: this.slot });
|
|
592
|
-
this.metrics.recordBuiltBlock(blockBuildDuration,
|
|
627
|
+
this.metrics.recordBuiltBlock(blockBuildDuration, block.header.totalManaUsed.toNumberUnsafe());
|
|
593
628
|
|
|
594
|
-
return { block, usedTxs
|
|
629
|
+
return { block, usedTxs };
|
|
595
630
|
} catch (err: any) {
|
|
596
631
|
this.eventEmitter.emit('block-build-failed', { reason: err.message, slot: this.slot });
|
|
597
632
|
this.log.error(`Error building block`, err, { blockNumber, slot: this.slot });
|
|
@@ -629,7 +664,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
629
664
|
blockNumber: BlockNumber;
|
|
630
665
|
indexWithinCheckpoint: IndexWithinCheckpoint;
|
|
631
666
|
buildDeadline: Date | undefined;
|
|
632
|
-
}): Promise<{ canStartBuilding: boolean; availableTxs: number }> {
|
|
667
|
+
}): Promise<{ canStartBuilding: boolean; availableTxs: number; minTxs: number }> {
|
|
633
668
|
const { indexWithinCheckpoint, blockNumber, buildDeadline, forceCreate } = opts;
|
|
634
669
|
|
|
635
670
|
// We only allow a block with 0 txs in the first block of the checkpoint
|
|
@@ -646,7 +681,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
646
681
|
// If we're past deadline, or we have no deadline, give up
|
|
647
682
|
const now = this.dateProvider.nowAsDate();
|
|
648
683
|
if (startBuildingDeadline === undefined || now >= startBuildingDeadline) {
|
|
649
|
-
return { canStartBuilding: false, availableTxs
|
|
684
|
+
return { canStartBuilding: false, availableTxs, minTxs };
|
|
650
685
|
}
|
|
651
686
|
|
|
652
687
|
// Wait a bit before checking again
|
|
@@ -659,7 +694,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
659
694
|
availableTxs = await this.p2pClient.getPendingTxCount();
|
|
660
695
|
}
|
|
661
696
|
|
|
662
|
-
return { canStartBuilding: true, availableTxs };
|
|
697
|
+
return { canStartBuilding: true, availableTxs, minTxs };
|
|
663
698
|
}
|
|
664
699
|
|
|
665
700
|
/**
|
|
@@ -711,11 +746,28 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
711
746
|
|
|
712
747
|
collectedAttestationsCount = attestations.length;
|
|
713
748
|
|
|
749
|
+
// Trim attestations to minimum required to save L1 calldata gas
|
|
750
|
+
const localAddresses = this.validatorClient.getValidatorAddresses();
|
|
751
|
+
const trimmed = trimAttestations(
|
|
752
|
+
attestations,
|
|
753
|
+
numberOfRequiredAttestations,
|
|
754
|
+
this.attestorAddress,
|
|
755
|
+
localAddresses,
|
|
756
|
+
);
|
|
757
|
+
if (trimmed.length < attestations.length) {
|
|
758
|
+
this.log.debug(`Trimmed attestations from ${attestations.length} to ${trimmed.length} for L1 submission`);
|
|
759
|
+
}
|
|
760
|
+
|
|
714
761
|
// Rollup contract requires that the signatures are provided in the order of the committee
|
|
715
|
-
const sorted = orderAttestations(
|
|
762
|
+
const sorted = orderAttestations(trimmed, committee);
|
|
716
763
|
|
|
717
764
|
// Manipulate the attestations if we've been configured to do so
|
|
718
|
-
if (
|
|
765
|
+
if (
|
|
766
|
+
this.config.injectFakeAttestation ||
|
|
767
|
+
this.config.injectHighSValueAttestation ||
|
|
768
|
+
this.config.injectUnrecoverableSignatureAttestation ||
|
|
769
|
+
this.config.shuffleAttestationOrdering
|
|
770
|
+
) {
|
|
719
771
|
return this.manipulateAttestations(proposal.slotNumber, epoch, seed, committee, sorted);
|
|
720
772
|
}
|
|
721
773
|
|
|
@@ -744,7 +796,11 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
744
796
|
this.epochCache.computeProposerIndex(slotNumber, epoch, seed, BigInt(committee.length)),
|
|
745
797
|
);
|
|
746
798
|
|
|
747
|
-
if (
|
|
799
|
+
if (
|
|
800
|
+
this.config.injectFakeAttestation ||
|
|
801
|
+
this.config.injectHighSValueAttestation ||
|
|
802
|
+
this.config.injectUnrecoverableSignatureAttestation
|
|
803
|
+
) {
|
|
748
804
|
// Find non-empty attestations that are not from the proposer
|
|
749
805
|
const nonProposerIndices: number[] = [];
|
|
750
806
|
for (let i = 0; i < attestations.length; i++) {
|
|
@@ -754,8 +810,20 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
754
810
|
}
|
|
755
811
|
if (nonProposerIndices.length > 0) {
|
|
756
812
|
const targetIndex = nonProposerIndices[randomInt(nonProposerIndices.length)];
|
|
757
|
-
this.
|
|
758
|
-
|
|
813
|
+
if (this.config.injectHighSValueAttestation) {
|
|
814
|
+
this.log.warn(
|
|
815
|
+
`Injecting high-s value attestation in checkpoint for slot ${slotNumber} at index ${targetIndex}`,
|
|
816
|
+
);
|
|
817
|
+
unfreeze(attestations[targetIndex]).signature = flipSignature(attestations[targetIndex].signature);
|
|
818
|
+
} else if (this.config.injectUnrecoverableSignatureAttestation) {
|
|
819
|
+
this.log.warn(
|
|
820
|
+
`Injecting unrecoverable signature attestation in checkpoint for slot ${slotNumber} at index ${targetIndex}`,
|
|
821
|
+
);
|
|
822
|
+
unfreeze(attestations[targetIndex]).signature = generateUnrecoverableSignature();
|
|
823
|
+
} else {
|
|
824
|
+
this.log.warn(`Injecting fake attestation in checkpoint for slot ${slotNumber} at index ${targetIndex}`);
|
|
825
|
+
unfreeze(attestations[targetIndex]).signature = generateRecoverableSignature();
|
|
826
|
+
}
|
|
759
827
|
}
|
|
760
828
|
return new CommitteeAttestationsAndSigners(attestations);
|
|
761
829
|
}
|
|
@@ -764,11 +832,20 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
764
832
|
this.log.warn(`Shuffling attestation ordering in checkpoint for slot ${slotNumber} (proposer #${proposerIndex})`);
|
|
765
833
|
|
|
766
834
|
const shuffled = [...attestations];
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
835
|
+
|
|
836
|
+
// Find two non-proposer positions that both have non-empty signatures to swap.
|
|
837
|
+
// This ensures the bitmap doesn't change, so the MaliciousCommitteeAttestationsAndSigners
|
|
838
|
+
// signers array stays correctly aligned with L1's committee reconstruction.
|
|
839
|
+
const swappable: number[] = [];
|
|
840
|
+
for (let k = 0; k < shuffled.length; k++) {
|
|
841
|
+
if (!shuffled[k].signature.isEmpty() && k !== proposerIndex) {
|
|
842
|
+
swappable.push(k);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
if (swappable.length >= 2) {
|
|
846
|
+
const [i, j] = [swappable[0], swappable[1]];
|
|
847
|
+
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
848
|
+
}
|
|
772
849
|
|
|
773
850
|
const signers = new CommitteeAttestationsAndSigners(attestations).getSigners();
|
|
774
851
|
return new MaliciousCommitteeAttestationsAndSigners(shuffled, signers);
|
|
@@ -826,7 +903,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
826
903
|
slot: this.slot,
|
|
827
904
|
feeAnalysisId: feeAnalysis?.id,
|
|
828
905
|
});
|
|
829
|
-
this.metrics.
|
|
906
|
+
this.metrics.recordCheckpointProposalFailed('block_build_failed');
|
|
830
907
|
}
|
|
831
908
|
|
|
832
909
|
this.publisher.clearPendingRequests();
|
package/src/sequencer/metrics.ts
CHANGED
|
@@ -18,7 +18,6 @@ import { type Hex, formatUnits } from 'viem';
|
|
|
18
18
|
|
|
19
19
|
import type { SequencerState } from './utils.js';
|
|
20
20
|
|
|
21
|
-
// TODO(palla/mbps): Review all metrics and add any missing ones per checkpoint
|
|
22
21
|
export class SequencerMetrics {
|
|
23
22
|
public readonly tracer: Tracer;
|
|
24
23
|
private meter: Meter;
|
|
@@ -40,11 +39,16 @@ export class SequencerMetrics {
|
|
|
40
39
|
private filledSlots: UpDownCounter;
|
|
41
40
|
|
|
42
41
|
private blockProposalFailed: UpDownCounter;
|
|
43
|
-
private
|
|
44
|
-
private
|
|
42
|
+
private checkpointProposalSuccess: UpDownCounter;
|
|
43
|
+
private checkpointPrecheckFailed: UpDownCounter;
|
|
44
|
+
private checkpointProposalFailed: UpDownCounter;
|
|
45
45
|
private checkpointSuccess: UpDownCounter;
|
|
46
46
|
private slashingAttempts: UpDownCounter;
|
|
47
47
|
private checkpointAttestationDelay: Histogram;
|
|
48
|
+
private checkpointBuildDuration: Histogram;
|
|
49
|
+
private checkpointBlockCount: Gauge;
|
|
50
|
+
private checkpointTxCount: Gauge;
|
|
51
|
+
private checkpointTotalMana: Gauge;
|
|
48
52
|
|
|
49
53
|
// Fisherman fee analysis metrics
|
|
50
54
|
private fishermanWouldBeIncluded: UpDownCounter;
|
|
@@ -54,6 +58,7 @@ export class SequencerMetrics {
|
|
|
54
58
|
private fishermanPendingBlobCount: Histogram;
|
|
55
59
|
private fishermanIncludedBlobCount: Histogram;
|
|
56
60
|
private fishermanBlockBlobsFull: UpDownCounter;
|
|
61
|
+
private fishermanMaxBlobCapacity: Histogram;
|
|
57
62
|
private fishermanCalculatedPriorityFee: Histogram;
|
|
58
63
|
private fishermanPriorityFeeDelta: Histogram;
|
|
59
64
|
private fishermanEstimatedCost: Histogram;
|
|
@@ -83,7 +88,7 @@ export class SequencerMetrics {
|
|
|
83
88
|
|
|
84
89
|
this.checkpointAttestationDelay = this.meter.createHistogram(Metrics.SEQUENCER_CHECKPOINT_ATTESTATION_DELAY);
|
|
85
90
|
|
|
86
|
-
this.rewards = this.meter.createGauge(Metrics.
|
|
91
|
+
this.rewards = this.meter.createGauge(Metrics.SEQUENCER_CURRENT_SLOT_REWARDS);
|
|
87
92
|
|
|
88
93
|
this.slots = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_SLOT_COUNT);
|
|
89
94
|
|
|
@@ -106,16 +111,16 @@ export class SequencerMetrics {
|
|
|
106
111
|
Metrics.SEQUENCER_BLOCK_PROPOSAL_FAILED_COUNT,
|
|
107
112
|
);
|
|
108
113
|
|
|
109
|
-
this.
|
|
114
|
+
this.checkpointProposalSuccess = createUpDownCounterWithDefault(
|
|
110
115
|
this.meter,
|
|
111
|
-
Metrics.
|
|
116
|
+
Metrics.SEQUENCER_CHECKPOINT_PROPOSAL_SUCCESS_COUNT,
|
|
112
117
|
);
|
|
113
118
|
|
|
114
119
|
this.checkpointSuccess = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_CHECKPOINT_SUCCESS_COUNT);
|
|
115
120
|
|
|
116
|
-
this.
|
|
121
|
+
this.checkpointPrecheckFailed = createUpDownCounterWithDefault(
|
|
117
122
|
this.meter,
|
|
118
|
-
Metrics.
|
|
123
|
+
Metrics.SEQUENCER_CHECKPOINT_PRECHECK_FAILED_COUNT,
|
|
119
124
|
{
|
|
120
125
|
[Attributes.ERROR_TYPE]: [
|
|
121
126
|
'slot_already_taken',
|
|
@@ -126,6 +131,16 @@ export class SequencerMetrics {
|
|
|
126
131
|
},
|
|
127
132
|
);
|
|
128
133
|
|
|
134
|
+
this.checkpointProposalFailed = createUpDownCounterWithDefault(
|
|
135
|
+
this.meter,
|
|
136
|
+
Metrics.SEQUENCER_CHECKPOINT_PROPOSAL_FAILED_COUNT,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
this.checkpointBuildDuration = this.meter.createHistogram(Metrics.SEQUENCER_CHECKPOINT_BUILD_DURATION);
|
|
140
|
+
this.checkpointBlockCount = this.meter.createGauge(Metrics.SEQUENCER_CHECKPOINT_BLOCK_COUNT);
|
|
141
|
+
this.checkpointTxCount = this.meter.createGauge(Metrics.SEQUENCER_CHECKPOINT_TX_COUNT);
|
|
142
|
+
this.checkpointTotalMana = this.meter.createGauge(Metrics.SEQUENCER_CHECKPOINT_TOTAL_MANA);
|
|
143
|
+
|
|
129
144
|
this.slashingAttempts = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_SLASHING_ATTEMPTS_COUNT);
|
|
130
145
|
|
|
131
146
|
// Fisherman fee analysis metrics
|
|
@@ -134,6 +149,7 @@ export class SequencerMetrics {
|
|
|
134
149
|
Metrics.FISHERMAN_FEE_ANALYSIS_WOULD_BE_INCLUDED,
|
|
135
150
|
{
|
|
136
151
|
[Attributes.OK]: [true, false],
|
|
152
|
+
[Attributes.BLOCK_FULL]: ['true', 'false'],
|
|
137
153
|
},
|
|
138
154
|
);
|
|
139
155
|
|
|
@@ -176,6 +192,8 @@ export class SequencerMetrics {
|
|
|
176
192
|
[Attributes.OK]: [true, false],
|
|
177
193
|
},
|
|
178
194
|
);
|
|
195
|
+
|
|
196
|
+
this.fishermanMaxBlobCapacity = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_MAX_BLOB_CAPACITY);
|
|
179
197
|
}
|
|
180
198
|
|
|
181
199
|
public recordRequiredAttestations(requiredAttestationsCount: number, allowanceMs: number) {
|
|
@@ -258,18 +276,30 @@ export class SequencerMetrics {
|
|
|
258
276
|
});
|
|
259
277
|
}
|
|
260
278
|
|
|
261
|
-
|
|
262
|
-
this.
|
|
279
|
+
recordCheckpointProposalSuccess() {
|
|
280
|
+
this.checkpointProposalSuccess.add(1);
|
|
263
281
|
}
|
|
264
282
|
|
|
265
|
-
|
|
283
|
+
recordCheckpointPrecheckFailed(
|
|
266
284
|
checkType: 'slot_already_taken' | 'rollup_contract_check_failed' | 'slot_mismatch' | 'block_number_mismatch',
|
|
267
285
|
) {
|
|
268
|
-
this.
|
|
269
|
-
|
|
286
|
+
this.checkpointPrecheckFailed.add(1, { [Attributes.ERROR_TYPE]: checkType });
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
recordCheckpointProposalFailed(reason?: string) {
|
|
290
|
+
this.checkpointProposalFailed.add(1, {
|
|
291
|
+
...(reason && { [Attributes.ERROR_TYPE]: reason }),
|
|
270
292
|
});
|
|
271
293
|
}
|
|
272
294
|
|
|
295
|
+
/** Records aggregate metrics for a completed checkpoint build. */
|
|
296
|
+
recordCheckpointBuild(durationMs: number, blockCount: number, txCount: number, totalMana: number) {
|
|
297
|
+
this.checkpointBuildDuration.record(Math.ceil(durationMs));
|
|
298
|
+
this.checkpointBlockCount.record(blockCount);
|
|
299
|
+
this.checkpointTxCount.record(txCount);
|
|
300
|
+
this.checkpointTotalMana.record(totalMana);
|
|
301
|
+
}
|
|
302
|
+
|
|
273
303
|
recordSlashingAttempt(actionCount: number) {
|
|
274
304
|
this.slashingAttempts.add(actionCount);
|
|
275
305
|
}
|
|
@@ -342,13 +372,21 @@ export class SequencerMetrics {
|
|
|
342
372
|
this.fishermanBlockBlobsFull.add(1, { ...strategyAttributes, [Attributes.OK]: false });
|
|
343
373
|
}
|
|
344
374
|
|
|
375
|
+
// Record the max blob capacity for this block
|
|
376
|
+
this.fishermanMaxBlobCapacity.record(analysis.analysis.maxBlobCapacity, strategyAttributes);
|
|
377
|
+
|
|
345
378
|
// Record strategy-specific inclusion result
|
|
346
379
|
if (strategyResult.wouldBeIncluded !== undefined) {
|
|
380
|
+
const inclusionAttributes = {
|
|
381
|
+
...strategyAttributes,
|
|
382
|
+
[Attributes.BLOCK_FULL]: analysis.analysis.blockBlobsFull ? 'true' : 'false',
|
|
383
|
+
};
|
|
384
|
+
|
|
347
385
|
if (strategyResult.wouldBeIncluded) {
|
|
348
|
-
this.fishermanWouldBeIncluded.add(1, { ...
|
|
386
|
+
this.fishermanWouldBeIncluded.add(1, { ...inclusionAttributes, [Attributes.OK]: true });
|
|
349
387
|
} else {
|
|
350
388
|
this.fishermanWouldBeIncluded.add(1, {
|
|
351
|
-
...
|
|
389
|
+
...inclusionAttributes,
|
|
352
390
|
[Attributes.OK]: false,
|
|
353
391
|
...(strategyResult.exclusionReason && { [Attributes.ERROR_TYPE]: strategyResult.exclusionReason }),
|
|
354
392
|
});
|
|
@@ -358,17 +396,29 @@ export class SequencerMetrics {
|
|
|
358
396
|
// Record strategy-specific priority fee delta
|
|
359
397
|
if (strategyResult.priorityFeeDelta !== undefined) {
|
|
360
398
|
const priorityFeeDeltaGwei = Number(strategyResult.priorityFeeDelta) / 1e9;
|
|
361
|
-
|
|
399
|
+
const deltaAttributes = {
|
|
400
|
+
...strategyAttributes,
|
|
401
|
+
[Attributes.BLOCK_FULL]: analysis.analysis.blockBlobsFull ? 'true' : 'false',
|
|
402
|
+
};
|
|
403
|
+
this.fishermanPriorityFeeDelta.record(priorityFeeDeltaGwei, deltaAttributes);
|
|
362
404
|
}
|
|
363
405
|
|
|
364
406
|
// Record estimated cost if available
|
|
365
407
|
if (strategyResult.estimatedCostEth !== undefined) {
|
|
366
|
-
|
|
408
|
+
const costAttributes = {
|
|
409
|
+
...strategyAttributes,
|
|
410
|
+
[Attributes.BLOCK_FULL]: analysis.analysis.blockBlobsFull ? 'true' : 'false',
|
|
411
|
+
};
|
|
412
|
+
this.fishermanEstimatedCost.record(strategyResult.estimatedCostEth, costAttributes);
|
|
367
413
|
}
|
|
368
414
|
|
|
369
415
|
// Record estimated overpayment if available
|
|
370
416
|
if (strategyResult.estimatedOverpaymentEth !== undefined) {
|
|
371
|
-
|
|
417
|
+
const overpaymentAttributes = {
|
|
418
|
+
...strategyAttributes,
|
|
419
|
+
[Attributes.BLOCK_FULL]: analysis.analysis.blockBlobsFull ? 'true' : 'false',
|
|
420
|
+
};
|
|
421
|
+
this.fishermanEstimatedOverpayment.record(strategyResult.estimatedOverpaymentEth, overpaymentAttributes);
|
|
372
422
|
}
|
|
373
423
|
}
|
|
374
424
|
}
|