@aztec/sequencer-client 2.0.3 → 2.1.0-rc.2
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 +7 -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 +2 -4
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +7 -10
- 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 +14 -20
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +53 -58
- package/dest/sequencer/errors.d.ts +11 -0
- package/dest/sequencer/errors.d.ts.map +1 -0
- package/dest/sequencer/errors.js +15 -0
- 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 +27 -21
- package/dest/sequencer/timetable.d.ts +0 -6
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +2 -9
- package/dest/sequencer/utils.d.ts +10 -24
- package/dest/sequencer/utils.d.ts.map +1 -1
- package/dest/sequencer/utils.js +9 -24
- package/package.json +28 -28
- package/src/client/sequencer-client.ts +6 -2
- package/src/config.ts +3 -0
- package/src/publisher/config.ts +13 -11
- package/src/publisher/index.ts +1 -1
- package/src/publisher/sequencer-publisher-factory.ts +12 -2
- package/src/publisher/sequencer-publisher.ts +77 -77
- package/src/sequencer/errors.ts +21 -0
- package/src/sequencer/metrics.ts +24 -100
- package/src/sequencer/sequencer.ts +51 -46
- package/src/sequencer/timetable.ts +2 -13
- package/src/sequencer/utils.ts +10 -24
|
@@ -5,12 +5,11 @@ import type { EpochCache } from '@aztec/epoch-cache';
|
|
|
5
5
|
import {
|
|
6
6
|
type EmpireSlashingProposerContract,
|
|
7
7
|
FormattedViemError,
|
|
8
|
-
type GasPrice,
|
|
9
8
|
type GovernanceProposerContract,
|
|
10
9
|
type IEmpireBase,
|
|
11
10
|
type L1BlobInputs,
|
|
12
11
|
type L1ContractsConfig,
|
|
13
|
-
type
|
|
12
|
+
type L1TxConfig,
|
|
14
13
|
type L1TxRequest,
|
|
15
14
|
MULTI_CALL_3_ADDRESS,
|
|
16
15
|
Multicall3,
|
|
@@ -27,20 +26,19 @@ import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs'
|
|
|
27
26
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
28
27
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
29
28
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
29
|
+
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
30
30
|
import type { Fr } from '@aztec/foundation/fields';
|
|
31
|
-
import { createLogger } from '@aztec/foundation/log';
|
|
31
|
+
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
32
32
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
33
33
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
34
34
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
35
35
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
36
|
-
import { CommitteeAttestation, type ValidateBlockResult } from '@aztec/stdlib/block';
|
|
36
|
+
import { CommitteeAttestation, CommitteeAttestationsAndSigners, type ValidateBlockResult } from '@aztec/stdlib/block';
|
|
37
37
|
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
38
|
-
import { ConsensusPayload, SignatureDomainSeparator, getHashedSignaturePayload } from '@aztec/stdlib/p2p';
|
|
39
38
|
import type { L1PublishBlockStats } from '@aztec/stdlib/stats';
|
|
40
|
-
import { type ProposedBlockHeader, StateReference
|
|
39
|
+
import { type ProposedBlockHeader, StateReference } from '@aztec/stdlib/tx';
|
|
41
40
|
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
42
41
|
|
|
43
|
-
import pick from 'lodash.pick';
|
|
44
42
|
import { type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
|
|
45
43
|
|
|
46
44
|
import type { PublisherConfig, TxSenderConfig } from './config.js';
|
|
@@ -56,17 +54,12 @@ type L1ProcessArgs = {
|
|
|
56
54
|
stateReference: StateReference;
|
|
57
55
|
/** L2 block blobs containing all tx effects. */
|
|
58
56
|
blobs: Blob[];
|
|
59
|
-
/** L2 block tx hashes */
|
|
60
|
-
txHashes: TxHash[];
|
|
61
57
|
/** Attestations */
|
|
62
|
-
|
|
58
|
+
attestationsAndSigners: CommitteeAttestationsAndSigners;
|
|
59
|
+
/** Attestations and signers signature */
|
|
60
|
+
attestationsAndSignersSignature: Signature;
|
|
63
61
|
};
|
|
64
62
|
|
|
65
|
-
export enum SignalType {
|
|
66
|
-
GOVERNANCE,
|
|
67
|
-
SLASHING,
|
|
68
|
-
}
|
|
69
|
-
|
|
70
63
|
export const Actions = [
|
|
71
64
|
'invalidate-by-invalid-attestation',
|
|
72
65
|
'invalidate-by-insufficient-attestations',
|
|
@@ -78,8 +71,11 @@ export const Actions = [
|
|
|
78
71
|
'vote-offenses',
|
|
79
72
|
'execute-slash',
|
|
80
73
|
] as const;
|
|
74
|
+
|
|
81
75
|
export type Action = (typeof Actions)[number];
|
|
82
76
|
|
|
77
|
+
type GovernanceSignalAction = Extract<Action, 'governance-signal' | 'empire-slashing-signal'>;
|
|
78
|
+
|
|
83
79
|
// Sorting for actions such that invalidations go before proposals, and proposals go before votes
|
|
84
80
|
export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
|
|
85
81
|
|
|
@@ -95,11 +91,11 @@ interface RequestWithExpiry {
|
|
|
95
91
|
action: Action;
|
|
96
92
|
request: L1TxRequest;
|
|
97
93
|
lastValidL2Slot: bigint;
|
|
98
|
-
gasConfig?: Pick<
|
|
94
|
+
gasConfig?: Pick<L1TxConfig, 'txTimeoutAt' | 'gasLimit'>;
|
|
99
95
|
blobConfig?: L1BlobInputs;
|
|
100
96
|
checkSuccess: (
|
|
101
97
|
request: L1TxRequest,
|
|
102
|
-
result?: { receipt: TransactionReceipt;
|
|
98
|
+
result?: { receipt: TransactionReceipt; stats?: TransactionStats; errorMsg?: string },
|
|
103
99
|
) => boolean;
|
|
104
100
|
}
|
|
105
101
|
|
|
@@ -111,12 +107,9 @@ export class SequencerPublisher {
|
|
|
111
107
|
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
112
108
|
protected slashingLog = createLogger('sequencer:publisher:slashing');
|
|
113
109
|
|
|
114
|
-
|
|
115
|
-
[SignalType.GOVERNANCE]: 0n,
|
|
116
|
-
[SignalType.SLASHING]: 0n,
|
|
117
|
-
};
|
|
110
|
+
protected lastActions: Partial<Record<Action, bigint>> = {};
|
|
118
111
|
|
|
119
|
-
protected log
|
|
112
|
+
protected log: Logger;
|
|
120
113
|
protected ethereumSlotDuration: bigint;
|
|
121
114
|
|
|
122
115
|
private blobSinkClient: BlobSinkClientInterface;
|
|
@@ -152,10 +145,14 @@ export class SequencerPublisher {
|
|
|
152
145
|
epochCache: EpochCache;
|
|
153
146
|
dateProvider: DateProvider;
|
|
154
147
|
metrics: SequencerPublisherMetrics;
|
|
148
|
+
lastActions: Partial<Record<Action, bigint>>;
|
|
149
|
+
log?: Logger;
|
|
155
150
|
},
|
|
156
151
|
) {
|
|
152
|
+
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
157
153
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
158
154
|
this.epochCache = deps.epochCache;
|
|
155
|
+
this.lastActions = deps.lastActions;
|
|
159
156
|
|
|
160
157
|
this.blobSinkClient =
|
|
161
158
|
deps.blobSinkClient ?? createBlobSinkClient(config, { logger: createLogger('sequencer:blob-sink:client') });
|
|
@@ -249,18 +246,21 @@ export class SequencerPublisher {
|
|
|
249
246
|
const gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
|
|
250
247
|
const txTimeoutAts = gasConfigs.map(g => g?.txTimeoutAt).filter((g): g is Date => g !== undefined);
|
|
251
248
|
const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map(g => g.getTime()))) : undefined; // earliest
|
|
252
|
-
const
|
|
249
|
+
const txConfig: RequestWithExpiry['gasConfig'] = { gasLimit, txTimeoutAt };
|
|
253
250
|
|
|
254
251
|
// Sort the requests so that proposals always go first
|
|
255
252
|
// This ensures the committee gets precomputed correctly
|
|
256
253
|
validRequests.sort((a, b) => compareActions(a.action, b.action));
|
|
257
254
|
|
|
258
255
|
try {
|
|
259
|
-
this.log.debug('Forwarding transactions', {
|
|
256
|
+
this.log.debug('Forwarding transactions', {
|
|
257
|
+
validRequests: validRequests.map(request => request.action),
|
|
258
|
+
txConfig,
|
|
259
|
+
});
|
|
260
260
|
const result = await Multicall3.forward(
|
|
261
261
|
validRequests.map(request => request.request),
|
|
262
262
|
this.l1TxUtils,
|
|
263
|
-
|
|
263
|
+
txConfig,
|
|
264
264
|
blobConfig,
|
|
265
265
|
this.rollupContract.address,
|
|
266
266
|
this.log,
|
|
@@ -285,7 +285,7 @@ export class SequencerPublisher {
|
|
|
285
285
|
|
|
286
286
|
private callbackBundledTransactions(
|
|
287
287
|
requests: RequestWithExpiry[],
|
|
288
|
-
result?: { receipt: TransactionReceipt
|
|
288
|
+
result?: { receipt: TransactionReceipt } | FormattedViemError,
|
|
289
289
|
) {
|
|
290
290
|
const actionsListStr = requests.map(r => r.action).join(', ');
|
|
291
291
|
if (result instanceof FormattedViemError) {
|
|
@@ -346,8 +346,9 @@ export class SequencerPublisher {
|
|
|
346
346
|
|
|
347
347
|
const args = [
|
|
348
348
|
header.toViem(),
|
|
349
|
-
|
|
349
|
+
CommitteeAttestationsAndSigners.empty().getPackedAttestations(),
|
|
350
350
|
[], // no signers
|
|
351
|
+
Signature.empty().toViemSignature(),
|
|
351
352
|
`0x${'0'.repeat(64)}`, // 32 empty bytes
|
|
352
353
|
header.contentCommitment.blobsHash.toString(),
|
|
353
354
|
flags,
|
|
@@ -446,17 +447,19 @@ export class SequencerPublisher {
|
|
|
446
447
|
const logData = { ...block, reason };
|
|
447
448
|
this.log.debug(`Simulating invalidate block ${block.blockNumber}`, logData);
|
|
448
449
|
|
|
450
|
+
const attestationsAndSigners = new CommitteeAttestationsAndSigners(attestations).getPackedAttestations();
|
|
451
|
+
|
|
449
452
|
if (reason === 'invalid-attestation') {
|
|
450
453
|
return this.rollupContract.buildInvalidateBadAttestationRequest(
|
|
451
454
|
block.blockNumber,
|
|
452
|
-
|
|
455
|
+
attestationsAndSigners,
|
|
453
456
|
committee,
|
|
454
457
|
validationResult.invalidIndex,
|
|
455
458
|
);
|
|
456
459
|
} else if (reason === 'insufficient-attestations') {
|
|
457
460
|
return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
|
|
458
461
|
block.blockNumber,
|
|
459
|
-
|
|
462
|
+
attestationsAndSigners,
|
|
460
463
|
committee,
|
|
461
464
|
);
|
|
462
465
|
} else {
|
|
@@ -476,24 +479,22 @@ export class SequencerPublisher {
|
|
|
476
479
|
*/
|
|
477
480
|
public async validateBlockForSubmission(
|
|
478
481
|
block: L2Block,
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
attestations: [],
|
|
482
|
-
},
|
|
482
|
+
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
483
|
+
attestationsAndSignersSignature: Signature,
|
|
483
484
|
options: { forcePendingBlockNumber?: number },
|
|
484
485
|
): Promise<bigint> {
|
|
485
486
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
486
487
|
|
|
487
488
|
// If we have no attestations, we still need to provide the empty attestations
|
|
488
489
|
// so that the committee is recalculated correctly
|
|
489
|
-
const ignoreSignatures =
|
|
490
|
+
const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
490
491
|
if (ignoreSignatures) {
|
|
491
492
|
const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber.toBigInt());
|
|
492
493
|
if (!committee) {
|
|
493
494
|
this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber.toBigInt()}`);
|
|
494
495
|
throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber.toBigInt()}`);
|
|
495
496
|
}
|
|
496
|
-
|
|
497
|
+
attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
497
498
|
CommitteeAttestation.fromAddress(committeeMember),
|
|
498
499
|
);
|
|
499
500
|
}
|
|
@@ -501,23 +502,18 @@ export class SequencerPublisher {
|
|
|
501
502
|
const blobs = await Blob.getBlobsPerBlock(block.body.toBlobFields());
|
|
502
503
|
const blobInput = Blob.getPrefixedEthBlobCommitments(blobs);
|
|
503
504
|
|
|
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
505
|
const args = [
|
|
510
506
|
{
|
|
511
507
|
header: block.header.toPropose().toViem(),
|
|
512
508
|
archive: toHex(block.archive.root.toBuffer()),
|
|
513
509
|
stateReference: block.header.state.toViem(),
|
|
514
|
-
txHashes: block.body.txEffects.map(txEffect => txEffect.txHash.toString()),
|
|
515
510
|
oracleInput: {
|
|
516
511
|
feeAssetPriceModifier: 0n,
|
|
517
512
|
},
|
|
518
513
|
},
|
|
519
|
-
|
|
520
|
-
|
|
514
|
+
attestationsAndSigners.getPackedAttestations(),
|
|
515
|
+
attestationsAndSigners.getSigners().map(signer => signer.toString()),
|
|
516
|
+
attestationsAndSignersSignature.toViemSignature(),
|
|
521
517
|
blobInput,
|
|
522
518
|
] as const;
|
|
523
519
|
|
|
@@ -528,13 +524,14 @@ export class SequencerPublisher {
|
|
|
528
524
|
private async enqueueCastSignalHelper(
|
|
529
525
|
slotNumber: bigint,
|
|
530
526
|
timestamp: bigint,
|
|
531
|
-
signalType:
|
|
527
|
+
signalType: GovernanceSignalAction,
|
|
532
528
|
payload: EthAddress,
|
|
533
529
|
base: IEmpireBase,
|
|
534
530
|
signerAddress: EthAddress,
|
|
535
531
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
536
532
|
): Promise<boolean> {
|
|
537
|
-
if (this.
|
|
533
|
+
if (this.lastActions[signalType] && this.lastActions[signalType] === slotNumber) {
|
|
534
|
+
this.log.debug(`Skipping duplicate vote cast signal ${signalType} for slot ${slotNumber}`);
|
|
538
535
|
return false;
|
|
539
536
|
}
|
|
540
537
|
if (payload.equals(EthAddress.ZERO)) {
|
|
@@ -551,10 +548,9 @@ export class SequencerPublisher {
|
|
|
551
548
|
return false;
|
|
552
549
|
}
|
|
553
550
|
|
|
554
|
-
const cachedLastVote = this.
|
|
555
|
-
this.
|
|
556
|
-
|
|
557
|
-
const action = signalType === SignalType.GOVERNANCE ? 'governance-signal' : 'empire-slashing-signal';
|
|
551
|
+
const cachedLastVote = this.lastActions[signalType];
|
|
552
|
+
this.lastActions[signalType] = slotNumber;
|
|
553
|
+
const action = signalType;
|
|
558
554
|
|
|
559
555
|
const request = await base.createSignalRequestWithSignature(
|
|
560
556
|
payload.toString(),
|
|
@@ -597,7 +593,7 @@ export class SequencerPublisher {
|
|
|
597
593
|
`Signaling in [${action}] for ${payload} at slot ${slotNumber} in round ${round} failed`,
|
|
598
594
|
logData,
|
|
599
595
|
);
|
|
600
|
-
this.
|
|
596
|
+
this.lastActions[signalType] = cachedLastVote;
|
|
601
597
|
return false;
|
|
602
598
|
} else {
|
|
603
599
|
this.log.info(
|
|
@@ -627,7 +623,7 @@ export class SequencerPublisher {
|
|
|
627
623
|
return this.enqueueCastSignalHelper(
|
|
628
624
|
slotNumber,
|
|
629
625
|
timestamp,
|
|
630
|
-
|
|
626
|
+
'governance-signal',
|
|
631
627
|
governancePayload,
|
|
632
628
|
this.govProposerContract,
|
|
633
629
|
signerAddress,
|
|
@@ -661,7 +657,7 @@ export class SequencerPublisher {
|
|
|
661
657
|
await this.enqueueCastSignalHelper(
|
|
662
658
|
slotNumber,
|
|
663
659
|
timestamp,
|
|
664
|
-
|
|
660
|
+
'empire-slashing-signal',
|
|
665
661
|
action.payload,
|
|
666
662
|
this.slashingProposerContract,
|
|
667
663
|
signerAddress,
|
|
@@ -766,15 +762,12 @@ export class SequencerPublisher {
|
|
|
766
762
|
*/
|
|
767
763
|
public async enqueueProposeL2Block(
|
|
768
764
|
block: L2Block,
|
|
769
|
-
|
|
770
|
-
|
|
765
|
+
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
766
|
+
attestationsAndSignersSignature: Signature,
|
|
771
767
|
opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: number } = {},
|
|
772
768
|
): Promise<boolean> {
|
|
773
769
|
const proposedBlockHeader = block.header.toPropose();
|
|
774
770
|
|
|
775
|
-
const consensusPayload = ConsensusPayload.fromBlock(block);
|
|
776
|
-
const digest = getHashedSignaturePayload(consensusPayload, SignatureDomainSeparator.blockAttestation);
|
|
777
|
-
|
|
778
771
|
const blobs = await Blob.getBlobsPerBlock(block.body.toBlobFields());
|
|
779
772
|
const proposeTxArgs = {
|
|
780
773
|
header: proposedBlockHeader,
|
|
@@ -782,8 +775,8 @@ export class SequencerPublisher {
|
|
|
782
775
|
stateReference: block.header.state,
|
|
783
776
|
body: block.body.toBuffer(),
|
|
784
777
|
blobs,
|
|
785
|
-
|
|
786
|
-
|
|
778
|
+
attestationsAndSigners,
|
|
779
|
+
attestationsAndSignersSignature,
|
|
787
780
|
};
|
|
788
781
|
|
|
789
782
|
let ts: bigint;
|
|
@@ -793,9 +786,8 @@ export class SequencerPublisher {
|
|
|
793
786
|
// This means that we can avoid the simulation issues in later checks.
|
|
794
787
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
795
788
|
// make time consistency checks break.
|
|
796
|
-
const attestationData = { digest: digest.toBuffer(), attestations: attestations ?? [] };
|
|
797
789
|
// 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,
|
|
790
|
+
ts = await this.validateBlockForSubmission(block, attestationsAndSigners, attestationsAndSignersSignature, opts);
|
|
799
791
|
} catch (err: any) {
|
|
800
792
|
this.log.error(`Block validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
801
793
|
...block.getStats(),
|
|
@@ -818,7 +810,8 @@ export class SequencerPublisher {
|
|
|
818
810
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
819
811
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(request.gasUsed) * 64) / 63)));
|
|
820
812
|
|
|
821
|
-
const
|
|
813
|
+
const { gasUsed, blockNumber } = request;
|
|
814
|
+
const logData = { gasUsed, blockNumber, gasLimit, opts };
|
|
822
815
|
this.log.verbose(`Enqueuing invalidate block request`, logData);
|
|
823
816
|
this.addRequest({
|
|
824
817
|
action: `invalidate-by-${request.reason}`,
|
|
@@ -842,16 +835,24 @@ export class SequencerPublisher {
|
|
|
842
835
|
}
|
|
843
836
|
|
|
844
837
|
private async simulateAndEnqueueRequest(
|
|
845
|
-
action:
|
|
838
|
+
action: Action,
|
|
846
839
|
request: L1TxRequest,
|
|
847
840
|
checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
|
|
848
841
|
slotNumber: bigint,
|
|
849
842
|
timestamp: bigint,
|
|
850
843
|
) {
|
|
851
844
|
const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
|
|
852
|
-
|
|
845
|
+
if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
|
|
846
|
+
this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
|
|
847
|
+
return false;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
const cachedLastActionSlot = this.lastActions[action];
|
|
851
|
+
this.lastActions[action] = slotNumber;
|
|
853
852
|
|
|
854
|
-
this.log.debug(`Simulating ${action}`, logData);
|
|
853
|
+
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
854
|
+
|
|
855
|
+
let gasUsed: bigint;
|
|
855
856
|
try {
|
|
856
857
|
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], ErrorsAbi)); // TODO(palla/slash): Check the timestamp logic
|
|
857
858
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
@@ -875,6 +876,7 @@ export class SequencerPublisher {
|
|
|
875
876
|
const success = result && result.receipt && result.receipt.status === 'success' && checkSuccess(result.receipt);
|
|
876
877
|
if (!success) {
|
|
877
878
|
this.log.warn(`Action ${action} at ${slotNumber} failed`, { ...result, ...logData });
|
|
879
|
+
this.lastActions[action] = cachedLastActionSlot;
|
|
878
880
|
} else {
|
|
879
881
|
this.log.info(`Action ${action} at ${slotNumber} succeeded`, { ...result, ...logData });
|
|
880
882
|
}
|
|
@@ -932,12 +934,7 @@ export class SequencerPublisher {
|
|
|
932
934
|
throw new Error('Failed to validate blobs');
|
|
933
935
|
});
|
|
934
936
|
|
|
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());
|
|
937
|
+
const signers = encodedData.attestationsAndSigners.getSigners().map(signer => signer.toString());
|
|
941
938
|
|
|
942
939
|
const args = [
|
|
943
940
|
{
|
|
@@ -948,10 +945,10 @@ export class SequencerPublisher {
|
|
|
948
945
|
// We are currently not modifying these. See #9963
|
|
949
946
|
feeAssetPriceModifier: 0n,
|
|
950
947
|
},
|
|
951
|
-
txHashes,
|
|
952
948
|
},
|
|
953
|
-
|
|
954
|
-
signers
|
|
949
|
+
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
950
|
+
signers,
|
|
951
|
+
encodedData.attestationsAndSignersSignature.toViemSignature(),
|
|
955
952
|
blobInput,
|
|
956
953
|
] as const;
|
|
957
954
|
|
|
@@ -972,13 +969,13 @@ export class SequencerPublisher {
|
|
|
972
969
|
readonly header: ViemHeader;
|
|
973
970
|
readonly archive: `0x${string}`;
|
|
974
971
|
readonly stateReference: ViemStateReference;
|
|
975
|
-
readonly txHashes: `0x${string}`[];
|
|
976
972
|
readonly oracleInput: {
|
|
977
973
|
readonly feeAssetPriceModifier: 0n;
|
|
978
974
|
};
|
|
979
975
|
},
|
|
980
976
|
ViemCommitteeAttestations,
|
|
981
|
-
`0x${string}`[],
|
|
977
|
+
`0x${string}`[], // Signers
|
|
978
|
+
ViemSignature,
|
|
982
979
|
`0x${string}`,
|
|
983
980
|
],
|
|
984
981
|
timestamp: bigint,
|
|
@@ -1084,13 +1081,16 @@ export class SequencerPublisher {
|
|
|
1084
1081
|
if (success) {
|
|
1085
1082
|
const endBlock = receipt.blockNumber;
|
|
1086
1083
|
const inclusionBlocks = Number(endBlock - startBlock);
|
|
1084
|
+
const { calldataGas, calldataSize, sender } = stats!;
|
|
1087
1085
|
const publishStats: L1PublishBlockStats = {
|
|
1088
1086
|
gasPrice: receipt.effectiveGasPrice,
|
|
1089
1087
|
gasUsed: receipt.gasUsed,
|
|
1090
1088
|
blobGasUsed: receipt.blobGasUsed ?? 0n,
|
|
1091
1089
|
blobDataGas: receipt.blobGasPrice ?? 0n,
|
|
1092
1090
|
transactionHash: receipt.transactionHash,
|
|
1093
|
-
|
|
1091
|
+
calldataGas,
|
|
1092
|
+
calldataSize,
|
|
1093
|
+
sender,
|
|
1094
1094
|
...block.getStats(),
|
|
1095
1095
|
eventName: 'rollup-published-to-l1',
|
|
1096
1096
|
blobCount: encodedData.blobs.length,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SequencerState } from './utils.js';
|
|
2
|
+
|
|
3
|
+
export class SequencerTooSlowError extends Error {
|
|
4
|
+
constructor(
|
|
5
|
+
public readonly proposedState: SequencerState,
|
|
6
|
+
public readonly maxAllowedTime: number,
|
|
7
|
+
public readonly currentTime: number,
|
|
8
|
+
) {
|
|
9
|
+
super(
|
|
10
|
+
`Too far into slot for ${proposedState} (time into slot ${currentTime}s greater than ${maxAllowedTime}s allowance)`,
|
|
11
|
+
);
|
|
12
|
+
this.name = 'SequencerTooSlowError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class SequencerInterruptedError extends Error {
|
|
17
|
+
constructor() {
|
|
18
|
+
super(`Sequencer was interrupted`);
|
|
19
|
+
this.name = 'SequencerInterruptedError';
|
|
20
|
+
}
|
|
21
|
+
}
|
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
|
}
|