@aztec/sequencer-client 2.0.3-rc.9 → 2.1.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +5 -4
- package/dest/config.d.ts +2 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +2 -0
- package/dest/publisher/config.d.ts +5 -3
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +13 -5
- package/dest/publisher/index.d.ts +1 -1
- package/dest/publisher/index.d.ts.map +1 -1
- package/dest/publisher/index.js +1 -1
- package/dest/publisher/sequencer-publisher-factory.d.ts +5 -1
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +8 -1
- package/dest/publisher/sequencer-publisher.d.ts +13 -15
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +58 -55
- package/dest/sequencer/metrics.d.ts +5 -17
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +22 -88
- package/dest/sequencer/sequencer.d.ts +4 -3
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +16 -17
- package/package.json +28 -28
- package/src/client/sequencer-client.ts +4 -2
- package/src/config.ts +3 -0
- package/src/publisher/config.ts +23 -5
- package/src/publisher/index.ts +1 -1
- package/src/publisher/sequencer-publisher-factory.ts +12 -2
- package/src/publisher/sequencer-publisher.ts +77 -69
- package/src/sequencer/metrics.ts +24 -100
- package/src/sequencer/sequencer.ts +42 -38
|
@@ -27,20 +27,19 @@ import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs'
|
|
|
27
27
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
28
28
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
29
29
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
30
|
+
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
30
31
|
import type { Fr } from '@aztec/foundation/fields';
|
|
31
|
-
import { createLogger } from '@aztec/foundation/log';
|
|
32
|
+
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
32
33
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
33
34
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
34
35
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
35
36
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
36
|
-
import { CommitteeAttestation, type ValidateBlockResult } from '@aztec/stdlib/block';
|
|
37
|
+
import { CommitteeAttestation, CommitteeAttestationsAndSigners, type ValidateBlockResult } from '@aztec/stdlib/block';
|
|
37
38
|
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
38
|
-
import { ConsensusPayload, SignatureDomainSeparator, getHashedSignaturePayload } from '@aztec/stdlib/p2p';
|
|
39
39
|
import type { L1PublishBlockStats } from '@aztec/stdlib/stats';
|
|
40
|
-
import { type ProposedBlockHeader, StateReference
|
|
40
|
+
import { type ProposedBlockHeader, StateReference } from '@aztec/stdlib/tx';
|
|
41
41
|
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
42
42
|
|
|
43
|
-
import pick from 'lodash.pick';
|
|
44
43
|
import { type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
|
|
45
44
|
|
|
46
45
|
import type { PublisherConfig, TxSenderConfig } from './config.js';
|
|
@@ -56,17 +55,12 @@ type L1ProcessArgs = {
|
|
|
56
55
|
stateReference: StateReference;
|
|
57
56
|
/** L2 block blobs containing all tx effects. */
|
|
58
57
|
blobs: Blob[];
|
|
59
|
-
/** L2 block tx hashes */
|
|
60
|
-
txHashes: TxHash[];
|
|
61
58
|
/** Attestations */
|
|
62
|
-
|
|
59
|
+
attestationsAndSigners: CommitteeAttestationsAndSigners;
|
|
60
|
+
/** Attestations and signers signature */
|
|
61
|
+
attestationsAndSignersSignature: Signature;
|
|
63
62
|
};
|
|
64
63
|
|
|
65
|
-
export enum SignalType {
|
|
66
|
-
GOVERNANCE,
|
|
67
|
-
SLASHING,
|
|
68
|
-
}
|
|
69
|
-
|
|
70
64
|
export const Actions = [
|
|
71
65
|
'invalidate-by-invalid-attestation',
|
|
72
66
|
'invalidate-by-insufficient-attestations',
|
|
@@ -78,8 +72,11 @@ export const Actions = [
|
|
|
78
72
|
'vote-offenses',
|
|
79
73
|
'execute-slash',
|
|
80
74
|
] as const;
|
|
75
|
+
|
|
81
76
|
export type Action = (typeof Actions)[number];
|
|
82
77
|
|
|
78
|
+
type GovernanceSignalAction = Extract<Action, 'governance-signal' | 'empire-slashing-signal'>;
|
|
79
|
+
|
|
83
80
|
// Sorting for actions such that invalidations go before proposals, and proposals go before votes
|
|
84
81
|
export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
|
|
85
82
|
|
|
@@ -104,6 +101,7 @@ interface RequestWithExpiry {
|
|
|
104
101
|
}
|
|
105
102
|
|
|
106
103
|
export class SequencerPublisher {
|
|
104
|
+
private enabled: boolean;
|
|
107
105
|
private interrupted = false;
|
|
108
106
|
private metrics: SequencerPublisherMetrics;
|
|
109
107
|
public epochCache: EpochCache;
|
|
@@ -111,12 +109,9 @@ export class SequencerPublisher {
|
|
|
111
109
|
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
112
110
|
protected slashingLog = createLogger('sequencer:publisher:slashing');
|
|
113
111
|
|
|
114
|
-
|
|
115
|
-
[SignalType.GOVERNANCE]: 0n,
|
|
116
|
-
[SignalType.SLASHING]: 0n,
|
|
117
|
-
};
|
|
112
|
+
protected lastActions: Partial<Record<Action, bigint>> = {};
|
|
118
113
|
|
|
119
|
-
protected log
|
|
114
|
+
protected log: Logger;
|
|
120
115
|
protected ethereumSlotDuration: bigint;
|
|
121
116
|
|
|
122
117
|
private blobSinkClient: BlobSinkClientInterface;
|
|
@@ -152,10 +147,15 @@ export class SequencerPublisher {
|
|
|
152
147
|
epochCache: EpochCache;
|
|
153
148
|
dateProvider: DateProvider;
|
|
154
149
|
metrics: SequencerPublisherMetrics;
|
|
150
|
+
lastActions: Partial<Record<Action, bigint>>;
|
|
151
|
+
log?: Logger;
|
|
155
152
|
},
|
|
156
153
|
) {
|
|
154
|
+
this.enabled = config.publisherEnabled ?? true;
|
|
155
|
+
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
157
156
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
158
157
|
this.epochCache = deps.epochCache;
|
|
158
|
+
this.lastActions = deps.lastActions;
|
|
159
159
|
|
|
160
160
|
this.blobSinkClient =
|
|
161
161
|
deps.blobSinkClient ?? createBlobSinkClient(config, { logger: createLogger('sequencer:blob-sink:client') });
|
|
@@ -201,6 +201,14 @@ export class SequencerPublisher {
|
|
|
201
201
|
* - undefined if no valid requests are found OR the tx failed to send.
|
|
202
202
|
*/
|
|
203
203
|
public async sendRequests() {
|
|
204
|
+
if (!this.enabled) {
|
|
205
|
+
this.log.warn(`Sending L1 txs is disabled`, {
|
|
206
|
+
requestsDiscarded: this.requests.map(r => r.action),
|
|
207
|
+
});
|
|
208
|
+
this.requests = [];
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
|
|
204
212
|
const requestsToProcess = [...this.requests];
|
|
205
213
|
this.requests = [];
|
|
206
214
|
if (this.interrupted) {
|
|
@@ -346,8 +354,9 @@ export class SequencerPublisher {
|
|
|
346
354
|
|
|
347
355
|
const args = [
|
|
348
356
|
header.toViem(),
|
|
349
|
-
|
|
357
|
+
CommitteeAttestationsAndSigners.empty().getPackedAttestations(),
|
|
350
358
|
[], // no signers
|
|
359
|
+
Signature.empty().toViemSignature(),
|
|
351
360
|
`0x${'0'.repeat(64)}`, // 32 empty bytes
|
|
352
361
|
header.contentCommitment.blobsHash.toString(),
|
|
353
362
|
flags,
|
|
@@ -446,17 +455,19 @@ export class SequencerPublisher {
|
|
|
446
455
|
const logData = { ...block, reason };
|
|
447
456
|
this.log.debug(`Simulating invalidate block ${block.blockNumber}`, logData);
|
|
448
457
|
|
|
458
|
+
const attestationsAndSigners = new CommitteeAttestationsAndSigners(attestations).getPackedAttestations();
|
|
459
|
+
|
|
449
460
|
if (reason === 'invalid-attestation') {
|
|
450
461
|
return this.rollupContract.buildInvalidateBadAttestationRequest(
|
|
451
462
|
block.blockNumber,
|
|
452
|
-
|
|
463
|
+
attestationsAndSigners,
|
|
453
464
|
committee,
|
|
454
465
|
validationResult.invalidIndex,
|
|
455
466
|
);
|
|
456
467
|
} else if (reason === 'insufficient-attestations') {
|
|
457
468
|
return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
|
|
458
469
|
block.blockNumber,
|
|
459
|
-
|
|
470
|
+
attestationsAndSigners,
|
|
460
471
|
committee,
|
|
461
472
|
);
|
|
462
473
|
} else {
|
|
@@ -476,24 +487,22 @@ export class SequencerPublisher {
|
|
|
476
487
|
*/
|
|
477
488
|
public async validateBlockForSubmission(
|
|
478
489
|
block: L2Block,
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
attestations: [],
|
|
482
|
-
},
|
|
490
|
+
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
491
|
+
attestationsAndSignersSignature: Signature,
|
|
483
492
|
options: { forcePendingBlockNumber?: number },
|
|
484
493
|
): Promise<bigint> {
|
|
485
494
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
486
495
|
|
|
487
496
|
// If we have no attestations, we still need to provide the empty attestations
|
|
488
497
|
// so that the committee is recalculated correctly
|
|
489
|
-
const ignoreSignatures =
|
|
498
|
+
const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
490
499
|
if (ignoreSignatures) {
|
|
491
500
|
const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber.toBigInt());
|
|
492
501
|
if (!committee) {
|
|
493
502
|
this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber.toBigInt()}`);
|
|
494
503
|
throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber.toBigInt()}`);
|
|
495
504
|
}
|
|
496
|
-
|
|
505
|
+
attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
497
506
|
CommitteeAttestation.fromAddress(committeeMember),
|
|
498
507
|
);
|
|
499
508
|
}
|
|
@@ -501,23 +510,18 @@ export class SequencerPublisher {
|
|
|
501
510
|
const blobs = await Blob.getBlobsPerBlock(block.body.toBlobFields());
|
|
502
511
|
const blobInput = Blob.getPrefixedEthBlobCommitments(blobs);
|
|
503
512
|
|
|
504
|
-
const formattedAttestations = attestationData.attestations.map(attest => attest.toViem());
|
|
505
|
-
const signers = attestationData.attestations
|
|
506
|
-
.filter(attest => !attest.signature.isEmpty())
|
|
507
|
-
.map(attest => attest.address.toString());
|
|
508
|
-
|
|
509
513
|
const args = [
|
|
510
514
|
{
|
|
511
515
|
header: block.header.toPropose().toViem(),
|
|
512
516
|
archive: toHex(block.archive.root.toBuffer()),
|
|
513
517
|
stateReference: block.header.state.toViem(),
|
|
514
|
-
txHashes: block.body.txEffects.map(txEffect => txEffect.txHash.toString()),
|
|
515
518
|
oracleInput: {
|
|
516
519
|
feeAssetPriceModifier: 0n,
|
|
517
520
|
},
|
|
518
521
|
},
|
|
519
|
-
|
|
520
|
-
|
|
522
|
+
attestationsAndSigners.getPackedAttestations(),
|
|
523
|
+
attestationsAndSigners.getSigners().map(signer => signer.toString()),
|
|
524
|
+
attestationsAndSignersSignature.toViemSignature(),
|
|
521
525
|
blobInput,
|
|
522
526
|
] as const;
|
|
523
527
|
|
|
@@ -528,13 +532,14 @@ export class SequencerPublisher {
|
|
|
528
532
|
private async enqueueCastSignalHelper(
|
|
529
533
|
slotNumber: bigint,
|
|
530
534
|
timestamp: bigint,
|
|
531
|
-
signalType:
|
|
535
|
+
signalType: GovernanceSignalAction,
|
|
532
536
|
payload: EthAddress,
|
|
533
537
|
base: IEmpireBase,
|
|
534
538
|
signerAddress: EthAddress,
|
|
535
539
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
536
540
|
): Promise<boolean> {
|
|
537
|
-
if (this.
|
|
541
|
+
if (this.lastActions[signalType] && this.lastActions[signalType] === slotNumber) {
|
|
542
|
+
this.log.debug(`Skipping duplicate vote cast signal ${signalType} for slot ${slotNumber}`);
|
|
538
543
|
return false;
|
|
539
544
|
}
|
|
540
545
|
if (payload.equals(EthAddress.ZERO)) {
|
|
@@ -551,10 +556,9 @@ export class SequencerPublisher {
|
|
|
551
556
|
return false;
|
|
552
557
|
}
|
|
553
558
|
|
|
554
|
-
const cachedLastVote = this.
|
|
555
|
-
this.
|
|
556
|
-
|
|
557
|
-
const action = signalType === SignalType.GOVERNANCE ? 'governance-signal' : 'empire-slashing-signal';
|
|
559
|
+
const cachedLastVote = this.lastActions[signalType];
|
|
560
|
+
this.lastActions[signalType] = slotNumber;
|
|
561
|
+
const action = signalType;
|
|
558
562
|
|
|
559
563
|
const request = await base.createSignalRequestWithSignature(
|
|
560
564
|
payload.toString(),
|
|
@@ -597,7 +601,7 @@ export class SequencerPublisher {
|
|
|
597
601
|
`Signaling in [${action}] for ${payload} at slot ${slotNumber} in round ${round} failed`,
|
|
598
602
|
logData,
|
|
599
603
|
);
|
|
600
|
-
this.
|
|
604
|
+
this.lastActions[signalType] = cachedLastVote;
|
|
601
605
|
return false;
|
|
602
606
|
} else {
|
|
603
607
|
this.log.info(
|
|
@@ -627,7 +631,7 @@ export class SequencerPublisher {
|
|
|
627
631
|
return this.enqueueCastSignalHelper(
|
|
628
632
|
slotNumber,
|
|
629
633
|
timestamp,
|
|
630
|
-
|
|
634
|
+
'governance-signal',
|
|
631
635
|
governancePayload,
|
|
632
636
|
this.govProposerContract,
|
|
633
637
|
signerAddress,
|
|
@@ -661,7 +665,7 @@ export class SequencerPublisher {
|
|
|
661
665
|
await this.enqueueCastSignalHelper(
|
|
662
666
|
slotNumber,
|
|
663
667
|
timestamp,
|
|
664
|
-
|
|
668
|
+
'empire-slashing-signal',
|
|
665
669
|
action.payload,
|
|
666
670
|
this.slashingProposerContract,
|
|
667
671
|
signerAddress,
|
|
@@ -766,15 +770,12 @@ export class SequencerPublisher {
|
|
|
766
770
|
*/
|
|
767
771
|
public async enqueueProposeL2Block(
|
|
768
772
|
block: L2Block,
|
|
769
|
-
|
|
770
|
-
|
|
773
|
+
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
774
|
+
attestationsAndSignersSignature: Signature,
|
|
771
775
|
opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: number } = {},
|
|
772
776
|
): Promise<boolean> {
|
|
773
777
|
const proposedBlockHeader = block.header.toPropose();
|
|
774
778
|
|
|
775
|
-
const consensusPayload = ConsensusPayload.fromBlock(block);
|
|
776
|
-
const digest = getHashedSignaturePayload(consensusPayload, SignatureDomainSeparator.blockAttestation);
|
|
777
|
-
|
|
778
779
|
const blobs = await Blob.getBlobsPerBlock(block.body.toBlobFields());
|
|
779
780
|
const proposeTxArgs = {
|
|
780
781
|
header: proposedBlockHeader,
|
|
@@ -782,8 +783,8 @@ export class SequencerPublisher {
|
|
|
782
783
|
stateReference: block.header.state,
|
|
783
784
|
body: block.body.toBuffer(),
|
|
784
785
|
blobs,
|
|
785
|
-
|
|
786
|
-
|
|
786
|
+
attestationsAndSigners,
|
|
787
|
+
attestationsAndSignersSignature,
|
|
787
788
|
};
|
|
788
789
|
|
|
789
790
|
let ts: bigint;
|
|
@@ -793,9 +794,8 @@ export class SequencerPublisher {
|
|
|
793
794
|
// This means that we can avoid the simulation issues in later checks.
|
|
794
795
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
795
796
|
// make time consistency checks break.
|
|
796
|
-
const attestationData = { digest: digest.toBuffer(), attestations: attestations ?? [] };
|
|
797
797
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
798
|
-
ts = await this.validateBlockForSubmission(block,
|
|
798
|
+
ts = await this.validateBlockForSubmission(block, attestationsAndSigners, attestationsAndSignersSignature, opts);
|
|
799
799
|
} catch (err: any) {
|
|
800
800
|
this.log.error(`Block validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
801
801
|
...block.getStats(),
|
|
@@ -818,7 +818,8 @@ export class SequencerPublisher {
|
|
|
818
818
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
819
819
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(request.gasUsed) * 64) / 63)));
|
|
820
820
|
|
|
821
|
-
const
|
|
821
|
+
const { gasUsed, blockNumber } = request;
|
|
822
|
+
const logData = { gasUsed, blockNumber, gasLimit, opts };
|
|
822
823
|
this.log.verbose(`Enqueuing invalidate block request`, logData);
|
|
823
824
|
this.addRequest({
|
|
824
825
|
action: `invalidate-by-${request.reason}`,
|
|
@@ -842,16 +843,24 @@ export class SequencerPublisher {
|
|
|
842
843
|
}
|
|
843
844
|
|
|
844
845
|
private async simulateAndEnqueueRequest(
|
|
845
|
-
action:
|
|
846
|
+
action: Action,
|
|
846
847
|
request: L1TxRequest,
|
|
847
848
|
checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
|
|
848
849
|
slotNumber: bigint,
|
|
849
850
|
timestamp: bigint,
|
|
850
851
|
) {
|
|
851
852
|
const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
|
|
852
|
-
|
|
853
|
+
if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
|
|
854
|
+
this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
|
|
855
|
+
return false;
|
|
856
|
+
}
|
|
853
857
|
|
|
854
|
-
this.
|
|
858
|
+
const cachedLastActionSlot = this.lastActions[action];
|
|
859
|
+
this.lastActions[action] = slotNumber;
|
|
860
|
+
|
|
861
|
+
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
862
|
+
|
|
863
|
+
let gasUsed: bigint;
|
|
855
864
|
try {
|
|
856
865
|
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], ErrorsAbi)); // TODO(palla/slash): Check the timestamp logic
|
|
857
866
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
@@ -875,6 +884,7 @@ export class SequencerPublisher {
|
|
|
875
884
|
const success = result && result.receipt && result.receipt.status === 'success' && checkSuccess(result.receipt);
|
|
876
885
|
if (!success) {
|
|
877
886
|
this.log.warn(`Action ${action} at ${slotNumber} failed`, { ...result, ...logData });
|
|
887
|
+
this.lastActions[action] = cachedLastActionSlot;
|
|
878
888
|
} else {
|
|
879
889
|
this.log.info(`Action ${action} at ${slotNumber} succeeded`, { ...result, ...logData });
|
|
880
890
|
}
|
|
@@ -932,12 +942,7 @@ export class SequencerPublisher {
|
|
|
932
942
|
throw new Error('Failed to validate blobs');
|
|
933
943
|
});
|
|
934
944
|
|
|
935
|
-
const
|
|
936
|
-
const txHashes = encodedData.txHashes ? encodedData.txHashes.map(txHash => txHash.toString()) : [];
|
|
937
|
-
|
|
938
|
-
const signers = encodedData.attestations
|
|
939
|
-
?.filter(attest => !attest.signature.isEmpty())
|
|
940
|
-
.map(attest => attest.address.toString());
|
|
945
|
+
const signers = encodedData.attestationsAndSigners.getSigners().map(signer => signer.toString());
|
|
941
946
|
|
|
942
947
|
const args = [
|
|
943
948
|
{
|
|
@@ -948,10 +953,10 @@ export class SequencerPublisher {
|
|
|
948
953
|
// We are currently not modifying these. See #9963
|
|
949
954
|
feeAssetPriceModifier: 0n,
|
|
950
955
|
},
|
|
951
|
-
txHashes,
|
|
952
956
|
},
|
|
953
|
-
|
|
954
|
-
signers
|
|
957
|
+
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
958
|
+
signers,
|
|
959
|
+
encodedData.attestationsAndSignersSignature.toViemSignature(),
|
|
955
960
|
blobInput,
|
|
956
961
|
] as const;
|
|
957
962
|
|
|
@@ -972,13 +977,13 @@ export class SequencerPublisher {
|
|
|
972
977
|
readonly header: ViemHeader;
|
|
973
978
|
readonly archive: `0x${string}`;
|
|
974
979
|
readonly stateReference: ViemStateReference;
|
|
975
|
-
readonly txHashes: `0x${string}`[];
|
|
976
980
|
readonly oracleInput: {
|
|
977
981
|
readonly feeAssetPriceModifier: 0n;
|
|
978
982
|
};
|
|
979
983
|
},
|
|
980
984
|
ViemCommitteeAttestations,
|
|
981
|
-
`0x${string}`[],
|
|
985
|
+
`0x${string}`[], // Signers
|
|
986
|
+
ViemSignature,
|
|
982
987
|
`0x${string}`,
|
|
983
988
|
],
|
|
984
989
|
timestamp: bigint,
|
|
@@ -1084,13 +1089,16 @@ export class SequencerPublisher {
|
|
|
1084
1089
|
if (success) {
|
|
1085
1090
|
const endBlock = receipt.blockNumber;
|
|
1086
1091
|
const inclusionBlocks = Number(endBlock - startBlock);
|
|
1092
|
+
const { calldataGas, calldataSize, sender } = stats!;
|
|
1087
1093
|
const publishStats: L1PublishBlockStats = {
|
|
1088
1094
|
gasPrice: receipt.effectiveGasPrice,
|
|
1089
1095
|
gasUsed: receipt.gasUsed,
|
|
1090
1096
|
blobGasUsed: receipt.blobGasUsed ?? 0n,
|
|
1091
1097
|
blobDataGas: receipt.blobGasPrice ?? 0n,
|
|
1092
1098
|
transactionHash: receipt.transactionHash,
|
|
1093
|
-
|
|
1099
|
+
calldataGas,
|
|
1100
|
+
calldataSize,
|
|
1101
|
+
sender,
|
|
1094
1102
|
...block.getStats(),
|
|
1095
1103
|
eventName: 'rollup-published-to-l1',
|
|
1096
1104
|
blobCount: encodedData.blobs.length,
|
package/src/sequencer/metrics.ts
CHANGED
|
@@ -2,21 +2,19 @@ import type { EthAddress } from '@aztec/aztec.js';
|
|
|
2
2
|
import type { RollupContract } from '@aztec/ethereum';
|
|
3
3
|
import {
|
|
4
4
|
Attributes,
|
|
5
|
-
type BatchObservableResult,
|
|
6
5
|
type Gauge,
|
|
7
6
|
type Histogram,
|
|
8
7
|
type Meter,
|
|
9
8
|
Metrics,
|
|
10
|
-
type ObservableGauge,
|
|
11
9
|
type TelemetryClient,
|
|
12
10
|
type Tracer,
|
|
13
11
|
type UpDownCounter,
|
|
14
12
|
ValueType,
|
|
15
13
|
} from '@aztec/telemetry-client';
|
|
16
14
|
|
|
17
|
-
import { formatUnits } from 'viem';
|
|
15
|
+
import { type Hex, formatUnits } from 'viem';
|
|
18
16
|
|
|
19
|
-
import {
|
|
17
|
+
import type { SequencerState } from './utils.js';
|
|
20
18
|
|
|
21
19
|
export class SequencerMetrics {
|
|
22
20
|
public readonly tracer: Tracer;
|
|
@@ -26,9 +24,6 @@ export class SequencerMetrics {
|
|
|
26
24
|
private blockBuildDuration: Histogram;
|
|
27
25
|
private blockBuildManaPerSecond: Gauge;
|
|
28
26
|
private stateTransitionBufferDuration: Histogram;
|
|
29
|
-
private currentBlockNumber: Gauge;
|
|
30
|
-
private currentBlockSize: Gauge;
|
|
31
|
-
private blockBuilderInsertions: Histogram;
|
|
32
27
|
|
|
33
28
|
// these are gauges because for individual sequencers building a block is not something that happens often enough to warrant a histogram
|
|
34
29
|
private timeToCollectAttestations: Gauge;
|
|
@@ -36,18 +31,15 @@ export class SequencerMetrics {
|
|
|
36
31
|
private requiredAttestions: Gauge;
|
|
37
32
|
private collectedAttestions: Gauge;
|
|
38
33
|
|
|
39
|
-
private rewards:
|
|
34
|
+
private rewards: Gauge;
|
|
40
35
|
|
|
41
36
|
private slots: UpDownCounter;
|
|
42
37
|
private filledSlots: UpDownCounter;
|
|
43
|
-
private missedSlots: UpDownCounter;
|
|
44
38
|
|
|
45
39
|
private lastSeenSlot?: bigint;
|
|
46
40
|
|
|
47
41
|
constructor(
|
|
48
42
|
client: TelemetryClient,
|
|
49
|
-
getState: SequencerStateCallback,
|
|
50
|
-
private coinbase: EthAddress,
|
|
51
43
|
private rollup: RollupContract,
|
|
52
44
|
name = 'Sequencer',
|
|
53
45
|
) {
|
|
@@ -78,35 +70,7 @@ export class SequencerMetrics {
|
|
|
78
70
|
},
|
|
79
71
|
);
|
|
80
72
|
|
|
81
|
-
const currentState = this.meter.createObservableGauge(Metrics.SEQUENCER_CURRENT_STATE, {
|
|
82
|
-
description: 'Current state of the sequencer',
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
currentState.addCallback(observer => {
|
|
86
|
-
observer.observe(sequencerStateToNumber(getState()));
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
this.currentBlockNumber = this.meter.createGauge(Metrics.SEQUENCER_CURRENT_BLOCK_NUMBER, {
|
|
90
|
-
description: 'Current block number',
|
|
91
|
-
valueType: ValueType.INT,
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
this.currentBlockSize = this.meter.createGauge(Metrics.SEQUENCER_CURRENT_BLOCK_SIZE, {
|
|
95
|
-
description: 'Current block size',
|
|
96
|
-
valueType: ValueType.INT,
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
this.blockBuilderInsertions = this.meter.createHistogram(Metrics.SEQUENCER_BLOCK_BUILD_INSERTION_TIME, {
|
|
100
|
-
description: 'Timer for tree insertions performed by the block builder',
|
|
101
|
-
unit: 'us',
|
|
102
|
-
valueType: ValueType.INT,
|
|
103
|
-
});
|
|
104
|
-
|
|
105
73
|
// Init gauges and counters
|
|
106
|
-
this.setCurrentBlock(0, 0);
|
|
107
|
-
this.blockCounter.add(0, {
|
|
108
|
-
[Attributes.STATUS]: 'cancelled',
|
|
109
|
-
});
|
|
110
74
|
this.blockCounter.add(0, {
|
|
111
75
|
[Attributes.STATUS]: 'failed',
|
|
112
76
|
});
|
|
@@ -114,7 +78,7 @@ export class SequencerMetrics {
|
|
|
114
78
|
[Attributes.STATUS]: 'built',
|
|
115
79
|
});
|
|
116
80
|
|
|
117
|
-
this.rewards = this.meter.
|
|
81
|
+
this.rewards = this.meter.createGauge(Metrics.SEQUENCER_CURRENT_BLOCK_REWARDS, {
|
|
118
82
|
valueType: ValueType.DOUBLE,
|
|
119
83
|
description: 'The rewards earned',
|
|
120
84
|
});
|
|
@@ -124,16 +88,15 @@ export class SequencerMetrics {
|
|
|
124
88
|
description: 'The number of slots this sequencer was selected for',
|
|
125
89
|
});
|
|
126
90
|
|
|
91
|
+
/**
|
|
92
|
+
* NOTE: we do not track missed slots as a separate metric. That would be difficult to determine
|
|
93
|
+
* Instead, use a computed metric, `slots - filledSlots` to get the number of slots a sequencer has missed.
|
|
94
|
+
*/
|
|
127
95
|
this.filledSlots = this.meter.createUpDownCounter(Metrics.SEQUENCER_FILLED_SLOT_COUNT, {
|
|
128
96
|
valueType: ValueType.INT,
|
|
129
97
|
description: 'The number of slots this sequencer has filled',
|
|
130
98
|
});
|
|
131
99
|
|
|
132
|
-
this.missedSlots = this.meter.createUpDownCounter(Metrics.SEQUENCER_MISSED_SLOT_COUNT, {
|
|
133
|
-
valueType: ValueType.INT,
|
|
134
|
-
description: 'The number of slots this sequencer has missed to fill',
|
|
135
|
-
});
|
|
136
|
-
|
|
137
100
|
this.timeToCollectAttestations = this.meter.createGauge(Metrics.SEQUENCER_COLLECT_ATTESTATIONS_DURATION, {
|
|
138
101
|
description: 'The time spent collecting attestations from committee members',
|
|
139
102
|
unit: 'ms',
|
|
@@ -160,28 +123,6 @@ export class SequencerMetrics {
|
|
|
160
123
|
});
|
|
161
124
|
}
|
|
162
125
|
|
|
163
|
-
public setCoinbase(coinbase: EthAddress) {
|
|
164
|
-
this.coinbase = coinbase;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
public start() {
|
|
168
|
-
this.meter.addBatchObservableCallback(this.observe, [this.rewards]);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
public stop() {
|
|
172
|
-
this.meter.removeBatchObservableCallback(this.observe, [this.rewards]);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
private observe = async (observer: BatchObservableResult): Promise<void> => {
|
|
176
|
-
let rewards = 0n;
|
|
177
|
-
rewards = await this.rollup.getSequencerRewards(this.coinbase);
|
|
178
|
-
|
|
179
|
-
const fmt = parseFloat(formatUnits(rewards, 18));
|
|
180
|
-
observer.observe(this.rewards, fmt, {
|
|
181
|
-
[Attributes.COINBASE]: this.coinbase.toString(),
|
|
182
|
-
});
|
|
183
|
-
};
|
|
184
|
-
|
|
185
126
|
public recordRequiredAttestations(requiredAttestationsCount: number, allowanceMs: number) {
|
|
186
127
|
this.requiredAttestions.record(requiredAttestationsCount);
|
|
187
128
|
this.allowanceToCollectAttestations.record(Math.ceil(allowanceMs));
|
|
@@ -196,17 +137,6 @@ export class SequencerMetrics {
|
|
|
196
137
|
this.timeToCollectAttestations.record(Math.ceil(durationMs));
|
|
197
138
|
}
|
|
198
139
|
|
|
199
|
-
recordBlockBuilderTreeInsertions(timeUs: number) {
|
|
200
|
-
this.blockBuilderInsertions.record(Math.ceil(timeUs));
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
recordCancelledBlock() {
|
|
204
|
-
this.blockCounter.add(1, {
|
|
205
|
-
[Attributes.STATUS]: 'cancelled',
|
|
206
|
-
});
|
|
207
|
-
this.setCurrentBlock(0, 0);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
140
|
recordBuiltBlock(buildDurationMs: number, totalMana: number) {
|
|
211
141
|
this.blockCounter.add(1, {
|
|
212
142
|
[Attributes.STATUS]: 'built',
|
|
@@ -219,11 +149,6 @@ export class SequencerMetrics {
|
|
|
219
149
|
this.blockCounter.add(1, {
|
|
220
150
|
[Attributes.STATUS]: 'failed',
|
|
221
151
|
});
|
|
222
|
-
this.setCurrentBlock(0, 0);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
recordNewBlock(blockNumber: number, txCount: number) {
|
|
226
|
-
this.setCurrentBlock(blockNumber, txCount);
|
|
227
152
|
}
|
|
228
153
|
|
|
229
154
|
recordStateTransitionBufferMs(durationMs: number, state: SequencerState) {
|
|
@@ -232,36 +157,35 @@ export class SequencerMetrics {
|
|
|
232
157
|
});
|
|
233
158
|
}
|
|
234
159
|
|
|
235
|
-
|
|
160
|
+
incOpenSlot(slot: bigint, proposer: string) {
|
|
236
161
|
// sequencer went through the loop a second time. Noop
|
|
237
162
|
if (slot === this.lastSeenSlot) {
|
|
238
163
|
return;
|
|
239
164
|
}
|
|
240
165
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (typeof slot === 'bigint') {
|
|
248
|
-
this.slots.add(1, {
|
|
249
|
-
[Attributes.BLOCK_PROPOSER]: proposer,
|
|
250
|
-
});
|
|
251
|
-
}
|
|
166
|
+
this.slots.add(1, {
|
|
167
|
+
[Attributes.BLOCK_PROPOSER]: proposer,
|
|
168
|
+
});
|
|
252
169
|
|
|
253
170
|
this.lastSeenSlot = slot;
|
|
254
171
|
}
|
|
255
172
|
|
|
256
|
-
incFilledSlot(proposer: string) {
|
|
173
|
+
async incFilledSlot(proposer: string, coinbase: Hex | EthAddress | undefined): Promise<void> {
|
|
257
174
|
this.filledSlots.add(1, {
|
|
258
175
|
[Attributes.BLOCK_PROPOSER]: proposer,
|
|
259
176
|
});
|
|
260
177
|
this.lastSeenSlot = undefined;
|
|
261
|
-
}
|
|
262
178
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
179
|
+
if (coinbase) {
|
|
180
|
+
try {
|
|
181
|
+
const rewards = await this.rollup.getSequencerRewards(coinbase);
|
|
182
|
+
const fmt = parseFloat(formatUnits(rewards, 18));
|
|
183
|
+
this.rewards.record(fmt, {
|
|
184
|
+
[Attributes.COINBASE]: coinbase.toString(),
|
|
185
|
+
});
|
|
186
|
+
} catch {
|
|
187
|
+
// no-op
|
|
188
|
+
}
|
|
189
|
+
}
|
|
266
190
|
}
|
|
267
191
|
}
|