@aztec/sequencer-client 0.0.1-commit.8f9871590 → 0.0.1-commit.934299a21
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 +35 -17
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +106 -42
- package/dest/publisher/index.d.ts +2 -1
- package/dest/publisher/index.d.ts.map +1 -1
- package/dest/publisher/l1_tx_failed_store/factory.d.ts +11 -0
- package/dest/publisher/l1_tx_failed_store/factory.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/factory.js +22 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +59 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +1 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +15 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +34 -0
- package/dest/publisher/l1_tx_failed_store/index.d.ts +4 -0
- package/dest/publisher/l1_tx_failed_store/index.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/index.js +2 -0
- package/dest/publisher/sequencer-publisher-factory.d.ts +11 -3
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +13 -2
- package/dest/publisher/sequencer-publisher.d.ts +15 -7
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +244 -24
- 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 +34 -8
- package/dest/sequencer/metrics.d.ts +13 -5
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +32 -10
- package/dest/sequencer/sequencer.d.ts +15 -7
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +23 -25
- package/dest/test/index.d.ts +3 -5
- package/dest/test/index.d.ts.map +1 -1
- 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 +121 -43
- package/src/publisher/index.ts +3 -0
- package/src/publisher/l1_tx_failed_store/factory.ts +32 -0
- package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +55 -0
- package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +46 -0
- package/src/publisher/l1_tx_failed_store/index.ts +3 -0
- package/src/publisher/sequencer-publisher-factory.ts +23 -6
- package/src/publisher/sequencer-publisher.ts +214 -31
- package/src/sequencer/checkpoint_proposal_job.ts +53 -9
- package/src/sequencer/metrics.ts +39 -13
- package/src/sequencer/sequencer.ts +31 -30
- package/src/test/index.ts +2 -4
|
@@ -19,11 +19,11 @@ import {
|
|
|
19
19
|
type L1BlobInputs,
|
|
20
20
|
type L1TxConfig,
|
|
21
21
|
type L1TxRequest,
|
|
22
|
+
type L1TxUtils,
|
|
22
23
|
MAX_L1_TX_LIMIT,
|
|
23
24
|
type TransactionStats,
|
|
24
25
|
WEI_CONST,
|
|
25
26
|
} from '@aztec/ethereum/l1-tx-utils';
|
|
26
|
-
import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs';
|
|
27
27
|
import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
28
28
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
29
29
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
@@ -33,6 +33,7 @@ import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
|
33
33
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
34
34
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
35
35
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
36
|
+
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
36
37
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
37
38
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
38
39
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
@@ -44,9 +45,19 @@ import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
|
44
45
|
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
45
46
|
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
46
47
|
|
|
47
|
-
import {
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
import {
|
|
49
|
+
type Hex,
|
|
50
|
+
type StateOverride,
|
|
51
|
+
type TransactionReceipt,
|
|
52
|
+
type TypedDataDefinition,
|
|
53
|
+
encodeFunctionData,
|
|
54
|
+
keccak256,
|
|
55
|
+
multicall3Abi,
|
|
56
|
+
toHex,
|
|
57
|
+
} from 'viem';
|
|
58
|
+
|
|
59
|
+
import type { SequencerPublisherConfig } from './config.js';
|
|
60
|
+
import { type FailedL1Tx, type L1TxFailedStore, createL1TxFailedStore } from './l1_tx_failed_store/index.js';
|
|
50
61
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
51
62
|
|
|
52
63
|
/** Arguments to the process method of the rollup contract */
|
|
@@ -108,6 +119,7 @@ export class SequencerPublisher {
|
|
|
108
119
|
private interrupted = false;
|
|
109
120
|
private metrics: SequencerPublisherMetrics;
|
|
110
121
|
public epochCache: EpochCache;
|
|
122
|
+
private failedTxStore?: Promise<L1TxFailedStore | undefined>;
|
|
111
123
|
|
|
112
124
|
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
113
125
|
protected slashingLog = createLogger('sequencer:publisher:slashing');
|
|
@@ -115,6 +127,7 @@ export class SequencerPublisher {
|
|
|
115
127
|
protected lastActions: Partial<Record<Action, SlotNumber>> = {};
|
|
116
128
|
|
|
117
129
|
private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
|
|
130
|
+
private payloadProposedCache: Set<string> = new Set<string>();
|
|
118
131
|
|
|
119
132
|
protected log: Logger;
|
|
120
133
|
protected ethereumSlotDuration: bigint;
|
|
@@ -136,7 +149,7 @@ export class SequencerPublisher {
|
|
|
136
149
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
137
150
|
public static VOTE_GAS_GUESS: bigint = 800_000n;
|
|
138
151
|
|
|
139
|
-
public l1TxUtils:
|
|
152
|
+
public l1TxUtils: L1TxUtils;
|
|
140
153
|
public rollupContract: RollupContract;
|
|
141
154
|
public govProposerContract: GovernanceProposerContract;
|
|
142
155
|
public slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
@@ -147,11 +160,12 @@ export class SequencerPublisher {
|
|
|
147
160
|
protected requests: RequestWithExpiry[] = [];
|
|
148
161
|
|
|
149
162
|
constructor(
|
|
150
|
-
private config:
|
|
163
|
+
private config: Pick<SequencerPublisherConfig, 'fishermanMode' | 'l1TxFailedStore'> &
|
|
164
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration'> & { l1ChainId: number },
|
|
151
165
|
deps: {
|
|
152
166
|
telemetry?: TelemetryClient;
|
|
153
167
|
blobClient: BlobClientInterface;
|
|
154
|
-
l1TxUtils:
|
|
168
|
+
l1TxUtils: L1TxUtils;
|
|
155
169
|
rollupContract: RollupContract;
|
|
156
170
|
slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
157
171
|
governanceProposerContract: GovernanceProposerContract;
|
|
@@ -202,6 +216,31 @@ export class SequencerPublisher {
|
|
|
202
216
|
this.rollupContract,
|
|
203
217
|
createLogger('sequencer:publisher:price-oracle'),
|
|
204
218
|
);
|
|
219
|
+
|
|
220
|
+
// Initialize failed L1 tx store (optional, for test networks)
|
|
221
|
+
this.failedTxStore = createL1TxFailedStore(config.l1TxFailedStore, this.log);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Backs up a failed L1 transaction to the configured store for debugging.
|
|
226
|
+
* Does nothing if no store is configured.
|
|
227
|
+
*/
|
|
228
|
+
private backupFailedTx(failedTx: Omit<FailedL1Tx, 'timestamp'>): void {
|
|
229
|
+
if (!this.failedTxStore) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const tx: FailedL1Tx = {
|
|
234
|
+
...failedTx,
|
|
235
|
+
timestamp: Date.now(),
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Fire and forget - don't block on backup
|
|
239
|
+
void this.failedTxStore
|
|
240
|
+
.then(store => store?.saveFailedTx(tx))
|
|
241
|
+
.catch(err => {
|
|
242
|
+
this.log.warn(`Failed to backup failed L1 tx to store`, err);
|
|
243
|
+
});
|
|
205
244
|
}
|
|
206
245
|
|
|
207
246
|
public getRollupContract(): RollupContract {
|
|
@@ -383,6 +422,21 @@ export class SequencerPublisher {
|
|
|
383
422
|
validRequests.sort((a, b) => compareActions(a.action, b.action));
|
|
384
423
|
|
|
385
424
|
try {
|
|
425
|
+
// Capture context for failed tx backup before sending
|
|
426
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
427
|
+
const multicallData = encodeFunctionData({
|
|
428
|
+
abi: multicall3Abi,
|
|
429
|
+
functionName: 'aggregate3',
|
|
430
|
+
args: [
|
|
431
|
+
validRequests.map(r => ({
|
|
432
|
+
target: r.request.to!,
|
|
433
|
+
callData: r.request.data!,
|
|
434
|
+
allowFailure: true,
|
|
435
|
+
})),
|
|
436
|
+
],
|
|
437
|
+
});
|
|
438
|
+
const blobDataHex = blobConfig?.blobs?.map(b => toHex(b)) as Hex[] | undefined;
|
|
439
|
+
|
|
386
440
|
this.log.debug('Forwarding transactions', {
|
|
387
441
|
validRequests: validRequests.map(request => request.action),
|
|
388
442
|
txConfig,
|
|
@@ -395,7 +449,12 @@ export class SequencerPublisher {
|
|
|
395
449
|
this.rollupContract.address,
|
|
396
450
|
this.log,
|
|
397
451
|
);
|
|
398
|
-
const
|
|
452
|
+
const txContext = { multicallData, blobData: blobDataHex, l1BlockNumber };
|
|
453
|
+
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(
|
|
454
|
+
validRequests,
|
|
455
|
+
result,
|
|
456
|
+
txContext,
|
|
457
|
+
);
|
|
399
458
|
return { result, expiredActions, sentActions: validActions, successfulActions, failedActions };
|
|
400
459
|
} catch (err) {
|
|
401
460
|
const viemError = formatViemError(err);
|
|
@@ -415,11 +474,25 @@ export class SequencerPublisher {
|
|
|
415
474
|
|
|
416
475
|
private callbackBundledTransactions(
|
|
417
476
|
requests: RequestWithExpiry[],
|
|
418
|
-
result
|
|
477
|
+
result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError | undefined,
|
|
478
|
+
txContext: { multicallData: Hex; blobData?: Hex[]; l1BlockNumber: bigint },
|
|
419
479
|
) {
|
|
420
480
|
const actionsListStr = requests.map(r => r.action).join(', ');
|
|
421
481
|
if (result instanceof FormattedViemError) {
|
|
422
482
|
this.log.error(`Failed to publish bundled transactions (${actionsListStr})`, result);
|
|
483
|
+
this.backupFailedTx({
|
|
484
|
+
id: keccak256(txContext.multicallData),
|
|
485
|
+
failureType: 'send-error',
|
|
486
|
+
request: { to: MULTI_CALL_3_ADDRESS, data: txContext.multicallData },
|
|
487
|
+
blobData: txContext.blobData,
|
|
488
|
+
l1BlockNumber: txContext.l1BlockNumber.toString(),
|
|
489
|
+
error: { message: result.message, name: result.name },
|
|
490
|
+
context: {
|
|
491
|
+
actions: requests.map(r => r.action),
|
|
492
|
+
requests: requests.map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
|
|
493
|
+
sender: this.getSenderAddress().toString(),
|
|
494
|
+
},
|
|
495
|
+
});
|
|
423
496
|
return { failedActions: requests.map(r => r.action) };
|
|
424
497
|
} else {
|
|
425
498
|
this.log.verbose(`Published bundled transactions (${actionsListStr})`, { result, requests });
|
|
@@ -432,6 +505,30 @@ export class SequencerPublisher {
|
|
|
432
505
|
failedActions.push(request.action);
|
|
433
506
|
}
|
|
434
507
|
}
|
|
508
|
+
// Single backup for the whole reverted tx
|
|
509
|
+
if (failedActions.length > 0 && result?.receipt?.status === 'reverted') {
|
|
510
|
+
this.backupFailedTx({
|
|
511
|
+
id: result.receipt.transactionHash,
|
|
512
|
+
failureType: 'revert',
|
|
513
|
+
request: { to: MULTI_CALL_3_ADDRESS, data: txContext.multicallData },
|
|
514
|
+
blobData: txContext.blobData,
|
|
515
|
+
l1BlockNumber: result.receipt.blockNumber.toString(),
|
|
516
|
+
receipt: {
|
|
517
|
+
transactionHash: result.receipt.transactionHash,
|
|
518
|
+
blockNumber: result.receipt.blockNumber.toString(),
|
|
519
|
+
gasUsed: (result.receipt.gasUsed ?? 0n).toString(),
|
|
520
|
+
status: 'reverted',
|
|
521
|
+
},
|
|
522
|
+
error: { message: result.errorMsg ?? 'Transaction reverted' },
|
|
523
|
+
context: {
|
|
524
|
+
actions: failedActions,
|
|
525
|
+
requests: requests
|
|
526
|
+
.filter(r => failedActions.includes(r.action))
|
|
527
|
+
.map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
|
|
528
|
+
sender: this.getSenderAddress().toString(),
|
|
529
|
+
},
|
|
530
|
+
});
|
|
531
|
+
}
|
|
435
532
|
return { successfulActions, failedActions };
|
|
436
533
|
}
|
|
437
534
|
}
|
|
@@ -543,6 +640,8 @@ export class SequencerPublisher {
|
|
|
543
640
|
const request = this.buildInvalidateCheckpointRequest(validationResult);
|
|
544
641
|
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
|
|
545
642
|
|
|
643
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
644
|
+
|
|
546
645
|
try {
|
|
547
646
|
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
548
647
|
request,
|
|
@@ -594,6 +693,18 @@ export class SequencerPublisher {
|
|
|
594
693
|
|
|
595
694
|
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
596
695
|
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
|
|
696
|
+
this.backupFailedTx({
|
|
697
|
+
id: keccak256(request.data!),
|
|
698
|
+
failureType: 'simulation',
|
|
699
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
700
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
701
|
+
error: { message: viemError.message, name: viemError.name },
|
|
702
|
+
context: {
|
|
703
|
+
actions: [`invalidate-${reason}`],
|
|
704
|
+
checkpointNumber,
|
|
705
|
+
sender: this.getSenderAddress().toString(),
|
|
706
|
+
},
|
|
707
|
+
});
|
|
597
708
|
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
|
|
598
709
|
}
|
|
599
710
|
}
|
|
@@ -639,24 +750,8 @@ export class SequencerPublisher {
|
|
|
639
750
|
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
640
751
|
): Promise<bigint> {
|
|
641
752
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
642
|
-
|
|
643
|
-
// TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
|
|
644
|
-
// If we have no attestations, we still need to provide the empty attestations
|
|
645
|
-
// so that the committee is recalculated correctly
|
|
646
|
-
// const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
647
|
-
// if (ignoreSignatures) {
|
|
648
|
-
// const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
|
|
649
|
-
// if (!committee) {
|
|
650
|
-
// this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
651
|
-
// throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
652
|
-
// }
|
|
653
|
-
// attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
654
|
-
// CommitteeAttestation.fromAddress(committeeMember),
|
|
655
|
-
// );
|
|
656
|
-
// }
|
|
657
|
-
|
|
658
753
|
const blobFields = checkpoint.toBlobFields();
|
|
659
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
754
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
660
755
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
661
756
|
|
|
662
757
|
const args = [
|
|
@@ -713,6 +808,32 @@ export class SequencerPublisher {
|
|
|
713
808
|
return false;
|
|
714
809
|
}
|
|
715
810
|
|
|
811
|
+
// Check if payload was already submitted to governance
|
|
812
|
+
const cacheKey = payload.toString();
|
|
813
|
+
if (!this.payloadProposedCache.has(cacheKey)) {
|
|
814
|
+
try {
|
|
815
|
+
const l1StartBlock = await this.rollupContract.getL1StartBlock();
|
|
816
|
+
const proposed = await retry(
|
|
817
|
+
() => base.hasPayloadBeenProposed(payload.toString(), l1StartBlock),
|
|
818
|
+
'Check if payload was proposed',
|
|
819
|
+
makeBackoff([0, 1, 2]),
|
|
820
|
+
this.log,
|
|
821
|
+
true,
|
|
822
|
+
);
|
|
823
|
+
if (proposed) {
|
|
824
|
+
this.payloadProposedCache.add(cacheKey);
|
|
825
|
+
}
|
|
826
|
+
} catch (err) {
|
|
827
|
+
this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
|
|
828
|
+
return false;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
if (this.payloadProposedCache.has(cacheKey)) {
|
|
833
|
+
this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
|
|
834
|
+
return false;
|
|
835
|
+
}
|
|
836
|
+
|
|
716
837
|
const cachedLastVote = this.lastActions[signalType];
|
|
717
838
|
this.lastActions[signalType] = slotNumber;
|
|
718
839
|
const action = signalType;
|
|
@@ -731,11 +852,26 @@ export class SequencerPublisher {
|
|
|
731
852
|
lastValidL2Slot: slotNumber,
|
|
732
853
|
});
|
|
733
854
|
|
|
855
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
856
|
+
|
|
734
857
|
try {
|
|
735
858
|
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
736
859
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
737
860
|
} catch (err) {
|
|
738
|
-
|
|
861
|
+
const viemError = formatViemError(err);
|
|
862
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError);
|
|
863
|
+
this.backupFailedTx({
|
|
864
|
+
id: keccak256(request.data!),
|
|
865
|
+
failureType: 'simulation',
|
|
866
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
867
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
868
|
+
error: { message: viemError.message, name: viemError.name },
|
|
869
|
+
context: {
|
|
870
|
+
actions: [action],
|
|
871
|
+
slot: slotNumber,
|
|
872
|
+
sender: this.getSenderAddress().toString(),
|
|
873
|
+
},
|
|
874
|
+
});
|
|
739
875
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
740
876
|
}
|
|
741
877
|
|
|
@@ -940,7 +1076,7 @@ export class SequencerPublisher {
|
|
|
940
1076
|
const checkpointHeader = checkpoint.header;
|
|
941
1077
|
|
|
942
1078
|
const blobFields = checkpoint.toBlobFields();
|
|
943
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
1079
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
944
1080
|
|
|
945
1081
|
const proposeTxArgs: L1ProcessArgs = {
|
|
946
1082
|
header: checkpointHeader,
|
|
@@ -1031,6 +1167,8 @@ export class SequencerPublisher {
|
|
|
1031
1167
|
|
|
1032
1168
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
1033
1169
|
|
|
1170
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1171
|
+
|
|
1034
1172
|
let gasUsed: bigint;
|
|
1035
1173
|
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
1036
1174
|
try {
|
|
@@ -1040,6 +1178,19 @@ export class SequencerPublisher {
|
|
|
1040
1178
|
const viemError = formatViemError(err, simulateAbi);
|
|
1041
1179
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1042
1180
|
|
|
1181
|
+
this.backupFailedTx({
|
|
1182
|
+
id: keccak256(request.data!),
|
|
1183
|
+
failureType: 'simulation',
|
|
1184
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
1185
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1186
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1187
|
+
context: {
|
|
1188
|
+
actions: [action],
|
|
1189
|
+
slot: slotNumber,
|
|
1190
|
+
sender: this.getSenderAddress().toString(),
|
|
1191
|
+
},
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1043
1194
|
return false;
|
|
1044
1195
|
}
|
|
1045
1196
|
|
|
@@ -1123,9 +1274,27 @@ export class SequencerPublisher {
|
|
|
1123
1274
|
kzg,
|
|
1124
1275
|
},
|
|
1125
1276
|
)
|
|
1126
|
-
.catch(err => {
|
|
1127
|
-
const
|
|
1128
|
-
this.log.error(`Failed to validate blobs`, message, { metaMessages });
|
|
1277
|
+
.catch(async err => {
|
|
1278
|
+
const viemError = formatViemError(err);
|
|
1279
|
+
this.log.error(`Failed to validate blobs`, viemError.message, { metaMessages: viemError.metaMessages });
|
|
1280
|
+
const validateBlobsData = encodeFunctionData({
|
|
1281
|
+
abi: RollupAbi,
|
|
1282
|
+
functionName: 'validateBlobs',
|
|
1283
|
+
args: [blobInput],
|
|
1284
|
+
});
|
|
1285
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1286
|
+
this.backupFailedTx({
|
|
1287
|
+
id: keccak256(validateBlobsData),
|
|
1288
|
+
failureType: 'simulation',
|
|
1289
|
+
request: { to: this.rollupContract.address as Hex, data: validateBlobsData },
|
|
1290
|
+
blobData: encodedData.blobs.map(b => toHex(b.data)) as Hex[],
|
|
1291
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1292
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1293
|
+
context: {
|
|
1294
|
+
actions: ['validate-blobs'],
|
|
1295
|
+
sender: this.getSenderAddress().toString(),
|
|
1296
|
+
},
|
|
1297
|
+
});
|
|
1129
1298
|
throw new Error('Failed to validate blobs');
|
|
1130
1299
|
});
|
|
1131
1300
|
}
|
|
@@ -1204,6 +1373,8 @@ export class SequencerPublisher {
|
|
|
1204
1373
|
});
|
|
1205
1374
|
}
|
|
1206
1375
|
|
|
1376
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1377
|
+
|
|
1207
1378
|
const simulationResult = await this.l1TxUtils
|
|
1208
1379
|
.simulate(
|
|
1209
1380
|
{
|
|
@@ -1237,6 +1408,18 @@ export class SequencerPublisher {
|
|
|
1237
1408
|
};
|
|
1238
1409
|
}
|
|
1239
1410
|
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1411
|
+
this.backupFailedTx({
|
|
1412
|
+
id: keccak256(rollupData),
|
|
1413
|
+
failureType: 'simulation',
|
|
1414
|
+
request: { to: this.rollupContract.address, data: rollupData },
|
|
1415
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1416
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1417
|
+
context: {
|
|
1418
|
+
actions: ['propose'],
|
|
1419
|
+
slot: Number(args[0].header.slotNumber),
|
|
1420
|
+
sender: this.getSenderAddress().toString(),
|
|
1421
|
+
},
|
|
1422
|
+
});
|
|
1240
1423
|
throw err;
|
|
1241
1424
|
});
|
|
1242
1425
|
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
} from '@aztec/stdlib/interfaces/server';
|
|
39
39
|
import { type L1ToL2MessageSource, computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
|
|
40
40
|
import type { BlockProposalOptions, CheckpointProposal, CheckpointProposalOptions } from '@aztec/stdlib/p2p';
|
|
41
|
-
import { orderAttestations } from '@aztec/stdlib/p2p';
|
|
41
|
+
import { orderAttestations, trimAttestations } from '@aztec/stdlib/p2p';
|
|
42
42
|
import type { L2BlockBuiltStats } from '@aztec/stdlib/stats';
|
|
43
43
|
import { type FailedTx, Tx } from '@aztec/stdlib/tx';
|
|
44
44
|
import { AttestationTimeoutError } from '@aztec/stdlib/validators';
|
|
@@ -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,16 +186,15 @@ 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
|
-
const previousCheckpointOutHashes = previousCheckpoints.map(c => c.getCheckpointOutHash());
|
|
189
|
+
const previousCheckpointOutHashes = (await this.l2BlockSource.getCheckpointsDataForEpoch(this.epoch))
|
|
190
|
+
.filter(c => c.checkpointNumber < this.checkpointNumber)
|
|
191
|
+
.map(c => c.checkpointOutHash);
|
|
193
192
|
|
|
194
193
|
// Get the fee asset price modifier from the oracle
|
|
195
194
|
const feeAssetPriceModifier = await this.publisher.getFeeAssetPriceModifier();
|
|
196
195
|
|
|
197
196
|
// Create a long-lived forked world state for the checkpoint builder
|
|
198
|
-
using fork = await this.worldState.fork(this.syncedToBlockNumber, { closeDelayMs: 12_000 });
|
|
197
|
+
await using fork = await this.worldState.fork(this.syncedToBlockNumber, { closeDelayMs: 12_000 });
|
|
199
198
|
|
|
200
199
|
// Create checkpoint builder for the entire slot
|
|
201
200
|
const checkpointBuilder = await this.checkpointsBuilder.startCheckpoint(
|
|
@@ -221,6 +220,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
221
220
|
|
|
222
221
|
let blocksInCheckpoint: L2Block[] = [];
|
|
223
222
|
let blockPendingBroadcast: { block: L2Block; txs: Tx[] } | undefined = undefined;
|
|
223
|
+
const checkpointBuildTimer = new Timer();
|
|
224
224
|
|
|
225
225
|
try {
|
|
226
226
|
// Main loop: build blocks for the checkpoint
|
|
@@ -248,11 +248,28 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
248
248
|
return undefined;
|
|
249
249
|
}
|
|
250
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
|
+
|
|
251
260
|
// Assemble and broadcast the checkpoint proposal, including the last block that was not
|
|
252
261
|
// broadcasted yet, and wait to collect the committee attestations.
|
|
253
262
|
this.setStateFn(SequencerState.ASSEMBLING_CHECKPOINT, this.slot);
|
|
254
263
|
const checkpoint = await checkpointBuilder.completeCheckpoint();
|
|
255
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
|
+
|
|
256
273
|
// Do not collect attestations nor publish to L1 in fisherman mode
|
|
257
274
|
if (this.config.fishermanMode) {
|
|
258
275
|
this.log.info(
|
|
@@ -318,6 +335,21 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
318
335
|
const aztecSlotDuration = this.l1Constants.slotDuration;
|
|
319
336
|
const slotStartBuildTimestamp = this.getSlotStartBuildTimestamp();
|
|
320
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
|
+
|
|
321
353
|
await this.publisher.enqueueProposeCheckpoint(checkpoint, attestations, attestationsSignature, {
|
|
322
354
|
txTimeoutAt,
|
|
323
355
|
forcePendingCheckpointNumber: this.invalidateCheckpoint?.forcePendingCheckpointNumber,
|
|
@@ -711,8 +743,20 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
711
743
|
|
|
712
744
|
collectedAttestationsCount = attestations.length;
|
|
713
745
|
|
|
746
|
+
// Trim attestations to minimum required to save L1 calldata gas
|
|
747
|
+
const localAddresses = this.validatorClient.getValidatorAddresses();
|
|
748
|
+
const trimmed = trimAttestations(
|
|
749
|
+
attestations,
|
|
750
|
+
numberOfRequiredAttestations,
|
|
751
|
+
this.attestorAddress,
|
|
752
|
+
localAddresses,
|
|
753
|
+
);
|
|
754
|
+
if (trimmed.length < attestations.length) {
|
|
755
|
+
this.log.debug(`Trimmed attestations from ${attestations.length} to ${trimmed.length} for L1 submission`);
|
|
756
|
+
}
|
|
757
|
+
|
|
714
758
|
// Rollup contract requires that the signatures are provided in the order of the committee
|
|
715
|
-
const sorted = orderAttestations(
|
|
759
|
+
const sorted = orderAttestations(trimmed, committee);
|
|
716
760
|
|
|
717
761
|
// Manipulate the attestations if we've been configured to do so
|
|
718
762
|
if (this.config.injectFakeAttestation || this.config.shuffleAttestationOrdering) {
|
|
@@ -826,7 +870,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
826
870
|
slot: this.slot,
|
|
827
871
|
feeAnalysisId: feeAnalysis?.id,
|
|
828
872
|
});
|
|
829
|
-
this.metrics.
|
|
873
|
+
this.metrics.recordCheckpointProposalFailed('block_build_failed');
|
|
830
874
|
}
|
|
831
875
|
|
|
832
876
|
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;
|
|
@@ -84,7 +88,7 @@ export class SequencerMetrics {
|
|
|
84
88
|
|
|
85
89
|
this.checkpointAttestationDelay = this.meter.createHistogram(Metrics.SEQUENCER_CHECKPOINT_ATTESTATION_DELAY);
|
|
86
90
|
|
|
87
|
-
this.rewards = this.meter.createGauge(Metrics.
|
|
91
|
+
this.rewards = this.meter.createGauge(Metrics.SEQUENCER_CURRENT_SLOT_REWARDS);
|
|
88
92
|
|
|
89
93
|
this.slots = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_SLOT_COUNT);
|
|
90
94
|
|
|
@@ -107,16 +111,16 @@ export class SequencerMetrics {
|
|
|
107
111
|
Metrics.SEQUENCER_BLOCK_PROPOSAL_FAILED_COUNT,
|
|
108
112
|
);
|
|
109
113
|
|
|
110
|
-
this.
|
|
114
|
+
this.checkpointProposalSuccess = createUpDownCounterWithDefault(
|
|
111
115
|
this.meter,
|
|
112
|
-
Metrics.
|
|
116
|
+
Metrics.SEQUENCER_CHECKPOINT_PROPOSAL_SUCCESS_COUNT,
|
|
113
117
|
);
|
|
114
118
|
|
|
115
119
|
this.checkpointSuccess = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_CHECKPOINT_SUCCESS_COUNT);
|
|
116
120
|
|
|
117
|
-
this.
|
|
121
|
+
this.checkpointPrecheckFailed = createUpDownCounterWithDefault(
|
|
118
122
|
this.meter,
|
|
119
|
-
Metrics.
|
|
123
|
+
Metrics.SEQUENCER_CHECKPOINT_PRECHECK_FAILED_COUNT,
|
|
120
124
|
{
|
|
121
125
|
[Attributes.ERROR_TYPE]: [
|
|
122
126
|
'slot_already_taken',
|
|
@@ -127,6 +131,16 @@ export class SequencerMetrics {
|
|
|
127
131
|
},
|
|
128
132
|
);
|
|
129
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
|
+
|
|
130
144
|
this.slashingAttempts = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_SLASHING_ATTEMPTS_COUNT);
|
|
131
145
|
|
|
132
146
|
// Fisherman fee analysis metrics
|
|
@@ -262,18 +276,30 @@ export class SequencerMetrics {
|
|
|
262
276
|
});
|
|
263
277
|
}
|
|
264
278
|
|
|
265
|
-
|
|
266
|
-
this.
|
|
279
|
+
recordCheckpointProposalSuccess() {
|
|
280
|
+
this.checkpointProposalSuccess.add(1);
|
|
267
281
|
}
|
|
268
282
|
|
|
269
|
-
|
|
283
|
+
recordCheckpointPrecheckFailed(
|
|
270
284
|
checkType: 'slot_already_taken' | 'rollup_contract_check_failed' | 'slot_mismatch' | 'block_number_mismatch',
|
|
271
285
|
) {
|
|
272
|
-
this.
|
|
273
|
-
|
|
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 }),
|
|
274
292
|
});
|
|
275
293
|
}
|
|
276
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
|
+
|
|
277
303
|
recordSlashingAttempt(actionCount: number) {
|
|
278
304
|
this.slashingAttempts.add(actionCount);
|
|
279
305
|
}
|