@aztec/sequencer-client 0.0.1-commit.fce3e4f → 0.0.1-commit.fffb133c
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 -12
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +32 -25
- package/dest/config.d.ts +12 -5
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +73 -30
- package/dest/global_variable_builder/global_builder.d.ts +22 -13
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +51 -41
- package/dest/index.d.ts +2 -3
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -2
- package/dest/publisher/config.d.ts +7 -4
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +9 -3
- package/dest/publisher/sequencer-publisher-factory.d.ts +5 -4
- 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-metrics.js +15 -86
- package/dest/publisher/sequencer-publisher.d.ts +49 -41
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +566 -114
- package/dest/sequencer/checkpoint_proposal_job.d.ts +79 -0
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_proposal_job.js +1165 -0
- package/dest/sequencer/checkpoint_voter.d.ts +35 -0
- package/dest/sequencer/checkpoint_voter.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_voter.js +109 -0
- package/dest/sequencer/config.d.ts +3 -2
- package/dest/sequencer/config.d.ts.map +1 -1
- 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 +4 -2
- package/dest/sequencer/index.d.ts.map +1 -1
- package/dest/sequencer/index.js +3 -1
- package/dest/sequencer/metrics.d.ts +22 -2
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +125 -62
- package/dest/sequencer/sequencer.d.ts +107 -131
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +694 -605
- 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 +4 -3
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts +95 -0
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -0
- package/dest/test/mock_checkpoint_builder.js +222 -0
- package/dest/test/utils.d.ts +53 -0
- package/dest/test/utils.d.ts.map +1 -0
- package/dest/test/utils.js +103 -0
- package/package.json +32 -30
- package/src/client/sequencer-client.ts +30 -41
- package/src/config.ts +78 -34
- package/src/global_variable_builder/global_builder.ts +65 -61
- package/src/index.ts +1 -7
- package/src/publisher/config.ts +12 -9
- package/src/publisher/sequencer-publisher-factory.ts +5 -4
- package/src/publisher/sequencer-publisher-metrics.ts +16 -72
- package/src/publisher/sequencer-publisher.ts +269 -146
- package/src/sequencer/README.md +531 -0
- package/src/sequencer/checkpoint_proposal_job.ts +845 -0
- package/src/sequencer/checkpoint_voter.ts +130 -0
- package/src/sequencer/config.ts +2 -1
- package/src/sequencer/events.ts +27 -0
- package/src/sequencer/index.ts +3 -1
- package/src/sequencer/metrics.ts +164 -70
- package/src/sequencer/sequencer.ts +437 -812
- 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 +3 -2
- package/src/test/mock_checkpoint_builder.ts +311 -0
- package/src/test/utils.ts +164 -0
- package/dest/sequencer/block_builder.d.ts +0 -27
- package/dest/sequencer/block_builder.d.ts.map +0 -1
- package/dest/sequencer/block_builder.js +0 -134
- package/dest/tx_validator/nullifier_cache.d.ts +0 -14
- package/dest/tx_validator/nullifier_cache.d.ts.map +0 -1
- package/dest/tx_validator/nullifier_cache.js +0 -24
- package/dest/tx_validator/tx_validator_factory.d.ts +0 -17
- package/dest/tx_validator/tx_validator_factory.d.ts.map +0 -1
- package/dest/tx_validator/tx_validator_factory.js +0 -53
- package/src/sequencer/block_builder.ts +0 -222
- package/src/tx_validator/nullifier_cache.ts +0 -30
- package/src/tx_validator/tx_validator_factory.ts +0 -132
|
@@ -1,44 +1,46 @@
|
|
|
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';
|
|
4
|
+
import type { L1ContractsConfig } from '@aztec/ethereum/config';
|
|
5
5
|
import {
|
|
6
6
|
type EmpireSlashingProposerContract,
|
|
7
|
-
FormattedViemError,
|
|
8
7
|
type GovernanceProposerContract,
|
|
9
8
|
type IEmpireBase,
|
|
10
|
-
type L1BlobInputs,
|
|
11
|
-
type L1ContractsConfig,
|
|
12
|
-
type L1TxConfig,
|
|
13
|
-
type L1TxRequest,
|
|
14
9
|
MULTI_CALL_3_ADDRESS,
|
|
15
10
|
Multicall3,
|
|
16
11
|
RollupContract,
|
|
17
12
|
type TallySlashingProposerContract,
|
|
18
|
-
type TransactionStats,
|
|
19
13
|
type ViemCommitteeAttestations,
|
|
20
14
|
type ViemHeader,
|
|
15
|
+
} from '@aztec/ethereum/contracts';
|
|
16
|
+
import { type L1FeeAnalysisResult, L1FeeAnalyzer } from '@aztec/ethereum/l1-fee-analysis';
|
|
17
|
+
import {
|
|
18
|
+
type L1BlobInputs,
|
|
19
|
+
type L1TxConfig,
|
|
20
|
+
type L1TxRequest,
|
|
21
|
+
type TransactionStats,
|
|
21
22
|
WEI_CONST,
|
|
22
|
-
|
|
23
|
-
tryExtractEvent,
|
|
24
|
-
} from '@aztec/ethereum';
|
|
23
|
+
} from '@aztec/ethereum/l1-tx-utils';
|
|
25
24
|
import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs';
|
|
25
|
+
import { FormattedViemError, formatViemError, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
26
26
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
27
27
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
28
|
-
import { SlotNumber } from '@aztec/foundation/branded-types';
|
|
28
|
+
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
29
|
+
import { pick } from '@aztec/foundation/collection';
|
|
30
|
+
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
29
31
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
30
32
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
31
|
-
import type { Fr } from '@aztec/foundation/fields';
|
|
32
33
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
33
34
|
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 ValidateCheckpointResult } 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 {
|
|
41
|
-
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
42
|
+
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
43
|
+
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
42
44
|
|
|
43
45
|
import { type StateOverride, type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
|
|
44
46
|
|
|
@@ -78,12 +80,12 @@ type GovernanceSignalAction = Extract<Action, 'governance-signal' | 'empire-slas
|
|
|
78
80
|
// Sorting for actions such that invalidations go before proposals, and proposals go before votes
|
|
79
81
|
export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
|
|
80
82
|
|
|
81
|
-
export type
|
|
83
|
+
export type InvalidateCheckpointRequest = {
|
|
82
84
|
request: L1TxRequest;
|
|
83
85
|
reason: 'invalid-attestation' | 'insufficient-attestations';
|
|
84
86
|
gasUsed: bigint;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
checkpointNumber: CheckpointNumber;
|
|
88
|
+
forcePendingCheckpointNumber: CheckpointNumber;
|
|
87
89
|
};
|
|
88
90
|
|
|
89
91
|
interface RequestWithExpiry {
|
|
@@ -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)
|
|
@@ -132,13 +139,15 @@ export class SequencerPublisher {
|
|
|
132
139
|
public slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
133
140
|
public slashFactoryContract: SlashFactoryContract;
|
|
134
141
|
|
|
142
|
+
public readonly tracer: Tracer;
|
|
143
|
+
|
|
135
144
|
protected requests: RequestWithExpiry[] = [];
|
|
136
145
|
|
|
137
146
|
constructor(
|
|
138
147
|
private config: TxSenderConfig & PublisherConfig & Pick<L1ContractsConfig, 'ethereumSlotDuration'>,
|
|
139
148
|
deps: {
|
|
140
149
|
telemetry?: TelemetryClient;
|
|
141
|
-
|
|
150
|
+
blobClient: BlobClientInterface;
|
|
142
151
|
l1TxUtils: L1TxUtilsWithBlobs;
|
|
143
152
|
rollupContract: RollupContract;
|
|
144
153
|
slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
@@ -156,11 +165,11 @@ export class SequencerPublisher {
|
|
|
156
165
|
this.epochCache = deps.epochCache;
|
|
157
166
|
this.lastActions = deps.lastActions;
|
|
158
167
|
|
|
159
|
-
this.
|
|
160
|
-
deps.blobSinkClient ?? createBlobSinkClient(config, { logger: createLogger('sequencer:blob-sink:client') });
|
|
168
|
+
this.blobClient = deps.blobClient;
|
|
161
169
|
|
|
162
170
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
163
171
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
172
|
+
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
164
173
|
this.l1TxUtils = deps.l1TxUtils;
|
|
165
174
|
|
|
166
175
|
this.rollupContract = deps.rollupContract;
|
|
@@ -174,6 +183,15 @@ export class SequencerPublisher {
|
|
|
174
183
|
this.slashingProposerContract = newSlashingProposer;
|
|
175
184
|
});
|
|
176
185
|
this.slashFactoryContract = deps.slashFactoryContract;
|
|
186
|
+
|
|
187
|
+
// Initialize L1 fee analyzer for fisherman mode
|
|
188
|
+
if (config.fishermanMode) {
|
|
189
|
+
this.l1FeeAnalyzer = new L1FeeAnalyzer(
|
|
190
|
+
this.l1TxUtils.client,
|
|
191
|
+
deps.dateProvider,
|
|
192
|
+
createLogger('sequencer:publisher:fee-analyzer'),
|
|
193
|
+
);
|
|
194
|
+
}
|
|
177
195
|
}
|
|
178
196
|
|
|
179
197
|
public getRollupContract(): RollupContract {
|
|
@@ -184,6 +202,13 @@ export class SequencerPublisher {
|
|
|
184
202
|
return this.l1TxUtils.getSenderAddress();
|
|
185
203
|
}
|
|
186
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Gets the L1 fee analyzer instance (only available in fisherman mode)
|
|
207
|
+
*/
|
|
208
|
+
public getL1FeeAnalyzer(): L1FeeAnalyzer | undefined {
|
|
209
|
+
return this.l1FeeAnalyzer;
|
|
210
|
+
}
|
|
211
|
+
|
|
187
212
|
/**
|
|
188
213
|
* Sets the proposer address to use for simulations in fisherman mode.
|
|
189
214
|
* @param proposerAddress - The actual proposer's address to use for balance lookups in simulations
|
|
@@ -211,6 +236,62 @@ export class SequencerPublisher {
|
|
|
211
236
|
}
|
|
212
237
|
}
|
|
213
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Analyzes L1 fees for the pending requests without sending them.
|
|
241
|
+
* This is used in fisherman mode to validate fee calculations.
|
|
242
|
+
* @param l2SlotNumber - The L2 slot number for this analysis
|
|
243
|
+
* @param onComplete - Optional callback to invoke when analysis completes (after block is mined)
|
|
244
|
+
* @returns The analysis result (incomplete until block mines), or undefined if no requests
|
|
245
|
+
*/
|
|
246
|
+
public async analyzeL1Fees(
|
|
247
|
+
l2SlotNumber: SlotNumber,
|
|
248
|
+
onComplete?: (analysis: L1FeeAnalysisResult) => void,
|
|
249
|
+
): Promise<L1FeeAnalysisResult | undefined> {
|
|
250
|
+
if (!this.l1FeeAnalyzer) {
|
|
251
|
+
this.log.warn('L1 fee analyzer not available (not in fisherman mode)');
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const requestsToAnalyze = [...this.requests];
|
|
256
|
+
if (requestsToAnalyze.length === 0) {
|
|
257
|
+
this.log.debug('No requests to analyze for L1 fees');
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Extract blob config from requests (if any)
|
|
262
|
+
const blobConfigs = requestsToAnalyze.filter(request => request.blobConfig).map(request => request.blobConfig);
|
|
263
|
+
const blobConfig = blobConfigs[0];
|
|
264
|
+
|
|
265
|
+
// Get gas configs
|
|
266
|
+
const gasConfigs = requestsToAnalyze.filter(request => request.gasConfig).map(request => request.gasConfig);
|
|
267
|
+
const gasLimits = gasConfigs.map(g => g?.gasLimit).filter((g): g is bigint => g !== undefined);
|
|
268
|
+
const gasLimit = gasLimits.length > 0 ? gasLimits.reduce((sum, g) => sum + g, 0n) : 0n;
|
|
269
|
+
|
|
270
|
+
// Get the transaction requests
|
|
271
|
+
const l1Requests = requestsToAnalyze.map(r => r.request);
|
|
272
|
+
|
|
273
|
+
// Start the analysis
|
|
274
|
+
const analysisId = await this.l1FeeAnalyzer.startAnalysis(
|
|
275
|
+
l2SlotNumber,
|
|
276
|
+
gasLimit > 0n ? gasLimit : SequencerPublisher.PROPOSE_GAS_GUESS,
|
|
277
|
+
l1Requests,
|
|
278
|
+
blobConfig,
|
|
279
|
+
onComplete,
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
this.log.info('Started L1 fee analysis', {
|
|
283
|
+
analysisId,
|
|
284
|
+
l2SlotNumber: l2SlotNumber.toString(),
|
|
285
|
+
requestCount: requestsToAnalyze.length,
|
|
286
|
+
hasBlobConfig: !!blobConfig,
|
|
287
|
+
gasLimit: gasLimit.toString(),
|
|
288
|
+
actions: requestsToAnalyze.map(r => r.action),
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Return the analysis result (will be incomplete until block mines)
|
|
292
|
+
return this.l1FeeAnalyzer.getAnalysis(analysisId);
|
|
293
|
+
}
|
|
294
|
+
|
|
214
295
|
/**
|
|
215
296
|
* Sends all requests that are still valid.
|
|
216
297
|
* @returns one of:
|
|
@@ -218,10 +299,11 @@ export class SequencerPublisher {
|
|
|
218
299
|
* - a receipt and errorMsg if it failed on L1
|
|
219
300
|
* - undefined if no valid requests are found OR the tx failed to send.
|
|
220
301
|
*/
|
|
302
|
+
@trackSpan('SequencerPublisher.sendRequests')
|
|
221
303
|
public async sendRequests() {
|
|
222
304
|
const requestsToProcess = [...this.requests];
|
|
223
305
|
this.requests = [];
|
|
224
|
-
if (this.interrupted) {
|
|
306
|
+
if (this.interrupted || requestsToProcess.length === 0) {
|
|
225
307
|
return undefined;
|
|
226
308
|
}
|
|
227
309
|
const currentL2Slot = this.getCurrentL2Slot();
|
|
@@ -335,14 +417,14 @@ export class SequencerPublisher {
|
|
|
335
417
|
public canProposeAtNextEthBlock(
|
|
336
418
|
tipArchive: Fr,
|
|
337
419
|
msgSender: EthAddress,
|
|
338
|
-
opts: {
|
|
420
|
+
opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
339
421
|
) {
|
|
340
422
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
341
423
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
342
424
|
|
|
343
425
|
return this.rollupContract
|
|
344
426
|
.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
|
|
345
|
-
forcePendingCheckpointNumber: opts.
|
|
427
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
346
428
|
})
|
|
347
429
|
.catch(err => {
|
|
348
430
|
if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
|
|
@@ -361,7 +443,11 @@ export class SequencerPublisher {
|
|
|
361
443
|
* It will throw if the block header is invalid.
|
|
362
444
|
* @param header - The block header to validate
|
|
363
445
|
*/
|
|
364
|
-
|
|
446
|
+
@trackSpan('SequencerPublisher.validateBlockHeader')
|
|
447
|
+
public async validateBlockHeader(
|
|
448
|
+
header: CheckpointHeader,
|
|
449
|
+
opts?: { forcePendingCheckpointNumber: CheckpointNumber | undefined },
|
|
450
|
+
): Promise<void> {
|
|
365
451
|
const flags = { ignoreDA: true, ignoreSignatures: true };
|
|
366
452
|
|
|
367
453
|
const args = [
|
|
@@ -370,12 +456,14 @@ export class SequencerPublisher {
|
|
|
370
456
|
[], // no signers
|
|
371
457
|
Signature.empty().toViemSignature(),
|
|
372
458
|
`0x${'0'.repeat(64)}`, // 32 empty bytes
|
|
373
|
-
header.
|
|
459
|
+
header.blobsHash.toString(),
|
|
374
460
|
flags,
|
|
375
461
|
] as const;
|
|
376
462
|
|
|
377
463
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
378
|
-
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
464
|
+
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
465
|
+
opts?.forcePendingCheckpointNumber,
|
|
466
|
+
);
|
|
379
467
|
let balance = 0n;
|
|
380
468
|
if (this.config.fishermanMode) {
|
|
381
469
|
// In fisherman mode, we can't know where the proposer is publishing from
|
|
@@ -402,77 +490,90 @@ export class SequencerPublisher {
|
|
|
402
490
|
}
|
|
403
491
|
|
|
404
492
|
/**
|
|
405
|
-
* Simulate making a call to invalidate a
|
|
406
|
-
* @param
|
|
493
|
+
* Simulate making a call to invalidate a checkpoint with invalid attestations. Returns undefined if no need to invalidate.
|
|
494
|
+
* @param validationResult - The validation result indicating which checkpoint to invalidate (as returned by the archiver)
|
|
407
495
|
*/
|
|
408
|
-
public async
|
|
409
|
-
validationResult:
|
|
410
|
-
): Promise<
|
|
496
|
+
public async simulateInvalidateCheckpoint(
|
|
497
|
+
validationResult: ValidateCheckpointResult,
|
|
498
|
+
): Promise<InvalidateCheckpointRequest | undefined> {
|
|
411
499
|
if (validationResult.valid) {
|
|
412
500
|
return undefined;
|
|
413
501
|
}
|
|
414
502
|
|
|
415
|
-
const { reason,
|
|
416
|
-
const
|
|
417
|
-
const logData = { ...
|
|
503
|
+
const { reason, checkpoint } = validationResult;
|
|
504
|
+
const checkpointNumber = checkpoint.checkpointNumber;
|
|
505
|
+
const logData = { ...checkpoint, reason };
|
|
418
506
|
|
|
419
|
-
const
|
|
420
|
-
if (
|
|
507
|
+
const currentCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
508
|
+
if (currentCheckpointNumber < checkpointNumber) {
|
|
421
509
|
this.log.verbose(
|
|
422
|
-
`Skipping
|
|
423
|
-
{
|
|
510
|
+
`Skipping checkpoint ${checkpointNumber} invalidation since it has already been removed from the pending chain`,
|
|
511
|
+
{ currentCheckpointNumber, ...logData },
|
|
424
512
|
);
|
|
425
513
|
return undefined;
|
|
426
514
|
}
|
|
427
515
|
|
|
428
|
-
const request = this.
|
|
429
|
-
this.log.debug(`Simulating invalidate
|
|
516
|
+
const request = this.buildInvalidateCheckpointRequest(validationResult);
|
|
517
|
+
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
|
|
430
518
|
|
|
431
519
|
try {
|
|
432
520
|
const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, ErrorsAbi);
|
|
433
|
-
this.log.verbose(`Simulation for invalidate
|
|
521
|
+
this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} succeeded`, {
|
|
522
|
+
...logData,
|
|
523
|
+
request,
|
|
524
|
+
gasUsed,
|
|
525
|
+
});
|
|
434
526
|
|
|
435
|
-
return {
|
|
527
|
+
return {
|
|
528
|
+
request,
|
|
529
|
+
gasUsed,
|
|
530
|
+
checkpointNumber,
|
|
531
|
+
forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
|
|
532
|
+
reason,
|
|
533
|
+
};
|
|
436
534
|
} catch (err) {
|
|
437
535
|
const viemError = formatViemError(err);
|
|
438
536
|
|
|
439
|
-
// If the error is due to the
|
|
440
|
-
// we can safely ignore it and return undefined so we go ahead with
|
|
537
|
+
// If the error is due to the checkpoint not being in the pending chain, and it was indeed removed by someone else,
|
|
538
|
+
// we can safely ignore it and return undefined so we go ahead with checkpoint building.
|
|
441
539
|
if (viemError.message?.includes('Rollup__BlockNotInPendingChain')) {
|
|
442
540
|
this.log.verbose(
|
|
443
|
-
`Simulation for invalidate
|
|
541
|
+
`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
|
|
444
542
|
{ ...logData, request, error: viemError.message },
|
|
445
543
|
);
|
|
446
|
-
const
|
|
447
|
-
if (
|
|
448
|
-
this.log.verbose(`
|
|
544
|
+
const latestPendingCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
545
|
+
if (latestPendingCheckpointNumber < checkpointNumber) {
|
|
546
|
+
this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, { ...logData });
|
|
449
547
|
return undefined;
|
|
450
548
|
} else {
|
|
451
549
|
this.log.error(
|
|
452
|
-
`Simulation for invalidate ${
|
|
550
|
+
`Simulation for invalidate checkpoint ${checkpointNumber} failed and it is still in pending chain`,
|
|
453
551
|
viemError,
|
|
454
552
|
logData,
|
|
455
553
|
);
|
|
456
|
-
throw new Error(
|
|
457
|
-
|
|
458
|
-
|
|
554
|
+
throw new Error(
|
|
555
|
+
`Failed to simulate invalidate checkpoint ${checkpointNumber} while it is still in pending chain`,
|
|
556
|
+
{
|
|
557
|
+
cause: viemError,
|
|
558
|
+
},
|
|
559
|
+
);
|
|
459
560
|
}
|
|
460
561
|
}
|
|
461
562
|
|
|
462
|
-
// Otherwise, throw. We cannot build the next
|
|
463
|
-
this.log.error(`Simulation for invalidate
|
|
464
|
-
throw new Error(`Failed to simulate invalidate
|
|
563
|
+
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
564
|
+
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
|
|
565
|
+
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
|
|
465
566
|
}
|
|
466
567
|
}
|
|
467
568
|
|
|
468
|
-
private
|
|
569
|
+
private buildInvalidateCheckpointRequest(validationResult: ValidateCheckpointResult) {
|
|
469
570
|
if (validationResult.valid) {
|
|
470
|
-
throw new Error('Cannot invalidate a valid
|
|
571
|
+
throw new Error('Cannot invalidate a valid checkpoint');
|
|
471
572
|
}
|
|
472
573
|
|
|
473
|
-
const {
|
|
474
|
-
const logData = { ...
|
|
475
|
-
this.log.debug(`
|
|
574
|
+
const { checkpoint, committee, reason } = validationResult;
|
|
575
|
+
const logData = { ...checkpoint, reason };
|
|
576
|
+
this.log.debug(`Building invalidate checkpoint ${checkpoint.checkpointNumber} request`, logData);
|
|
476
577
|
|
|
477
578
|
const attestationsAndSigners = new CommitteeAttestationsAndSigners(
|
|
478
579
|
validationResult.attestations,
|
|
@@ -480,14 +581,14 @@ export class SequencerPublisher {
|
|
|
480
581
|
|
|
481
582
|
if (reason === 'invalid-attestation') {
|
|
482
583
|
return this.rollupContract.buildInvalidateBadAttestationRequest(
|
|
483
|
-
|
|
584
|
+
checkpoint.checkpointNumber,
|
|
484
585
|
attestationsAndSigners,
|
|
485
586
|
committee,
|
|
486
587
|
validationResult.invalidIndex,
|
|
487
588
|
);
|
|
488
589
|
} else if (reason === 'insufficient-attestations') {
|
|
489
590
|
return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
|
|
490
|
-
|
|
591
|
+
checkpoint.checkpointNumber,
|
|
491
592
|
attestationsAndSigners,
|
|
492
593
|
committee,
|
|
493
594
|
);
|
|
@@ -497,45 +598,39 @@ export class SequencerPublisher {
|
|
|
497
598
|
}
|
|
498
599
|
}
|
|
499
600
|
|
|
500
|
-
/**
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
*
|
|
505
|
-
* @param block - The block to propose
|
|
506
|
-
* @param attestationData - The block's attestation data
|
|
507
|
-
*
|
|
508
|
-
*/
|
|
509
|
-
public async validateBlockForSubmission(
|
|
510
|
-
block: L2Block,
|
|
601
|
+
/** Simulates `propose` to make sure that the checkpoint is valid for submission */
|
|
602
|
+
@trackSpan('SequencerPublisher.validateCheckpointForSubmission')
|
|
603
|
+
public async validateCheckpointForSubmission(
|
|
604
|
+
checkpoint: Checkpoint,
|
|
511
605
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
512
606
|
attestationsAndSignersSignature: Signature,
|
|
513
|
-
options: {
|
|
607
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
514
608
|
): Promise<bigint> {
|
|
515
609
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
516
610
|
|
|
611
|
+
// TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
|
|
517
612
|
// If we have no attestations, we still need to provide the empty attestations
|
|
518
613
|
// so that the committee is recalculated correctly
|
|
519
|
-
const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
520
|
-
if (ignoreSignatures) {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
const blobFields =
|
|
614
|
+
// const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
615
|
+
// if (ignoreSignatures) {
|
|
616
|
+
// const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
|
|
617
|
+
// if (!committee) {
|
|
618
|
+
// this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
619
|
+
// throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
620
|
+
// }
|
|
621
|
+
// attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
622
|
+
// CommitteeAttestation.fromAddress(committeeMember),
|
|
623
|
+
// );
|
|
624
|
+
// }
|
|
625
|
+
|
|
626
|
+
const blobFields = checkpoint.toBlobFields();
|
|
532
627
|
const blobs = getBlobsPerL1Block(blobFields);
|
|
533
628
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
534
629
|
|
|
535
630
|
const args = [
|
|
536
631
|
{
|
|
537
|
-
header:
|
|
538
|
-
archive: toHex(
|
|
632
|
+
header: checkpoint.header.toViem(),
|
|
633
|
+
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
539
634
|
oracleInput: {
|
|
540
635
|
feeAssetPriceModifier: 0n,
|
|
541
636
|
},
|
|
@@ -573,10 +668,19 @@ export class SequencerPublisher {
|
|
|
573
668
|
const round = await base.computeRound(slotNumber);
|
|
574
669
|
const roundInfo = await base.getRoundInfo(this.rollupContract.address, round);
|
|
575
670
|
|
|
671
|
+
if (roundInfo.quorumReached) {
|
|
672
|
+
return false;
|
|
673
|
+
}
|
|
674
|
+
|
|
576
675
|
if (roundInfo.lastSignalSlot >= slotNumber) {
|
|
577
676
|
return false;
|
|
578
677
|
}
|
|
579
678
|
|
|
679
|
+
if (await this.isPayloadEmpty(payload)) {
|
|
680
|
+
this.log.warn(`Skipping vote cast for payload with empty code`);
|
|
681
|
+
return false;
|
|
682
|
+
}
|
|
683
|
+
|
|
580
684
|
const cachedLastVote = this.lastActions[signalType];
|
|
581
685
|
this.lastActions[signalType] = slotNumber;
|
|
582
686
|
const action = signalType;
|
|
@@ -619,14 +723,14 @@ export class SequencerPublisher {
|
|
|
619
723
|
const logData = { ...result, slotNumber, round, payload: payload.toString() };
|
|
620
724
|
if (!success) {
|
|
621
725
|
this.log.error(
|
|
622
|
-
`Signaling in
|
|
726
|
+
`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} failed`,
|
|
623
727
|
logData,
|
|
624
728
|
);
|
|
625
729
|
this.lastActions[signalType] = cachedLastVote;
|
|
626
730
|
return false;
|
|
627
731
|
} else {
|
|
628
732
|
this.log.info(
|
|
629
|
-
`Signaling in
|
|
733
|
+
`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} succeeded`,
|
|
630
734
|
logData,
|
|
631
735
|
);
|
|
632
736
|
return true;
|
|
@@ -636,6 +740,17 @@ export class SequencerPublisher {
|
|
|
636
740
|
return true;
|
|
637
741
|
}
|
|
638
742
|
|
|
743
|
+
private async isPayloadEmpty(payload: EthAddress): Promise<boolean> {
|
|
744
|
+
const key = payload.toString();
|
|
745
|
+
const cached = this.isPayloadEmptyCache.get(key);
|
|
746
|
+
if (cached) {
|
|
747
|
+
return cached;
|
|
748
|
+
}
|
|
749
|
+
const isEmpty = !(await this.l1TxUtils.getCode(payload));
|
|
750
|
+
this.isPayloadEmptyCache.set(key, isEmpty);
|
|
751
|
+
return isEmpty;
|
|
752
|
+
}
|
|
753
|
+
|
|
639
754
|
/**
|
|
640
755
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
641
756
|
* @param slotNumber - The slot number to cast a signal for.
|
|
@@ -783,27 +898,21 @@ export class SequencerPublisher {
|
|
|
783
898
|
return true;
|
|
784
899
|
}
|
|
785
900
|
|
|
786
|
-
/**
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
* @param block - L2 block to propose.
|
|
790
|
-
* @returns True if the tx has been enqueued, throws otherwise. See #9315
|
|
791
|
-
*/
|
|
792
|
-
public async enqueueProposeL2Block(
|
|
793
|
-
block: L2Block,
|
|
901
|
+
/** Simulates and enqueues a proposal for a checkpoint on L1 */
|
|
902
|
+
public async enqueueProposeCheckpoint(
|
|
903
|
+
checkpoint: Checkpoint,
|
|
794
904
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
795
905
|
attestationsAndSignersSignature: Signature,
|
|
796
|
-
opts: { txTimeoutAt?: Date;
|
|
797
|
-
): Promise<
|
|
798
|
-
const checkpointHeader =
|
|
906
|
+
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
907
|
+
): Promise<void> {
|
|
908
|
+
const checkpointHeader = checkpoint.header;
|
|
799
909
|
|
|
800
|
-
const blobFields =
|
|
910
|
+
const blobFields = checkpoint.toBlobFields();
|
|
801
911
|
const blobs = getBlobsPerL1Block(blobFields);
|
|
802
912
|
|
|
803
913
|
const proposeTxArgs = {
|
|
804
914
|
header: checkpointHeader,
|
|
805
|
-
archive:
|
|
806
|
-
body: block.body.toBuffer(),
|
|
915
|
+
archive: checkpoint.archive.root.toBuffer(),
|
|
807
916
|
blobs,
|
|
808
917
|
attestationsAndSigners,
|
|
809
918
|
attestationsAndSignersSignature,
|
|
@@ -817,22 +926,29 @@ export class SequencerPublisher {
|
|
|
817
926
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
818
927
|
// make time consistency checks break.
|
|
819
928
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
820
|
-
ts = await this.
|
|
929
|
+
ts = await this.validateCheckpointForSubmission(
|
|
930
|
+
checkpoint,
|
|
931
|
+
attestationsAndSigners,
|
|
932
|
+
attestationsAndSignersSignature,
|
|
933
|
+
opts,
|
|
934
|
+
);
|
|
821
935
|
} catch (err: any) {
|
|
822
|
-
this.log.error(`
|
|
823
|
-
...
|
|
824
|
-
slotNumber:
|
|
825
|
-
|
|
936
|
+
this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
937
|
+
...checkpoint.getStats(),
|
|
938
|
+
slotNumber: checkpoint.header.slotNumber,
|
|
939
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
826
940
|
});
|
|
827
941
|
throw err;
|
|
828
942
|
}
|
|
829
943
|
|
|
830
|
-
this.log.verbose(`Enqueuing
|
|
831
|
-
await this.addProposeTx(
|
|
832
|
-
return true;
|
|
944
|
+
this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
|
|
945
|
+
await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
|
|
833
946
|
}
|
|
834
947
|
|
|
835
|
-
public
|
|
948
|
+
public enqueueInvalidateCheckpoint(
|
|
949
|
+
request: InvalidateCheckpointRequest | undefined,
|
|
950
|
+
opts: { txTimeoutAt?: Date } = {},
|
|
951
|
+
) {
|
|
836
952
|
if (!request) {
|
|
837
953
|
return;
|
|
838
954
|
}
|
|
@@ -840,9 +956,9 @@ export class SequencerPublisher {
|
|
|
840
956
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
841
957
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(request.gasUsed) * 64) / 63)));
|
|
842
958
|
|
|
843
|
-
const { gasUsed,
|
|
844
|
-
const logData = { gasUsed,
|
|
845
|
-
this.log.verbose(`Enqueuing invalidate
|
|
959
|
+
const { gasUsed, checkpointNumber } = request;
|
|
960
|
+
const logData = { gasUsed, checkpointNumber, gasLimit, opts };
|
|
961
|
+
this.log.verbose(`Enqueuing invalidate checkpoint request`, logData);
|
|
846
962
|
this.addRequest({
|
|
847
963
|
action: `invalidate-by-${request.reason}`,
|
|
848
964
|
request: request.request,
|
|
@@ -855,9 +971,9 @@ export class SequencerPublisher {
|
|
|
855
971
|
result.receipt.status === 'success' &&
|
|
856
972
|
tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointInvalidated');
|
|
857
973
|
if (!success) {
|
|
858
|
-
this.log.warn(`Invalidate
|
|
974
|
+
this.log.warn(`Invalidate checkpoint ${request.checkpointNumber} failed`, { ...result, ...logData });
|
|
859
975
|
} else {
|
|
860
|
-
this.log.info(`Invalidate
|
|
976
|
+
this.log.info(`Invalidate checkpoint ${request.checkpointNumber} succeeded`, { ...result, ...logData });
|
|
861
977
|
}
|
|
862
978
|
return !!success;
|
|
863
979
|
},
|
|
@@ -936,7 +1052,7 @@ export class SequencerPublisher {
|
|
|
936
1052
|
private async prepareProposeTx(
|
|
937
1053
|
encodedData: L1ProcessArgs,
|
|
938
1054
|
timestamp: bigint,
|
|
939
|
-
options: {
|
|
1055
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
940
1056
|
) {
|
|
941
1057
|
const kzg = Blob.getViemKzgInstance();
|
|
942
1058
|
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
@@ -1017,7 +1133,7 @@ export class SequencerPublisher {
|
|
|
1017
1133
|
`0x${string}`,
|
|
1018
1134
|
],
|
|
1019
1135
|
timestamp: bigint,
|
|
1020
|
-
options: {
|
|
1136
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1021
1137
|
) {
|
|
1022
1138
|
const rollupData = encodeFunctionData({
|
|
1023
1139
|
abi: RollupAbi,
|
|
@@ -1025,10 +1141,10 @@ export class SequencerPublisher {
|
|
|
1025
1141
|
args,
|
|
1026
1142
|
});
|
|
1027
1143
|
|
|
1028
|
-
// override the pending
|
|
1029
|
-
const
|
|
1030
|
-
options.
|
|
1031
|
-
? await this.rollupContract.makePendingCheckpointNumberOverride(options.
|
|
1144
|
+
// override the pending checkpoint number if requested
|
|
1145
|
+
const forcePendingCheckpointNumberStateDiff = (
|
|
1146
|
+
options.forcePendingCheckpointNumber !== undefined
|
|
1147
|
+
? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber)
|
|
1032
1148
|
: []
|
|
1033
1149
|
).flatMap(override => override.stateDiff ?? []);
|
|
1034
1150
|
|
|
@@ -1038,7 +1154,7 @@ export class SequencerPublisher {
|
|
|
1038
1154
|
// @note we override checkBlob to false since blobs are not part simulate()
|
|
1039
1155
|
stateDiff: [
|
|
1040
1156
|
{ slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
|
|
1041
|
-
...
|
|
1157
|
+
...forcePendingCheckpointNumberStateDiff,
|
|
1042
1158
|
],
|
|
1043
1159
|
},
|
|
1044
1160
|
];
|
|
@@ -1090,11 +1206,12 @@ export class SequencerPublisher {
|
|
|
1090
1206
|
}
|
|
1091
1207
|
|
|
1092
1208
|
private async addProposeTx(
|
|
1093
|
-
|
|
1209
|
+
checkpoint: Checkpoint,
|
|
1094
1210
|
encodedData: L1ProcessArgs,
|
|
1095
|
-
opts: { txTimeoutAt?: Date;
|
|
1211
|
+
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
1096
1212
|
timestamp: bigint,
|
|
1097
1213
|
): Promise<void> {
|
|
1214
|
+
const slot = checkpoint.header.slotNumber;
|
|
1098
1215
|
const timer = new Timer();
|
|
1099
1216
|
const kzg = Blob.getViemKzgInstance();
|
|
1100
1217
|
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
@@ -1109,11 +1226,13 @@ export class SequencerPublisher {
|
|
|
1109
1226
|
SequencerPublisher.MULTICALL_OVERHEAD_GAS_GUESS, // We issue the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
1110
1227
|
);
|
|
1111
1228
|
|
|
1112
|
-
// Send the blobs to the blob
|
|
1113
|
-
// tx fails but it does get mined. We make sure that the blobs are sent to the blob
|
|
1114
|
-
void
|
|
1115
|
-
this.
|
|
1116
|
-
|
|
1229
|
+
// Send the blobs to the blob client preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
|
|
1230
|
+
// tx fails but it does get mined. We make sure that the blobs are sent to the blob client regardless of the tx outcome.
|
|
1231
|
+
void Promise.resolve().then(() =>
|
|
1232
|
+
this.blobClient.sendBlobsToFilestore(encodedData.blobs).catch(_err => {
|
|
1233
|
+
this.log.error('Failed to send blobs to blob client');
|
|
1234
|
+
}),
|
|
1235
|
+
);
|
|
1117
1236
|
|
|
1118
1237
|
return this.addRequest({
|
|
1119
1238
|
action: 'propose',
|
|
@@ -1121,7 +1240,7 @@ export class SequencerPublisher {
|
|
|
1121
1240
|
to: this.rollupContract.address,
|
|
1122
1241
|
data: rollupData,
|
|
1123
1242
|
},
|
|
1124
|
-
lastValidL2Slot:
|
|
1243
|
+
lastValidL2Slot: checkpoint.header.slotNumber,
|
|
1125
1244
|
gasConfig: { ...opts, gasLimit },
|
|
1126
1245
|
blobConfig: {
|
|
1127
1246
|
blobs: encodedData.blobs.map(b => b.data),
|
|
@@ -1136,11 +1255,12 @@ export class SequencerPublisher {
|
|
|
1136
1255
|
receipt &&
|
|
1137
1256
|
receipt.status === 'success' &&
|
|
1138
1257
|
tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointProposed');
|
|
1258
|
+
|
|
1139
1259
|
if (success) {
|
|
1140
1260
|
const endBlock = receipt.blockNumber;
|
|
1141
1261
|
const inclusionBlocks = Number(endBlock - startBlock);
|
|
1142
1262
|
const { calldataGas, calldataSize, sender } = stats!;
|
|
1143
|
-
const publishStats:
|
|
1263
|
+
const publishStats: L1PublishCheckpointStats = {
|
|
1144
1264
|
gasPrice: receipt.effectiveGasPrice,
|
|
1145
1265
|
gasUsed: receipt.gasUsed,
|
|
1146
1266
|
blobGasUsed: receipt.blobGasUsed ?? 0n,
|
|
@@ -1149,23 +1269,26 @@ export class SequencerPublisher {
|
|
|
1149
1269
|
calldataGas,
|
|
1150
1270
|
calldataSize,
|
|
1151
1271
|
sender,
|
|
1152
|
-
...
|
|
1272
|
+
...checkpoint.getStats(),
|
|
1153
1273
|
eventName: 'rollup-published-to-l1',
|
|
1154
1274
|
blobCount: encodedData.blobs.length,
|
|
1155
1275
|
inclusionBlocks,
|
|
1156
1276
|
};
|
|
1157
|
-
this.log.info(`Published
|
|
1277
|
+
this.log.info(`Published checkpoint ${checkpoint.number} at slot ${slot} to rollup contract`, {
|
|
1278
|
+
...stats,
|
|
1279
|
+
...checkpoint.getStats(),
|
|
1280
|
+
...pick(receipt, 'transactionHash', 'blockHash'),
|
|
1281
|
+
});
|
|
1158
1282
|
this.metrics.recordProcessBlockTx(timer.ms(), publishStats);
|
|
1159
1283
|
|
|
1160
1284
|
return true;
|
|
1161
1285
|
} else {
|
|
1162
1286
|
this.metrics.recordFailedTx('process');
|
|
1163
|
-
this.log.error(
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
});
|
|
1287
|
+
this.log.error(
|
|
1288
|
+
`Publishing checkpoint at slot ${slot} failed with ${errorMsg ?? 'no error message'}`,
|
|
1289
|
+
undefined,
|
|
1290
|
+
{ ...checkpoint.getStats(), ...receipt },
|
|
1291
|
+
);
|
|
1169
1292
|
return false;
|
|
1170
1293
|
}
|
|
1171
1294
|
},
|