@aztec/sequencer-client 3.0.0-rc.5 → 4.0.0-nightly.20260107
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 +9 -8
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +28 -24
- package/dest/config.d.ts +7 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +63 -26
- package/dest/global_variable_builder/global_builder.d.ts +16 -8
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +37 -28
- package/dest/index.d.ts +2 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -1
- package/dest/publisher/config.d.ts +3 -3
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +2 -2
- package/dest/publisher/sequencer-publisher-factory.d.ts +3 -3
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +1 -1
- package/dest/publisher/sequencer-publisher-metrics.d.ts +3 -3
- package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.d.ts +27 -23
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +119 -64
- package/dest/sequencer/block_builder.d.ts +1 -3
- package/dest/sequencer/block_builder.d.ts.map +1 -1
- package/dest/sequencer/block_builder.js +4 -2
- package/dest/sequencer/checkpoint_builder.d.ts +63 -0
- package/dest/sequencer/checkpoint_builder.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_builder.js +131 -0
- package/dest/sequencer/checkpoint_proposal_job.d.ts +74 -0
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_proposal_job.js +642 -0
- package/dest/sequencer/checkpoint_voter.d.ts +34 -0
- package/dest/sequencer/checkpoint_voter.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_voter.js +85 -0
- package/dest/sequencer/events.d.ts +46 -0
- package/dest/sequencer/events.d.ts.map +1 -0
- package/dest/sequencer/events.js +1 -0
- package/dest/sequencer/index.d.ts +5 -1
- package/dest/sequencer/index.d.ts.map +1 -1
- package/dest/sequencer/index.js +4 -0
- package/dest/sequencer/metrics.d.ts +21 -1
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +154 -0
- package/dest/sequencer/sequencer.d.ts +91 -125
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +581 -582
- package/dest/sequencer/timetable.d.ts +54 -14
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +148 -59
- package/dest/sequencer/types.d.ts +3 -0
- package/dest/sequencer/types.d.ts.map +1 -0
- package/dest/sequencer/types.js +1 -0
- package/dest/sequencer/utils.d.ts +14 -8
- package/dest/sequencer/utils.d.ts.map +1 -1
- package/dest/sequencer/utils.js +7 -4
- package/dest/test/index.d.ts +3 -1
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts +83 -0
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -0
- package/dest/test/mock_checkpoint_builder.js +179 -0
- package/dest/test/utils.d.ts +49 -0
- package/dest/test/utils.d.ts.map +1 -0
- package/dest/test/utils.js +94 -0
- package/package.json +27 -27
- package/src/client/sequencer-client.ts +24 -31
- package/src/config.ts +68 -25
- package/src/global_variable_builder/global_builder.ts +47 -41
- package/src/index.ts +2 -0
- package/src/publisher/config.ts +3 -3
- package/src/publisher/sequencer-publisher-factory.ts +3 -3
- package/src/publisher/sequencer-publisher-metrics.ts +2 -2
- package/src/publisher/sequencer-publisher.ts +170 -74
- package/src/sequencer/README.md +531 -0
- package/src/sequencer/block_builder.ts +4 -1
- package/src/sequencer/checkpoint_builder.ts +217 -0
- package/src/sequencer/checkpoint_proposal_job.ts +706 -0
- package/src/sequencer/checkpoint_voter.ts +105 -0
- package/src/sequencer/events.ts +27 -0
- package/src/sequencer/index.ts +4 -0
- package/src/sequencer/metrics.ts +202 -0
- package/src/sequencer/sequencer.ts +300 -779
- package/src/sequencer/timetable.ts +173 -79
- package/src/sequencer/types.ts +6 -0
- package/src/sequencer/utils.ts +18 -9
- package/src/test/index.ts +2 -0
- package/src/test/mock_checkpoint_builder.ts +247 -0
- package/src/test/utils.ts +137 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { BlobClientInterface } from '@aztec/blob-client/client';
|
|
2
2
|
import { Blob, getBlobsPerL1Block, getPrefixedEthBlobCommitments } from '@aztec/blob-lib';
|
|
3
|
-
import { type BlobSinkClientInterface, createBlobSinkClient } from '@aztec/blob-sink/client';
|
|
4
3
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
5
4
|
import type { L1ContractsConfig } from '@aztec/ethereum/config';
|
|
6
5
|
import {
|
|
@@ -14,6 +13,7 @@ import {
|
|
|
14
13
|
type ViemCommitteeAttestations,
|
|
15
14
|
type ViemHeader,
|
|
16
15
|
} from '@aztec/ethereum/contracts';
|
|
16
|
+
import { type L1FeeAnalysisResult, L1FeeAnalyzer } from '@aztec/ethereum/l1-fee-analysis';
|
|
17
17
|
import {
|
|
18
18
|
type L1BlobInputs,
|
|
19
19
|
type L1TxConfig,
|
|
@@ -26,6 +26,7 @@ import { FormattedViemError, formatViemError, tryExtractEvent } from '@aztec/eth
|
|
|
26
26
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
27
27
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
28
28
|
import { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
29
|
+
import { pick } from '@aztec/foundation/collection';
|
|
29
30
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
30
31
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
31
32
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
@@ -34,10 +35,11 @@ import { bufferToHex } from '@aztec/foundation/string';
|
|
|
34
35
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
35
36
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
36
37
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
37
|
-
import {
|
|
38
|
+
import { CommitteeAttestationsAndSigners, type ValidateBlockResult } from '@aztec/stdlib/block';
|
|
39
|
+
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
38
40
|
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
39
41
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
40
|
-
import type {
|
|
42
|
+
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
41
43
|
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
42
44
|
|
|
43
45
|
import { type StateOverride, type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
|
|
@@ -108,13 +110,18 @@ export class SequencerPublisher {
|
|
|
108
110
|
|
|
109
111
|
protected lastActions: Partial<Record<Action, SlotNumber>> = {};
|
|
110
112
|
|
|
113
|
+
private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
|
|
114
|
+
|
|
111
115
|
protected log: Logger;
|
|
112
116
|
protected ethereumSlotDuration: bigint;
|
|
113
117
|
|
|
114
|
-
private
|
|
118
|
+
private blobClient: BlobClientInterface;
|
|
115
119
|
|
|
116
120
|
/** Address to use for simulations in fisherman mode (actual proposer's address) */
|
|
117
121
|
private proposerAddressForSimulation?: EthAddress;
|
|
122
|
+
|
|
123
|
+
/** L1 fee analyzer for fisherman mode */
|
|
124
|
+
private l1FeeAnalyzer?: L1FeeAnalyzer;
|
|
118
125
|
// @note - with blobs, the below estimate seems too large.
|
|
119
126
|
// Total used for full block from int_l1_pub e2e test: 1m (of which 86k is 1x blob)
|
|
120
127
|
// Total used for emptier block from above test: 429k (of which 84k is 1x blob)
|
|
@@ -138,7 +145,7 @@ export class SequencerPublisher {
|
|
|
138
145
|
private config: TxSenderConfig & PublisherConfig & Pick<L1ContractsConfig, 'ethereumSlotDuration'>,
|
|
139
146
|
deps: {
|
|
140
147
|
telemetry?: TelemetryClient;
|
|
141
|
-
|
|
148
|
+
blobClient: BlobClientInterface;
|
|
142
149
|
l1TxUtils: L1TxUtilsWithBlobs;
|
|
143
150
|
rollupContract: RollupContract;
|
|
144
151
|
slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
@@ -156,8 +163,7 @@ export class SequencerPublisher {
|
|
|
156
163
|
this.epochCache = deps.epochCache;
|
|
157
164
|
this.lastActions = deps.lastActions;
|
|
158
165
|
|
|
159
|
-
this.
|
|
160
|
-
deps.blobSinkClient ?? createBlobSinkClient(config, { logger: createLogger('sequencer:blob-sink:client') });
|
|
166
|
+
this.blobClient = deps.blobClient;
|
|
161
167
|
|
|
162
168
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
163
169
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
@@ -174,6 +180,15 @@ export class SequencerPublisher {
|
|
|
174
180
|
this.slashingProposerContract = newSlashingProposer;
|
|
175
181
|
});
|
|
176
182
|
this.slashFactoryContract = deps.slashFactoryContract;
|
|
183
|
+
|
|
184
|
+
// Initialize L1 fee analyzer for fisherman mode
|
|
185
|
+
if (config.fishermanMode) {
|
|
186
|
+
this.l1FeeAnalyzer = new L1FeeAnalyzer(
|
|
187
|
+
this.l1TxUtils.client,
|
|
188
|
+
deps.dateProvider,
|
|
189
|
+
createLogger('sequencer:publisher:fee-analyzer'),
|
|
190
|
+
);
|
|
191
|
+
}
|
|
177
192
|
}
|
|
178
193
|
|
|
179
194
|
public getRollupContract(): RollupContract {
|
|
@@ -184,6 +199,13 @@ export class SequencerPublisher {
|
|
|
184
199
|
return this.l1TxUtils.getSenderAddress();
|
|
185
200
|
}
|
|
186
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Gets the L1 fee analyzer instance (only available in fisherman mode)
|
|
204
|
+
*/
|
|
205
|
+
public getL1FeeAnalyzer(): L1FeeAnalyzer | undefined {
|
|
206
|
+
return this.l1FeeAnalyzer;
|
|
207
|
+
}
|
|
208
|
+
|
|
187
209
|
/**
|
|
188
210
|
* Sets the proposer address to use for simulations in fisherman mode.
|
|
189
211
|
* @param proposerAddress - The actual proposer's address to use for balance lookups in simulations
|
|
@@ -211,6 +233,62 @@ export class SequencerPublisher {
|
|
|
211
233
|
}
|
|
212
234
|
}
|
|
213
235
|
|
|
236
|
+
/**
|
|
237
|
+
* Analyzes L1 fees for the pending requests without sending them.
|
|
238
|
+
* This is used in fisherman mode to validate fee calculations.
|
|
239
|
+
* @param l2SlotNumber - The L2 slot number for this analysis
|
|
240
|
+
* @param onComplete - Optional callback to invoke when analysis completes (after block is mined)
|
|
241
|
+
* @returns The analysis result (incomplete until block mines), or undefined if no requests
|
|
242
|
+
*/
|
|
243
|
+
public async analyzeL1Fees(
|
|
244
|
+
l2SlotNumber: SlotNumber,
|
|
245
|
+
onComplete?: (analysis: L1FeeAnalysisResult) => void,
|
|
246
|
+
): Promise<L1FeeAnalysisResult | undefined> {
|
|
247
|
+
if (!this.l1FeeAnalyzer) {
|
|
248
|
+
this.log.warn('L1 fee analyzer not available (not in fisherman mode)');
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const requestsToAnalyze = [...this.requests];
|
|
253
|
+
if (requestsToAnalyze.length === 0) {
|
|
254
|
+
this.log.debug('No requests to analyze for L1 fees');
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Extract blob config from requests (if any)
|
|
259
|
+
const blobConfigs = requestsToAnalyze.filter(request => request.blobConfig).map(request => request.blobConfig);
|
|
260
|
+
const blobConfig = blobConfigs[0];
|
|
261
|
+
|
|
262
|
+
// Get gas configs
|
|
263
|
+
const gasConfigs = requestsToAnalyze.filter(request => request.gasConfig).map(request => request.gasConfig);
|
|
264
|
+
const gasLimits = gasConfigs.map(g => g?.gasLimit).filter((g): g is bigint => g !== undefined);
|
|
265
|
+
const gasLimit = gasLimits.length > 0 ? gasLimits.reduce((sum, g) => sum + g, 0n) : 0n;
|
|
266
|
+
|
|
267
|
+
// Get the transaction requests
|
|
268
|
+
const l1Requests = requestsToAnalyze.map(r => r.request);
|
|
269
|
+
|
|
270
|
+
// Start the analysis
|
|
271
|
+
const analysisId = await this.l1FeeAnalyzer.startAnalysis(
|
|
272
|
+
l2SlotNumber,
|
|
273
|
+
gasLimit > 0n ? gasLimit : SequencerPublisher.PROPOSE_GAS_GUESS,
|
|
274
|
+
l1Requests,
|
|
275
|
+
blobConfig,
|
|
276
|
+
onComplete,
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
this.log.info('Started L1 fee analysis', {
|
|
280
|
+
analysisId,
|
|
281
|
+
l2SlotNumber: l2SlotNumber.toString(),
|
|
282
|
+
requestCount: requestsToAnalyze.length,
|
|
283
|
+
hasBlobConfig: !!blobConfig,
|
|
284
|
+
gasLimit: gasLimit.toString(),
|
|
285
|
+
actions: requestsToAnalyze.map(r => r.action),
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// Return the analysis result (will be incomplete until block mines)
|
|
289
|
+
return this.l1FeeAnalyzer.getAnalysis(analysisId);
|
|
290
|
+
}
|
|
291
|
+
|
|
214
292
|
/**
|
|
215
293
|
* Sends all requests that are still valid.
|
|
216
294
|
* @returns one of:
|
|
@@ -221,7 +299,7 @@ export class SequencerPublisher {
|
|
|
221
299
|
public async sendRequests() {
|
|
222
300
|
const requestsToProcess = [...this.requests];
|
|
223
301
|
this.requests = [];
|
|
224
|
-
if (this.interrupted) {
|
|
302
|
+
if (this.interrupted || requestsToProcess.length === 0) {
|
|
225
303
|
return undefined;
|
|
226
304
|
}
|
|
227
305
|
const currentL2Slot = this.getCurrentL2Slot();
|
|
@@ -509,45 +587,38 @@ export class SequencerPublisher {
|
|
|
509
587
|
}
|
|
510
588
|
}
|
|
511
589
|
|
|
512
|
-
/**
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
* @dev Throws if unable to propose
|
|
516
|
-
*
|
|
517
|
-
* @param block - The block to propose
|
|
518
|
-
* @param attestationData - The block's attestation data
|
|
519
|
-
*
|
|
520
|
-
*/
|
|
521
|
-
public async validateBlockForSubmission(
|
|
522
|
-
block: L2Block,
|
|
590
|
+
/** Simulates `propose` to make sure that the checkpoint is valid for submission */
|
|
591
|
+
public async validateCheckpointForSubmission(
|
|
592
|
+
checkpoint: Checkpoint,
|
|
523
593
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
524
594
|
attestationsAndSignersSignature: Signature,
|
|
525
|
-
options: { forcePendingBlockNumber?: BlockNumber },
|
|
595
|
+
options: { forcePendingBlockNumber?: BlockNumber }, // TODO(palla/mbps): Should this be forcePendingCheckpointNumber?
|
|
526
596
|
): Promise<bigint> {
|
|
527
597
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
528
598
|
|
|
599
|
+
// TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
|
|
529
600
|
// If we have no attestations, we still need to provide the empty attestations
|
|
530
601
|
// so that the committee is recalculated correctly
|
|
531
|
-
const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
532
|
-
if (ignoreSignatures) {
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
const blobFields =
|
|
602
|
+
// const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
603
|
+
// if (ignoreSignatures) {
|
|
604
|
+
// const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
|
|
605
|
+
// if (!committee) {
|
|
606
|
+
// this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
607
|
+
// throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
608
|
+
// }
|
|
609
|
+
// attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
610
|
+
// CommitteeAttestation.fromAddress(committeeMember),
|
|
611
|
+
// );
|
|
612
|
+
// }
|
|
613
|
+
|
|
614
|
+
const blobFields = checkpoint.toBlobFields();
|
|
544
615
|
const blobs = getBlobsPerL1Block(blobFields);
|
|
545
616
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
546
617
|
|
|
547
618
|
const args = [
|
|
548
619
|
{
|
|
549
|
-
header:
|
|
550
|
-
archive: toHex(
|
|
620
|
+
header: checkpoint.header.toViem(),
|
|
621
|
+
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
551
622
|
oracleInput: {
|
|
552
623
|
feeAssetPriceModifier: 0n,
|
|
553
624
|
},
|
|
@@ -585,10 +656,19 @@ export class SequencerPublisher {
|
|
|
585
656
|
const round = await base.computeRound(slotNumber);
|
|
586
657
|
const roundInfo = await base.getRoundInfo(this.rollupContract.address, round);
|
|
587
658
|
|
|
659
|
+
if (roundInfo.quorumReached) {
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
|
|
588
663
|
if (roundInfo.lastSignalSlot >= slotNumber) {
|
|
589
664
|
return false;
|
|
590
665
|
}
|
|
591
666
|
|
|
667
|
+
if (await this.isPayloadEmpty(payload)) {
|
|
668
|
+
this.log.warn(`Skipping vote cast for payload with empty code`);
|
|
669
|
+
return false;
|
|
670
|
+
}
|
|
671
|
+
|
|
592
672
|
const cachedLastVote = this.lastActions[signalType];
|
|
593
673
|
this.lastActions[signalType] = slotNumber;
|
|
594
674
|
const action = signalType;
|
|
@@ -631,14 +711,14 @@ export class SequencerPublisher {
|
|
|
631
711
|
const logData = { ...result, slotNumber, round, payload: payload.toString() };
|
|
632
712
|
if (!success) {
|
|
633
713
|
this.log.error(
|
|
634
|
-
`Signaling in
|
|
714
|
+
`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} failed`,
|
|
635
715
|
logData,
|
|
636
716
|
);
|
|
637
717
|
this.lastActions[signalType] = cachedLastVote;
|
|
638
718
|
return false;
|
|
639
719
|
} else {
|
|
640
720
|
this.log.info(
|
|
641
|
-
`Signaling in
|
|
721
|
+
`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} succeeded`,
|
|
642
722
|
logData,
|
|
643
723
|
);
|
|
644
724
|
return true;
|
|
@@ -648,6 +728,17 @@ export class SequencerPublisher {
|
|
|
648
728
|
return true;
|
|
649
729
|
}
|
|
650
730
|
|
|
731
|
+
private async isPayloadEmpty(payload: EthAddress): Promise<boolean> {
|
|
732
|
+
const key = payload.toString();
|
|
733
|
+
const cached = this.isPayloadEmptyCache.get(key);
|
|
734
|
+
if (cached) {
|
|
735
|
+
return cached;
|
|
736
|
+
}
|
|
737
|
+
const isEmpty = !(await this.l1TxUtils.getCode(payload));
|
|
738
|
+
this.isPayloadEmptyCache.set(key, isEmpty);
|
|
739
|
+
return isEmpty;
|
|
740
|
+
}
|
|
741
|
+
|
|
651
742
|
/**
|
|
652
743
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
653
744
|
* @param slotNumber - The slot number to cast a signal for.
|
|
@@ -795,27 +886,21 @@ export class SequencerPublisher {
|
|
|
795
886
|
return true;
|
|
796
887
|
}
|
|
797
888
|
|
|
798
|
-
/**
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
* @param block - L2 block to propose.
|
|
802
|
-
* @returns True if the tx has been enqueued, throws otherwise. See #9315
|
|
803
|
-
*/
|
|
804
|
-
public async enqueueProposeL2Block(
|
|
805
|
-
block: L2Block,
|
|
889
|
+
/** Simulates and enqueues a proposal for a checkpoint on L1 */
|
|
890
|
+
public async enqueueProposeCheckpoint(
|
|
891
|
+
checkpoint: Checkpoint,
|
|
806
892
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
807
893
|
attestationsAndSignersSignature: Signature,
|
|
808
894
|
opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: BlockNumber } = {},
|
|
809
|
-
): Promise<
|
|
810
|
-
const checkpointHeader =
|
|
895
|
+
): Promise<void> {
|
|
896
|
+
const checkpointHeader = checkpoint.header;
|
|
811
897
|
|
|
812
|
-
const blobFields =
|
|
898
|
+
const blobFields = checkpoint.toBlobFields();
|
|
813
899
|
const blobs = getBlobsPerL1Block(blobFields);
|
|
814
900
|
|
|
815
901
|
const proposeTxArgs = {
|
|
816
902
|
header: checkpointHeader,
|
|
817
|
-
archive:
|
|
818
|
-
body: block.body.toBuffer(),
|
|
903
|
+
archive: checkpoint.archive.root.toBuffer(),
|
|
819
904
|
blobs,
|
|
820
905
|
attestationsAndSigners,
|
|
821
906
|
attestationsAndSignersSignature,
|
|
@@ -829,19 +914,23 @@ export class SequencerPublisher {
|
|
|
829
914
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
830
915
|
// make time consistency checks break.
|
|
831
916
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
832
|
-
ts = await this.
|
|
917
|
+
ts = await this.validateCheckpointForSubmission(
|
|
918
|
+
checkpoint,
|
|
919
|
+
attestationsAndSigners,
|
|
920
|
+
attestationsAndSignersSignature,
|
|
921
|
+
opts,
|
|
922
|
+
);
|
|
833
923
|
} catch (err: any) {
|
|
834
|
-
this.log.error(`
|
|
835
|
-
...
|
|
836
|
-
slotNumber:
|
|
924
|
+
this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
925
|
+
...checkpoint.getStats(),
|
|
926
|
+
slotNumber: checkpoint.header.slotNumber,
|
|
837
927
|
forcePendingBlockNumber: opts.forcePendingBlockNumber,
|
|
838
928
|
});
|
|
839
929
|
throw err;
|
|
840
930
|
}
|
|
841
931
|
|
|
842
|
-
this.log.verbose(`Enqueuing
|
|
843
|
-
await this.addProposeTx(
|
|
844
|
-
return true;
|
|
932
|
+
this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
|
|
933
|
+
await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
|
|
845
934
|
}
|
|
846
935
|
|
|
847
936
|
public enqueueInvalidateBlock(request: InvalidateBlockRequest | undefined, opts: { txTimeoutAt?: Date } = {}) {
|
|
@@ -1106,11 +1195,12 @@ export class SequencerPublisher {
|
|
|
1106
1195
|
}
|
|
1107
1196
|
|
|
1108
1197
|
private async addProposeTx(
|
|
1109
|
-
|
|
1198
|
+
checkpoint: Checkpoint,
|
|
1110
1199
|
encodedData: L1ProcessArgs,
|
|
1111
1200
|
opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: BlockNumber } = {},
|
|
1112
1201
|
timestamp: bigint,
|
|
1113
1202
|
): Promise<void> {
|
|
1203
|
+
const slot = checkpoint.header.slotNumber;
|
|
1114
1204
|
const timer = new Timer();
|
|
1115
1205
|
const kzg = Blob.getViemKzgInstance();
|
|
1116
1206
|
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
@@ -1125,11 +1215,13 @@ export class SequencerPublisher {
|
|
|
1125
1215
|
SequencerPublisher.MULTICALL_OVERHEAD_GAS_GUESS, // We issue the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
1126
1216
|
);
|
|
1127
1217
|
|
|
1128
|
-
// Send the blobs to the blob
|
|
1129
|
-
// tx fails but it does get mined. We make sure that the blobs are sent to the blob
|
|
1130
|
-
void
|
|
1131
|
-
this.
|
|
1132
|
-
|
|
1218
|
+
// Send the blobs to the blob client preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
|
|
1219
|
+
// tx fails but it does get mined. We make sure that the blobs are sent to the blob client regardless of the tx outcome.
|
|
1220
|
+
void Promise.resolve().then(() =>
|
|
1221
|
+
this.blobClient.sendBlobsToFilestore(encodedData.blobs).catch(_err => {
|
|
1222
|
+
this.log.error('Failed to send blobs to blob client');
|
|
1223
|
+
}),
|
|
1224
|
+
);
|
|
1133
1225
|
|
|
1134
1226
|
return this.addRequest({
|
|
1135
1227
|
action: 'propose',
|
|
@@ -1137,7 +1229,7 @@ export class SequencerPublisher {
|
|
|
1137
1229
|
to: this.rollupContract.address,
|
|
1138
1230
|
data: rollupData,
|
|
1139
1231
|
},
|
|
1140
|
-
lastValidL2Slot:
|
|
1232
|
+
lastValidL2Slot: checkpoint.header.slotNumber,
|
|
1141
1233
|
gasConfig: { ...opts, gasLimit },
|
|
1142
1234
|
blobConfig: {
|
|
1143
1235
|
blobs: encodedData.blobs.map(b => b.data),
|
|
@@ -1152,11 +1244,12 @@ export class SequencerPublisher {
|
|
|
1152
1244
|
receipt &&
|
|
1153
1245
|
receipt.status === 'success' &&
|
|
1154
1246
|
tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointProposed');
|
|
1247
|
+
|
|
1155
1248
|
if (success) {
|
|
1156
1249
|
const endBlock = receipt.blockNumber;
|
|
1157
1250
|
const inclusionBlocks = Number(endBlock - startBlock);
|
|
1158
1251
|
const { calldataGas, calldataSize, sender } = stats!;
|
|
1159
|
-
const publishStats:
|
|
1252
|
+
const publishStats: L1PublishCheckpointStats = {
|
|
1160
1253
|
gasPrice: receipt.effectiveGasPrice,
|
|
1161
1254
|
gasUsed: receipt.gasUsed,
|
|
1162
1255
|
blobGasUsed: receipt.blobGasUsed ?? 0n,
|
|
@@ -1165,23 +1258,26 @@ export class SequencerPublisher {
|
|
|
1165
1258
|
calldataGas,
|
|
1166
1259
|
calldataSize,
|
|
1167
1260
|
sender,
|
|
1168
|
-
...
|
|
1261
|
+
...checkpoint.getStats(),
|
|
1169
1262
|
eventName: 'rollup-published-to-l1',
|
|
1170
1263
|
blobCount: encodedData.blobs.length,
|
|
1171
1264
|
inclusionBlocks,
|
|
1172
1265
|
};
|
|
1173
|
-
this.log.info(`Published
|
|
1266
|
+
this.log.info(`Published checkpoint ${checkpoint.number} at slot ${slot} to rollup contract`, {
|
|
1267
|
+
...stats,
|
|
1268
|
+
...checkpoint.getStats(),
|
|
1269
|
+
...pick(receipt, 'transactionHash', 'blockHash'),
|
|
1270
|
+
});
|
|
1174
1271
|
this.metrics.recordProcessBlockTx(timer.ms(), publishStats);
|
|
1175
1272
|
|
|
1176
1273
|
return true;
|
|
1177
1274
|
} else {
|
|
1178
1275
|
this.metrics.recordFailedTx('process');
|
|
1179
|
-
this.log.error(
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
});
|
|
1276
|
+
this.log.error(
|
|
1277
|
+
`Publishing checkpoint at slot ${slot} failed with ${errorMsg ?? 'no error message'}`,
|
|
1278
|
+
undefined,
|
|
1279
|
+
{ ...checkpoint.getStats(), ...receipt },
|
|
1280
|
+
);
|
|
1185
1281
|
return false;
|
|
1186
1282
|
}
|
|
1187
1283
|
},
|