@aztec/sequencer-client 0.0.1-commit.4ad48494d → 0.0.1-commit.4eabbdb
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 +12 -7
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +15 -4
- package/dest/config.d.ts +3 -3
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +13 -4
- 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 +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 +16 -7
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +41 -21
- package/dest/sequencer/checkpoint_proposal_job.d.ts +1 -1
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +30 -8
- 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 +11 -7
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +18 -25
- 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 +5 -3
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +6 -4
- package/dest/test/utils.d.ts +3 -3
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +4 -4
- package/package.json +28 -28
- package/src/client/sequencer-client.ts +25 -7
- package/src/config.ts +17 -8
- package/src/global_variable_builder/global_builder.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 +63 -28
- package/src/sequencer/checkpoint_proposal_job.ts +44 -7
- package/src/sequencer/metrics.ts +68 -18
- package/src/sequencer/sequencer.ts +24 -30
- package/src/test/index.ts +2 -4
- package/src/test/mock_checkpoint_builder.ts +12 -1
- package/src/test/utils.ts +4 -2
|
@@ -4,6 +4,7 @@ import type { EpochCache } from '@aztec/epoch-cache';
|
|
|
4
4
|
import type { L1ContractsConfig } from '@aztec/ethereum/config';
|
|
5
5
|
import {
|
|
6
6
|
type EmpireSlashingProposerContract,
|
|
7
|
+
FeeAssetPriceOracle,
|
|
7
8
|
type GovernanceProposerContract,
|
|
8
9
|
type IEmpireBase,
|
|
9
10
|
MULTI_CALL_3_ADDRESS,
|
|
@@ -18,11 +19,11 @@ import {
|
|
|
18
19
|
type L1BlobInputs,
|
|
19
20
|
type L1TxConfig,
|
|
20
21
|
type L1TxRequest,
|
|
22
|
+
type L1TxUtils,
|
|
21
23
|
MAX_L1_TX_LIMIT,
|
|
22
24
|
type TransactionStats,
|
|
23
25
|
WEI_CONST,
|
|
24
26
|
} from '@aztec/ethereum/l1-tx-utils';
|
|
25
|
-
import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs';
|
|
26
27
|
import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
27
28
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
28
29
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
@@ -32,6 +33,7 @@ import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
|
32
33
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
33
34
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
34
35
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
36
|
+
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
35
37
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
36
38
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
37
39
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
@@ -45,7 +47,7 @@ import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from
|
|
|
45
47
|
|
|
46
48
|
import { type StateOverride, type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
|
|
47
49
|
|
|
48
|
-
import type {
|
|
50
|
+
import type { SequencerPublisherConfig } from './config.js';
|
|
49
51
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
50
52
|
|
|
51
53
|
/** Arguments to the process method of the rollup contract */
|
|
@@ -60,6 +62,8 @@ type L1ProcessArgs = {
|
|
|
60
62
|
attestationsAndSigners: CommitteeAttestationsAndSigners;
|
|
61
63
|
/** Attestations and signers signature */
|
|
62
64
|
attestationsAndSignersSignature: Signature;
|
|
65
|
+
/** The fee asset price modifier in basis points (from oracle) */
|
|
66
|
+
feeAssetPriceModifier: bigint;
|
|
63
67
|
};
|
|
64
68
|
|
|
65
69
|
export const Actions = [
|
|
@@ -112,6 +116,7 @@ export class SequencerPublisher {
|
|
|
112
116
|
protected lastActions: Partial<Record<Action, SlotNumber>> = {};
|
|
113
117
|
|
|
114
118
|
private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
|
|
119
|
+
private payloadProposedCache: Set<string> = new Set<string>();
|
|
115
120
|
|
|
116
121
|
protected log: Logger;
|
|
117
122
|
protected ethereumSlotDuration: bigint;
|
|
@@ -123,13 +128,17 @@ export class SequencerPublisher {
|
|
|
123
128
|
|
|
124
129
|
/** L1 fee analyzer for fisherman mode */
|
|
125
130
|
private l1FeeAnalyzer?: L1FeeAnalyzer;
|
|
131
|
+
|
|
132
|
+
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */
|
|
133
|
+
private feeAssetPriceOracle: FeeAssetPriceOracle;
|
|
134
|
+
|
|
126
135
|
// A CALL to a cold address is 2700 gas
|
|
127
136
|
public static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
128
137
|
|
|
129
138
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
130
139
|
public static VOTE_GAS_GUESS: bigint = 800_000n;
|
|
131
140
|
|
|
132
|
-
public l1TxUtils:
|
|
141
|
+
public l1TxUtils: L1TxUtils;
|
|
133
142
|
public rollupContract: RollupContract;
|
|
134
143
|
public govProposerContract: GovernanceProposerContract;
|
|
135
144
|
public slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
@@ -140,11 +149,12 @@ export class SequencerPublisher {
|
|
|
140
149
|
protected requests: RequestWithExpiry[] = [];
|
|
141
150
|
|
|
142
151
|
constructor(
|
|
143
|
-
private config:
|
|
152
|
+
private config: Pick<SequencerPublisherConfig, 'fishermanMode'> &
|
|
153
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration'> & { l1ChainId: number },
|
|
144
154
|
deps: {
|
|
145
155
|
telemetry?: TelemetryClient;
|
|
146
156
|
blobClient: BlobClientInterface;
|
|
147
|
-
l1TxUtils:
|
|
157
|
+
l1TxUtils: L1TxUtils;
|
|
148
158
|
rollupContract: RollupContract;
|
|
149
159
|
slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
150
160
|
governanceProposerContract: GovernanceProposerContract;
|
|
@@ -188,12 +198,27 @@ export class SequencerPublisher {
|
|
|
188
198
|
createLogger('sequencer:publisher:fee-analyzer'),
|
|
189
199
|
);
|
|
190
200
|
}
|
|
201
|
+
|
|
202
|
+
// Initialize fee asset price oracle
|
|
203
|
+
this.feeAssetPriceOracle = new FeeAssetPriceOracle(
|
|
204
|
+
this.l1TxUtils.client,
|
|
205
|
+
this.rollupContract,
|
|
206
|
+
createLogger('sequencer:publisher:price-oracle'),
|
|
207
|
+
);
|
|
191
208
|
}
|
|
192
209
|
|
|
193
210
|
public getRollupContract(): RollupContract {
|
|
194
211
|
return this.rollupContract;
|
|
195
212
|
}
|
|
196
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Gets the fee asset price modifier from the oracle.
|
|
216
|
+
* Returns 0n if the oracle query fails.
|
|
217
|
+
*/
|
|
218
|
+
public getFeeAssetPriceModifier(): Promise<bigint> {
|
|
219
|
+
return this.feeAssetPriceOracle.computePriceModifier();
|
|
220
|
+
}
|
|
221
|
+
|
|
197
222
|
public getSenderAddress() {
|
|
198
223
|
return this.l1TxUtils.getSenderAddress();
|
|
199
224
|
}
|
|
@@ -617,24 +642,8 @@ export class SequencerPublisher {
|
|
|
617
642
|
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
618
643
|
): Promise<bigint> {
|
|
619
644
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
620
|
-
|
|
621
|
-
// TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
|
|
622
|
-
// If we have no attestations, we still need to provide the empty attestations
|
|
623
|
-
// so that the committee is recalculated correctly
|
|
624
|
-
// const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
625
|
-
// if (ignoreSignatures) {
|
|
626
|
-
// const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
|
|
627
|
-
// if (!committee) {
|
|
628
|
-
// this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
629
|
-
// throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
630
|
-
// }
|
|
631
|
-
// attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
632
|
-
// CommitteeAttestation.fromAddress(committeeMember),
|
|
633
|
-
// );
|
|
634
|
-
// }
|
|
635
|
-
|
|
636
645
|
const blobFields = checkpoint.toBlobFields();
|
|
637
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
646
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
638
647
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
639
648
|
|
|
640
649
|
const args = [
|
|
@@ -642,7 +651,7 @@ export class SequencerPublisher {
|
|
|
642
651
|
header: checkpoint.header.toViem(),
|
|
643
652
|
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
644
653
|
oracleInput: {
|
|
645
|
-
feeAssetPriceModifier:
|
|
654
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
646
655
|
},
|
|
647
656
|
},
|
|
648
657
|
attestationsAndSigners.getPackedAttestations(),
|
|
@@ -691,6 +700,32 @@ export class SequencerPublisher {
|
|
|
691
700
|
return false;
|
|
692
701
|
}
|
|
693
702
|
|
|
703
|
+
// Check if payload was already submitted to governance
|
|
704
|
+
const cacheKey = payload.toString();
|
|
705
|
+
if (!this.payloadProposedCache.has(cacheKey)) {
|
|
706
|
+
try {
|
|
707
|
+
const l1StartBlock = await this.rollupContract.getL1StartBlock();
|
|
708
|
+
const proposed = await retry(
|
|
709
|
+
() => base.hasPayloadBeenProposed(payload.toString(), l1StartBlock),
|
|
710
|
+
'Check if payload was proposed',
|
|
711
|
+
makeBackoff([0, 1, 2]),
|
|
712
|
+
this.log,
|
|
713
|
+
true,
|
|
714
|
+
);
|
|
715
|
+
if (proposed) {
|
|
716
|
+
this.payloadProposedCache.add(cacheKey);
|
|
717
|
+
}
|
|
718
|
+
} catch (err) {
|
|
719
|
+
this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
if (this.payloadProposedCache.has(cacheKey)) {
|
|
725
|
+
this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
|
|
726
|
+
return false;
|
|
727
|
+
}
|
|
728
|
+
|
|
694
729
|
const cachedLastVote = this.lastActions[signalType];
|
|
695
730
|
this.lastActions[signalType] = slotNumber;
|
|
696
731
|
const action = signalType;
|
|
@@ -918,14 +953,15 @@ export class SequencerPublisher {
|
|
|
918
953
|
const checkpointHeader = checkpoint.header;
|
|
919
954
|
|
|
920
955
|
const blobFields = checkpoint.toBlobFields();
|
|
921
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
956
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
922
957
|
|
|
923
|
-
const proposeTxArgs = {
|
|
958
|
+
const proposeTxArgs: L1ProcessArgs = {
|
|
924
959
|
header: checkpointHeader,
|
|
925
960
|
archive: checkpoint.archive.root.toBuffer(),
|
|
926
961
|
blobs,
|
|
927
962
|
attestationsAndSigners,
|
|
928
963
|
attestationsAndSignersSignature,
|
|
964
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
929
965
|
};
|
|
930
966
|
|
|
931
967
|
let ts: bigint;
|
|
@@ -1113,8 +1149,7 @@ export class SequencerPublisher {
|
|
|
1113
1149
|
header: encodedData.header.toViem(),
|
|
1114
1150
|
archive: toHex(encodedData.archive),
|
|
1115
1151
|
oracleInput: {
|
|
1116
|
-
|
|
1117
|
-
feeAssetPriceModifier: 0n,
|
|
1152
|
+
feeAssetPriceModifier: encodedData.feeAssetPriceModifier,
|
|
1118
1153
|
},
|
|
1119
1154
|
},
|
|
1120
1155
|
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
@@ -1140,7 +1175,7 @@ export class SequencerPublisher {
|
|
|
1140
1175
|
readonly header: ViemHeader;
|
|
1141
1176
|
readonly archive: `0x${string}`;
|
|
1142
1177
|
readonly oracleInput: {
|
|
1143
|
-
readonly feeAssetPriceModifier:
|
|
1178
|
+
readonly feeAssetPriceModifier: bigint;
|
|
1144
1179
|
};
|
|
1145
1180
|
},
|
|
1146
1181
|
ViemCommitteeAttestations,
|
|
@@ -129,7 +129,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
129
129
|
await Promise.all(votesPromises);
|
|
130
130
|
|
|
131
131
|
if (checkpoint) {
|
|
132
|
-
this.metrics.
|
|
132
|
+
this.metrics.recordCheckpointProposalSuccess();
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
// Do not post anything to L1 if we are fishermen, but do perform L1 fee analysis
|
|
@@ -186,18 +186,21 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
186
186
|
const inHash = computeInHashFromL1ToL2Messages(l1ToL2Messages);
|
|
187
187
|
|
|
188
188
|
// Collect the out hashes of all the checkpoints before this one in the same epoch
|
|
189
|
-
const
|
|
190
|
-
c => c.
|
|
191
|
-
|
|
192
|
-
|
|
189
|
+
const previousCheckpointOutHashes = (await this.l2BlockSource.getCheckpointsDataForEpoch(this.epoch))
|
|
190
|
+
.filter(c => c.checkpointNumber < this.checkpointNumber)
|
|
191
|
+
.map(c => c.checkpointOutHash);
|
|
192
|
+
|
|
193
|
+
// Get the fee asset price modifier from the oracle
|
|
194
|
+
const feeAssetPriceModifier = await this.publisher.getFeeAssetPriceModifier();
|
|
193
195
|
|
|
194
196
|
// Create a long-lived forked world state for the checkpoint builder
|
|
195
|
-
using fork = await this.worldState.fork(this.syncedToBlockNumber, { closeDelayMs: 12_000 });
|
|
197
|
+
await using fork = await this.worldState.fork(this.syncedToBlockNumber, { closeDelayMs: 12_000 });
|
|
196
198
|
|
|
197
199
|
// Create checkpoint builder for the entire slot
|
|
198
200
|
const checkpointBuilder = await this.checkpointsBuilder.startCheckpoint(
|
|
199
201
|
this.checkpointNumber,
|
|
200
202
|
checkpointGlobalVariables,
|
|
203
|
+
feeAssetPriceModifier,
|
|
201
204
|
l1ToL2Messages,
|
|
202
205
|
previousCheckpointOutHashes,
|
|
203
206
|
fork,
|
|
@@ -217,6 +220,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
217
220
|
|
|
218
221
|
let blocksInCheckpoint: L2Block[] = [];
|
|
219
222
|
let blockPendingBroadcast: { block: L2Block; txs: Tx[] } | undefined = undefined;
|
|
223
|
+
const checkpointBuildTimer = new Timer();
|
|
220
224
|
|
|
221
225
|
try {
|
|
222
226
|
// Main loop: build blocks for the checkpoint
|
|
@@ -244,11 +248,28 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
244
248
|
return undefined;
|
|
245
249
|
}
|
|
246
250
|
|
|
251
|
+
const minBlocksForCheckpoint = this.config.minBlocksForCheckpoint;
|
|
252
|
+
if (minBlocksForCheckpoint !== undefined && blocksInCheckpoint.length < minBlocksForCheckpoint) {
|
|
253
|
+
this.log.warn(
|
|
254
|
+
`Checkpoint has fewer blocks than minimum (${blocksInCheckpoint.length} < ${minBlocksForCheckpoint}), skipping proposal`,
|
|
255
|
+
{ slot: this.slot, blocksBuilt: blocksInCheckpoint.length, minBlocksForCheckpoint },
|
|
256
|
+
);
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
|
|
247
260
|
// Assemble and broadcast the checkpoint proposal, including the last block that was not
|
|
248
261
|
// broadcasted yet, and wait to collect the committee attestations.
|
|
249
262
|
this.setStateFn(SequencerState.ASSEMBLING_CHECKPOINT, this.slot);
|
|
250
263
|
const checkpoint = await checkpointBuilder.completeCheckpoint();
|
|
251
264
|
|
|
265
|
+
// Record checkpoint-level build metrics
|
|
266
|
+
this.metrics.recordCheckpointBuild(
|
|
267
|
+
checkpointBuildTimer.ms(),
|
|
268
|
+
blocksInCheckpoint.length,
|
|
269
|
+
checkpoint.getStats().txCount,
|
|
270
|
+
Number(checkpoint.header.totalManaUsed.toBigInt()),
|
|
271
|
+
);
|
|
272
|
+
|
|
252
273
|
// Do not collect attestations nor publish to L1 in fisherman mode
|
|
253
274
|
if (this.config.fishermanMode) {
|
|
254
275
|
this.log.info(
|
|
@@ -275,6 +296,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
275
296
|
const proposal = await this.validatorClient.createCheckpointProposal(
|
|
276
297
|
checkpoint.header,
|
|
277
298
|
checkpoint.archive.root,
|
|
299
|
+
feeAssetPriceModifier,
|
|
278
300
|
lastBlock,
|
|
279
301
|
this.proposer,
|
|
280
302
|
checkpointProposalOptions,
|
|
@@ -313,6 +335,21 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
313
335
|
const aztecSlotDuration = this.l1Constants.slotDuration;
|
|
314
336
|
const slotStartBuildTimestamp = this.getSlotStartBuildTimestamp();
|
|
315
337
|
const txTimeoutAt = new Date((slotStartBuildTimestamp + aztecSlotDuration) * 1000);
|
|
338
|
+
|
|
339
|
+
// If we have been configured to potentially skip publishing checkpoint then roll the dice here
|
|
340
|
+
if (
|
|
341
|
+
this.config.skipPublishingCheckpointsPercent !== undefined &&
|
|
342
|
+
this.config.skipPublishingCheckpointsPercent > 0
|
|
343
|
+
) {
|
|
344
|
+
const result = Math.max(0, randomInt(100));
|
|
345
|
+
if (result < this.config.skipPublishingCheckpointsPercent) {
|
|
346
|
+
this.log.warn(
|
|
347
|
+
`Skipping publishing proposal for checkpoint ${checkpoint.number}. Configured percentage: ${this.config.skipPublishingCheckpointsPercent}, generated value: ${result}`,
|
|
348
|
+
);
|
|
349
|
+
return checkpoint;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
316
353
|
await this.publisher.enqueueProposeCheckpoint(checkpoint, attestations, attestationsSignature, {
|
|
317
354
|
txTimeoutAt,
|
|
318
355
|
forcePendingCheckpointNumber: this.invalidateCheckpoint?.forcePendingCheckpointNumber,
|
|
@@ -821,7 +858,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
821
858
|
slot: this.slot,
|
|
822
859
|
feeAnalysisId: feeAnalysis?.id,
|
|
823
860
|
});
|
|
824
|
-
this.metrics.
|
|
861
|
+
this.metrics.recordCheckpointProposalFailed('block_build_failed');
|
|
825
862
|
}
|
|
826
863
|
|
|
827
864
|
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
|
}
|
|
@@ -12,7 +12,7 @@ import type { DateProvider } from '@aztec/foundation/timer';
|
|
|
12
12
|
import type { TypedEventEmitter } from '@aztec/foundation/types';
|
|
13
13
|
import type { P2P } from '@aztec/p2p';
|
|
14
14
|
import type { SlasherClientInterface } from '@aztec/slasher';
|
|
15
|
-
import type {
|
|
15
|
+
import type { BlockData, L2BlockSink, L2BlockSource, ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
16
16
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
17
17
|
import { getSlotAtTimestamp, getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
18
18
|
import {
|
|
@@ -25,7 +25,7 @@ import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
|
25
25
|
import { pickFromSchema } from '@aztec/stdlib/schemas';
|
|
26
26
|
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
27
27
|
import { Attributes, type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
28
|
-
import { FullNodeCheckpointsBuilder, type ValidatorClient } from '@aztec/validator-client';
|
|
28
|
+
import { FullNodeCheckpointsBuilder, NodeKeystoreAdapter, type ValidatorClient } from '@aztec/validator-client';
|
|
29
29
|
|
|
30
30
|
import EventEmitter from 'node:events';
|
|
31
31
|
|
|
@@ -75,14 +75,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
75
75
|
/** The maximum number of seconds that the sequencer can be into a slot to transition to a particular state. */
|
|
76
76
|
protected timetable!: SequencerTimetable;
|
|
77
77
|
|
|
78
|
-
// This shouldn't be here as this gets re-created each time we build/propose a block.
|
|
79
|
-
// But we have a number of tests that abuse/rely on this class having a permanent publisher.
|
|
80
|
-
// As long as those tests only configure a single publisher they will continue to work.
|
|
81
|
-
// This will get re-assigned every time the sequencer goes to build a new block to a publisher that is valid
|
|
82
|
-
// for the block proposer.
|
|
83
|
-
// TODO(palla/mbps): Remove this field and fix tests
|
|
84
|
-
protected publisher: SequencerPublisher | undefined;
|
|
85
|
-
|
|
86
78
|
/** Config for the sequencer */
|
|
87
79
|
protected config: ResolvedSequencerConfig = DefaultSequencerConfig;
|
|
88
80
|
|
|
@@ -134,10 +126,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
134
126
|
);
|
|
135
127
|
}
|
|
136
128
|
|
|
137
|
-
/** Initializes the sequencer (precomputes tables
|
|
138
|
-
public
|
|
129
|
+
/** Initializes the sequencer (precomputes tables). Takes about 3s. */
|
|
130
|
+
public init() {
|
|
139
131
|
getKzg();
|
|
140
|
-
this.publisher = (await this.publisherFactory.create(undefined)).publisher;
|
|
141
132
|
}
|
|
142
133
|
|
|
143
134
|
/** Starts the sequencer and moves to IDLE state. */
|
|
@@ -156,7 +147,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
156
147
|
public async stop(): Promise<void> {
|
|
157
148
|
this.log.info(`Stopping sequencer`);
|
|
158
149
|
this.setState(SequencerState.STOPPING, undefined, { force: true });
|
|
159
|
-
this.
|
|
150
|
+
this.publisherFactory.interruptAll();
|
|
160
151
|
await this.runningPromise?.stop();
|
|
161
152
|
this.setState(SequencerState.STOPPED, undefined, { force: true });
|
|
162
153
|
this.log.info('Stopped sequencer');
|
|
@@ -169,7 +160,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
169
160
|
} catch (err) {
|
|
170
161
|
this.emit('checkpoint-error', { error: err as Error });
|
|
171
162
|
if (err instanceof SequencerTooSlowError) {
|
|
172
|
-
// TODO(palla/mbps): Add missing states
|
|
173
163
|
// Log as warn only if we had to abort halfway through the block proposal
|
|
174
164
|
const logLvl = [SequencerState.INITIALIZING_CHECKPOINT, SequencerState.PROPOSER_CHECK].includes(
|
|
175
165
|
err.proposedState,
|
|
@@ -310,12 +300,12 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
310
300
|
}
|
|
311
301
|
|
|
312
302
|
// Check that the slot is not taken by a block already (should never happen, since only us can propose for this slot)
|
|
313
|
-
if (syncedTo.
|
|
303
|
+
if (syncedTo.blockData && syncedTo.blockData.header.getSlot() >= slot) {
|
|
314
304
|
this.log.warn(
|
|
315
305
|
`Cannot propose block at next L2 slot ${slot} since that slot was taken by block ${syncedTo.blockNumber}`,
|
|
316
|
-
{ ...logCtx, block: syncedTo.
|
|
306
|
+
{ ...logCtx, block: syncedTo.blockData.header.toInspect() },
|
|
317
307
|
);
|
|
318
|
-
this.metrics.
|
|
308
|
+
this.metrics.recordCheckpointPrecheckFailed('slot_already_taken');
|
|
319
309
|
return undefined;
|
|
320
310
|
}
|
|
321
311
|
|
|
@@ -326,7 +316,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
326
316
|
const proposerForPublisher = this.config.fishermanMode ? undefined : proposer;
|
|
327
317
|
const { attestorAddress, publisher } = await this.publisherFactory.create(proposerForPublisher);
|
|
328
318
|
this.log.verbose(`Created publisher at address ${publisher.getSenderAddress()} for attestor ${attestorAddress}`);
|
|
329
|
-
this.publisher = publisher;
|
|
330
319
|
|
|
331
320
|
// In fisherman mode, set the actual proposer's address for simulations
|
|
332
321
|
if (this.config.fishermanMode && proposer) {
|
|
@@ -351,7 +340,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
351
340
|
logCtx,
|
|
352
341
|
);
|
|
353
342
|
this.emit('proposer-rollup-check-failed', { reason: 'Rollup contract check failed', slot });
|
|
354
|
-
this.metrics.
|
|
343
|
+
this.metrics.recordCheckpointPrecheckFailed('rollup_contract_check_failed');
|
|
355
344
|
return undefined;
|
|
356
345
|
}
|
|
357
346
|
|
|
@@ -361,7 +350,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
361
350
|
{ ...logCtx, rollup: canProposeCheck, expectedSlot: slot },
|
|
362
351
|
);
|
|
363
352
|
this.emit('proposer-rollup-check-failed', { reason: 'Slot mismatch', slot });
|
|
364
|
-
this.metrics.
|
|
353
|
+
this.metrics.recordCheckpointPrecheckFailed('slot_mismatch');
|
|
365
354
|
return undefined;
|
|
366
355
|
}
|
|
367
356
|
|
|
@@ -371,7 +360,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
371
360
|
{ ...logCtx, rollup: canProposeCheck, expectedSlot: slot },
|
|
372
361
|
);
|
|
373
362
|
this.emit('proposer-rollup-check-failed', { reason: 'Block mismatch', slot });
|
|
374
|
-
this.metrics.
|
|
363
|
+
this.metrics.recordCheckpointPrecheckFailed('block_number_mismatch');
|
|
375
364
|
return undefined;
|
|
376
365
|
}
|
|
377
366
|
|
|
@@ -533,18 +522,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
533
522
|
};
|
|
534
523
|
}
|
|
535
524
|
|
|
536
|
-
const
|
|
537
|
-
if (!
|
|
525
|
+
const blockData = await this.l2BlockSource.getBlockData(blockNumber);
|
|
526
|
+
if (!blockData) {
|
|
538
527
|
// this shouldn't really happen because a moment ago we checked that all components were in sync
|
|
539
|
-
this.log.error(`Failed to get L2 block ${blockNumber} from the archiver with all components in sync`);
|
|
528
|
+
this.log.error(`Failed to get L2 block data ${blockNumber} from the archiver with all components in sync`);
|
|
540
529
|
return undefined;
|
|
541
530
|
}
|
|
542
531
|
|
|
543
532
|
return {
|
|
544
|
-
|
|
545
|
-
blockNumber:
|
|
546
|
-
checkpointNumber:
|
|
547
|
-
archive:
|
|
533
|
+
blockData,
|
|
534
|
+
blockNumber: blockData.header.getBlockNumber(),
|
|
535
|
+
checkpointNumber: blockData.checkpointNumber,
|
|
536
|
+
archive: blockData.archive.root,
|
|
548
537
|
l1Timestamp,
|
|
549
538
|
pendingChainValidationStatus,
|
|
550
539
|
};
|
|
@@ -867,6 +856,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
867
856
|
return this.validatorClient?.getValidatorAddresses();
|
|
868
857
|
}
|
|
869
858
|
|
|
859
|
+
/** Updates the publisher factory's node keystore adapter after a keystore reload. */
|
|
860
|
+
public updatePublisherNodeKeyStore(adapter: NodeKeystoreAdapter): void {
|
|
861
|
+
this.publisherFactory.updateNodeKeyStore(adapter);
|
|
862
|
+
}
|
|
863
|
+
|
|
870
864
|
public getConfig() {
|
|
871
865
|
return this.config;
|
|
872
866
|
}
|
|
@@ -877,7 +871,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
877
871
|
}
|
|
878
872
|
|
|
879
873
|
type SequencerSyncCheckResult = {
|
|
880
|
-
|
|
874
|
+
blockData?: BlockData;
|
|
881
875
|
checkpointNumber: CheckpointNumber;
|
|
882
876
|
blockNumber: BlockNumber;
|
|
883
877
|
archive: Fr;
|