@aztec/sequencer-client 0.0.1-commit.d3ec352c → 0.0.1-commit.e310a4c8
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 +33 -26
- 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 +21 -12
- 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 +23 -86
- package/dest/publisher/sequencer-publisher.d.ts +48 -41
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +600 -129
- package/dest/sequencer/checkpoint_proposal_job.d.ts +96 -0
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_proposal_job.js +1192 -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 +23 -3
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +143 -70
- package/dest/sequencer/sequencer.d.ts +107 -131
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +690 -602
- 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 +220 -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 +30 -28
- package/src/client/sequencer-client.ts +31 -42
- package/src/config.ts +78 -34
- package/src/global_variable_builder/global_builder.ts +63 -59
- 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 +19 -71
- package/src/publisher/sequencer-publisher.ts +293 -170
- package/src/sequencer/README.md +531 -0
- package/src/sequencer/checkpoint_proposal_job.ts +874 -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 +190 -78
- package/src/sequencer/sequencer.ts +430 -804
- 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 +309 -0
- package/src/test/utils.ts +164 -0
- package/dest/sequencer/block_builder.d.ts +0 -28
- 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 -18
- 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 -133
|
@@ -1,44 +1,47 @@
|
|
|
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
|
+
MAX_L1_TX_LIMIT,
|
|
22
|
+
type TransactionStats,
|
|
21
23
|
WEI_CONST,
|
|
22
|
-
|
|
23
|
-
tryExtractEvent,
|
|
24
|
-
} from '@aztec/ethereum';
|
|
24
|
+
} from '@aztec/ethereum/l1-tx-utils';
|
|
25
25
|
import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs';
|
|
26
|
+
import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
26
27
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
27
28
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
28
|
-
import {
|
|
29
|
+
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
30
|
+
import { pick } from '@aztec/foundation/collection';
|
|
31
|
+
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
29
32
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
30
33
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
31
|
-
import type { Fr } from '@aztec/foundation/fields';
|
|
32
34
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
33
35
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
34
36
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
35
37
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
36
38
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
37
|
-
import {
|
|
39
|
+
import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
40
|
+
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
38
41
|
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
39
42
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
40
|
-
import type {
|
|
41
|
-
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
43
|
+
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
44
|
+
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
42
45
|
|
|
43
46
|
import { type StateOverride, type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
|
|
44
47
|
|
|
@@ -78,12 +81,12 @@ type GovernanceSignalAction = Extract<Action, 'governance-signal' | 'empire-slas
|
|
|
78
81
|
// Sorting for actions such that invalidations go before proposals, and proposals go before votes
|
|
79
82
|
export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
|
|
80
83
|
|
|
81
|
-
export type
|
|
84
|
+
export type InvalidateCheckpointRequest = {
|
|
82
85
|
request: L1TxRequest;
|
|
83
86
|
reason: 'invalid-attestation' | 'insufficient-attestations';
|
|
84
87
|
gasUsed: bigint;
|
|
85
|
-
|
|
86
|
-
|
|
88
|
+
checkpointNumber: CheckpointNumber;
|
|
89
|
+
forcePendingCheckpointNumber: CheckpointNumber;
|
|
87
90
|
};
|
|
88
91
|
|
|
89
92
|
interface RequestWithExpiry {
|
|
@@ -108,18 +111,18 @@ export class SequencerPublisher {
|
|
|
108
111
|
|
|
109
112
|
protected lastActions: Partial<Record<Action, SlotNumber>> = {};
|
|
110
113
|
|
|
114
|
+
private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
|
|
115
|
+
|
|
111
116
|
protected log: Logger;
|
|
112
117
|
protected ethereumSlotDuration: bigint;
|
|
113
118
|
|
|
114
|
-
private
|
|
119
|
+
private blobClient: BlobClientInterface;
|
|
115
120
|
|
|
116
121
|
/** Address to use for simulations in fisherman mode (actual proposer's address) */
|
|
117
122
|
private proposerAddressForSimulation?: EthAddress;
|
|
118
|
-
// @note - with blobs, the below estimate seems too large.
|
|
119
|
-
// Total used for full block from int_l1_pub e2e test: 1m (of which 86k is 1x blob)
|
|
120
|
-
// Total used for emptier block from above test: 429k (of which 84k is 1x blob)
|
|
121
|
-
public static PROPOSE_GAS_GUESS: bigint = 12_000_000n;
|
|
122
123
|
|
|
124
|
+
/** L1 fee analyzer for fisherman mode */
|
|
125
|
+
private l1FeeAnalyzer?: L1FeeAnalyzer;
|
|
123
126
|
// A CALL to a cold address is 2700 gas
|
|
124
127
|
public static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
125
128
|
|
|
@@ -132,13 +135,15 @@ export class SequencerPublisher {
|
|
|
132
135
|
public slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
133
136
|
public slashFactoryContract: SlashFactoryContract;
|
|
134
137
|
|
|
138
|
+
public readonly tracer: Tracer;
|
|
139
|
+
|
|
135
140
|
protected requests: RequestWithExpiry[] = [];
|
|
136
141
|
|
|
137
142
|
constructor(
|
|
138
143
|
private config: TxSenderConfig & PublisherConfig & Pick<L1ContractsConfig, 'ethereumSlotDuration'>,
|
|
139
144
|
deps: {
|
|
140
145
|
telemetry?: TelemetryClient;
|
|
141
|
-
|
|
146
|
+
blobClient: BlobClientInterface;
|
|
142
147
|
l1TxUtils: L1TxUtilsWithBlobs;
|
|
143
148
|
rollupContract: RollupContract;
|
|
144
149
|
slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
@@ -156,11 +161,11 @@ export class SequencerPublisher {
|
|
|
156
161
|
this.epochCache = deps.epochCache;
|
|
157
162
|
this.lastActions = deps.lastActions;
|
|
158
163
|
|
|
159
|
-
this.
|
|
160
|
-
deps.blobSinkClient ?? createBlobSinkClient(config, { logger: createLogger('sequencer:blob-sink:client') });
|
|
164
|
+
this.blobClient = deps.blobClient;
|
|
161
165
|
|
|
162
166
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
163
167
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
168
|
+
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
164
169
|
this.l1TxUtils = deps.l1TxUtils;
|
|
165
170
|
|
|
166
171
|
this.rollupContract = deps.rollupContract;
|
|
@@ -174,6 +179,15 @@ export class SequencerPublisher {
|
|
|
174
179
|
this.slashingProposerContract = newSlashingProposer;
|
|
175
180
|
});
|
|
176
181
|
this.slashFactoryContract = deps.slashFactoryContract;
|
|
182
|
+
|
|
183
|
+
// Initialize L1 fee analyzer for fisherman mode
|
|
184
|
+
if (config.fishermanMode) {
|
|
185
|
+
this.l1FeeAnalyzer = new L1FeeAnalyzer(
|
|
186
|
+
this.l1TxUtils.client,
|
|
187
|
+
deps.dateProvider,
|
|
188
|
+
createLogger('sequencer:publisher:fee-analyzer'),
|
|
189
|
+
);
|
|
190
|
+
}
|
|
177
191
|
}
|
|
178
192
|
|
|
179
193
|
public getRollupContract(): RollupContract {
|
|
@@ -184,6 +198,13 @@ export class SequencerPublisher {
|
|
|
184
198
|
return this.l1TxUtils.getSenderAddress();
|
|
185
199
|
}
|
|
186
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Gets the L1 fee analyzer instance (only available in fisherman mode)
|
|
203
|
+
*/
|
|
204
|
+
public getL1FeeAnalyzer(): L1FeeAnalyzer | undefined {
|
|
205
|
+
return this.l1FeeAnalyzer;
|
|
206
|
+
}
|
|
207
|
+
|
|
187
208
|
/**
|
|
188
209
|
* Sets the proposer address to use for simulations in fisherman mode.
|
|
189
210
|
* @param proposerAddress - The actual proposer's address to use for balance lookups in simulations
|
|
@@ -211,6 +232,62 @@ export class SequencerPublisher {
|
|
|
211
232
|
}
|
|
212
233
|
}
|
|
213
234
|
|
|
235
|
+
/**
|
|
236
|
+
* Analyzes L1 fees for the pending requests without sending them.
|
|
237
|
+
* This is used in fisherman mode to validate fee calculations.
|
|
238
|
+
* @param l2SlotNumber - The L2 slot number for this analysis
|
|
239
|
+
* @param onComplete - Optional callback to invoke when analysis completes (after block is mined)
|
|
240
|
+
* @returns The analysis result (incomplete until block mines), or undefined if no requests
|
|
241
|
+
*/
|
|
242
|
+
public async analyzeL1Fees(
|
|
243
|
+
l2SlotNumber: SlotNumber,
|
|
244
|
+
onComplete?: (analysis: L1FeeAnalysisResult) => void,
|
|
245
|
+
): Promise<L1FeeAnalysisResult | undefined> {
|
|
246
|
+
if (!this.l1FeeAnalyzer) {
|
|
247
|
+
this.log.warn('L1 fee analyzer not available (not in fisherman mode)');
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const requestsToAnalyze = [...this.requests];
|
|
252
|
+
if (requestsToAnalyze.length === 0) {
|
|
253
|
+
this.log.debug('No requests to analyze for L1 fees');
|
|
254
|
+
return undefined;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Extract blob config from requests (if any)
|
|
258
|
+
const blobConfigs = requestsToAnalyze.filter(request => request.blobConfig).map(request => request.blobConfig);
|
|
259
|
+
const blobConfig = blobConfigs[0];
|
|
260
|
+
|
|
261
|
+
// Get gas configs
|
|
262
|
+
const gasConfigs = requestsToAnalyze.filter(request => request.gasConfig).map(request => request.gasConfig);
|
|
263
|
+
const gasLimits = gasConfigs.map(g => g?.gasLimit).filter((g): g is bigint => g !== undefined);
|
|
264
|
+
const gasLimit = gasLimits.length > 0 ? gasLimits.reduce((sum, g) => sum + g, 0n) : 0n;
|
|
265
|
+
|
|
266
|
+
// Get the transaction requests
|
|
267
|
+
const l1Requests = requestsToAnalyze.map(r => r.request);
|
|
268
|
+
|
|
269
|
+
// Start the analysis
|
|
270
|
+
const analysisId = await this.l1FeeAnalyzer.startAnalysis(
|
|
271
|
+
l2SlotNumber,
|
|
272
|
+
gasLimit > 0n ? gasLimit : MAX_L1_TX_LIMIT,
|
|
273
|
+
l1Requests,
|
|
274
|
+
blobConfig,
|
|
275
|
+
onComplete,
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
this.log.info('Started L1 fee analysis', {
|
|
279
|
+
analysisId,
|
|
280
|
+
l2SlotNumber: l2SlotNumber.toString(),
|
|
281
|
+
requestCount: requestsToAnalyze.length,
|
|
282
|
+
hasBlobConfig: !!blobConfig,
|
|
283
|
+
gasLimit: gasLimit.toString(),
|
|
284
|
+
actions: requestsToAnalyze.map(r => r.action),
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Return the analysis result (will be incomplete until block mines)
|
|
288
|
+
return this.l1FeeAnalyzer.getAnalysis(analysisId);
|
|
289
|
+
}
|
|
290
|
+
|
|
214
291
|
/**
|
|
215
292
|
* Sends all requests that are still valid.
|
|
216
293
|
* @returns one of:
|
|
@@ -218,10 +295,11 @@ export class SequencerPublisher {
|
|
|
218
295
|
* - a receipt and errorMsg if it failed on L1
|
|
219
296
|
* - undefined if no valid requests are found OR the tx failed to send.
|
|
220
297
|
*/
|
|
298
|
+
@trackSpan('SequencerPublisher.sendRequests')
|
|
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();
|
|
@@ -264,7 +342,16 @@ export class SequencerPublisher {
|
|
|
264
342
|
|
|
265
343
|
// Merge gasConfigs. Yields the sum of gasLimits, and the earliest txTimeoutAt, or undefined if no gasConfig sets them.
|
|
266
344
|
const gasLimits = gasConfigs.map(g => g?.gasLimit).filter((g): g is bigint => g !== undefined);
|
|
267
|
-
|
|
345
|
+
let gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
|
|
346
|
+
// Cap at L1 block gas limit so the node accepts the tx ("gas limit too high" otherwise).
|
|
347
|
+
const maxGas = MAX_L1_TX_LIMIT;
|
|
348
|
+
if (gasLimit !== undefined && gasLimit > maxGas) {
|
|
349
|
+
this.log.debug('Capping bundled tx gas limit to L1 max', {
|
|
350
|
+
requested: gasLimit,
|
|
351
|
+
capped: maxGas,
|
|
352
|
+
});
|
|
353
|
+
gasLimit = maxGas;
|
|
354
|
+
}
|
|
268
355
|
const txTimeoutAts = gasConfigs.map(g => g?.txTimeoutAt).filter((g): g is Date => g !== undefined);
|
|
269
356
|
const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map(g => g.getTime()))) : undefined; // earliest
|
|
270
357
|
const txConfig: RequestWithExpiry['gasConfig'] = { gasLimit, txTimeoutAt };
|
|
@@ -335,17 +422,14 @@ export class SequencerPublisher {
|
|
|
335
422
|
public canProposeAtNextEthBlock(
|
|
336
423
|
tipArchive: Fr,
|
|
337
424
|
msgSender: EthAddress,
|
|
338
|
-
opts: {
|
|
425
|
+
opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
339
426
|
) {
|
|
340
427
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
341
428
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
342
429
|
|
|
343
430
|
return this.rollupContract
|
|
344
431
|
.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
|
|
345
|
-
forcePendingCheckpointNumber:
|
|
346
|
-
opts.forcePendingBlockNumber !== undefined
|
|
347
|
-
? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber)
|
|
348
|
-
: undefined,
|
|
432
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
349
433
|
})
|
|
350
434
|
.catch(err => {
|
|
351
435
|
if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
|
|
@@ -364,10 +448,11 @@ export class SequencerPublisher {
|
|
|
364
448
|
* It will throw if the block header is invalid.
|
|
365
449
|
* @param header - The block header to validate
|
|
366
450
|
*/
|
|
451
|
+
@trackSpan('SequencerPublisher.validateBlockHeader')
|
|
367
452
|
public async validateBlockHeader(
|
|
368
453
|
header: CheckpointHeader,
|
|
369
|
-
opts?: {
|
|
370
|
-
) {
|
|
454
|
+
opts?: { forcePendingCheckpointNumber: CheckpointNumber | undefined },
|
|
455
|
+
): Promise<void> {
|
|
371
456
|
const flags = { ignoreDA: true, ignoreSignatures: true };
|
|
372
457
|
|
|
373
458
|
const args = [
|
|
@@ -376,17 +461,13 @@ export class SequencerPublisher {
|
|
|
376
461
|
[], // no signers
|
|
377
462
|
Signature.empty().toViemSignature(),
|
|
378
463
|
`0x${'0'.repeat(64)}`, // 32 empty bytes
|
|
379
|
-
header.
|
|
464
|
+
header.blobsHash.toString(),
|
|
380
465
|
flags,
|
|
381
466
|
] as const;
|
|
382
467
|
|
|
383
468
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
384
|
-
const optsForcePendingCheckpointNumber =
|
|
385
|
-
opts?.forcePendingBlockNumber !== undefined
|
|
386
|
-
? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber)
|
|
387
|
-
: undefined;
|
|
388
469
|
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
389
|
-
|
|
470
|
+
opts?.forcePendingCheckpointNumber,
|
|
390
471
|
);
|
|
391
472
|
let balance = 0n;
|
|
392
473
|
if (this.config.fishermanMode) {
|
|
@@ -414,77 +495,95 @@ export class SequencerPublisher {
|
|
|
414
495
|
}
|
|
415
496
|
|
|
416
497
|
/**
|
|
417
|
-
* Simulate making a call to invalidate a
|
|
418
|
-
* @param
|
|
498
|
+
* Simulate making a call to invalidate a checkpoint with invalid attestations. Returns undefined if no need to invalidate.
|
|
499
|
+
* @param validationResult - The validation result indicating which checkpoint to invalidate (as returned by the archiver)
|
|
419
500
|
*/
|
|
420
|
-
public async
|
|
421
|
-
validationResult:
|
|
422
|
-
): Promise<
|
|
501
|
+
public async simulateInvalidateCheckpoint(
|
|
502
|
+
validationResult: ValidateCheckpointResult,
|
|
503
|
+
): Promise<InvalidateCheckpointRequest | undefined> {
|
|
423
504
|
if (validationResult.valid) {
|
|
424
505
|
return undefined;
|
|
425
506
|
}
|
|
426
507
|
|
|
427
|
-
const { reason,
|
|
428
|
-
const
|
|
429
|
-
const logData = { ...
|
|
508
|
+
const { reason, checkpoint } = validationResult;
|
|
509
|
+
const checkpointNumber = checkpoint.checkpointNumber;
|
|
510
|
+
const logData = { ...checkpoint, reason };
|
|
430
511
|
|
|
431
|
-
const
|
|
432
|
-
if (
|
|
512
|
+
const currentCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
513
|
+
if (currentCheckpointNumber < checkpointNumber) {
|
|
433
514
|
this.log.verbose(
|
|
434
|
-
`Skipping
|
|
435
|
-
{
|
|
515
|
+
`Skipping checkpoint ${checkpointNumber} invalidation since it has already been removed from the pending chain`,
|
|
516
|
+
{ currentCheckpointNumber, ...logData },
|
|
436
517
|
);
|
|
437
518
|
return undefined;
|
|
438
519
|
}
|
|
439
520
|
|
|
440
|
-
const request = this.
|
|
441
|
-
this.log.debug(`Simulating invalidate
|
|
521
|
+
const request = this.buildInvalidateCheckpointRequest(validationResult);
|
|
522
|
+
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
|
|
442
523
|
|
|
443
524
|
try {
|
|
444
|
-
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
445
|
-
|
|
525
|
+
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
526
|
+
request,
|
|
527
|
+
undefined,
|
|
528
|
+
undefined,
|
|
529
|
+
mergeAbis([request.abi ?? [], ErrorsAbi]),
|
|
530
|
+
);
|
|
531
|
+
this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} succeeded`, {
|
|
532
|
+
...logData,
|
|
533
|
+
request,
|
|
534
|
+
gasUsed,
|
|
535
|
+
});
|
|
446
536
|
|
|
447
|
-
return {
|
|
537
|
+
return {
|
|
538
|
+
request,
|
|
539
|
+
gasUsed,
|
|
540
|
+
checkpointNumber,
|
|
541
|
+
forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
|
|
542
|
+
reason,
|
|
543
|
+
};
|
|
448
544
|
} catch (err) {
|
|
449
545
|
const viemError = formatViemError(err);
|
|
450
546
|
|
|
451
|
-
// If the error is due to the
|
|
452
|
-
// we can safely ignore it and return undefined so we go ahead with
|
|
453
|
-
if (viemError.message?.includes('
|
|
547
|
+
// If the error is due to the checkpoint not being in the pending chain, and it was indeed removed by someone else,
|
|
548
|
+
// we can safely ignore it and return undefined so we go ahead with checkpoint building.
|
|
549
|
+
if (viemError.message?.includes('Rollup__CheckpointNotInPendingChain')) {
|
|
454
550
|
this.log.verbose(
|
|
455
|
-
`Simulation for invalidate
|
|
551
|
+
`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
|
|
456
552
|
{ ...logData, request, error: viemError.message },
|
|
457
553
|
);
|
|
458
|
-
const
|
|
459
|
-
if (
|
|
460
|
-
this.log.verbose(`
|
|
554
|
+
const latestPendingCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
555
|
+
if (latestPendingCheckpointNumber < checkpointNumber) {
|
|
556
|
+
this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, { ...logData });
|
|
461
557
|
return undefined;
|
|
462
558
|
} else {
|
|
463
559
|
this.log.error(
|
|
464
|
-
`Simulation for invalidate ${
|
|
560
|
+
`Simulation for invalidate checkpoint ${checkpointNumber} failed and it is still in pending chain`,
|
|
465
561
|
viemError,
|
|
466
562
|
logData,
|
|
467
563
|
);
|
|
468
|
-
throw new Error(
|
|
469
|
-
|
|
470
|
-
|
|
564
|
+
throw new Error(
|
|
565
|
+
`Failed to simulate invalidate checkpoint ${checkpointNumber} while it is still in pending chain`,
|
|
566
|
+
{
|
|
567
|
+
cause: viemError,
|
|
568
|
+
},
|
|
569
|
+
);
|
|
471
570
|
}
|
|
472
571
|
}
|
|
473
572
|
|
|
474
|
-
// Otherwise, throw. We cannot build the next
|
|
475
|
-
this.log.error(`Simulation for invalidate
|
|
476
|
-
throw new Error(`Failed to simulate invalidate
|
|
573
|
+
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
574
|
+
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
|
|
575
|
+
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
|
|
477
576
|
}
|
|
478
577
|
}
|
|
479
578
|
|
|
480
|
-
private
|
|
579
|
+
private buildInvalidateCheckpointRequest(validationResult: ValidateCheckpointResult) {
|
|
481
580
|
if (validationResult.valid) {
|
|
482
|
-
throw new Error('Cannot invalidate a valid
|
|
581
|
+
throw new Error('Cannot invalidate a valid checkpoint');
|
|
483
582
|
}
|
|
484
583
|
|
|
485
|
-
const {
|
|
486
|
-
const logData = { ...
|
|
487
|
-
this.log.debug(`
|
|
584
|
+
const { checkpoint, committee, reason } = validationResult;
|
|
585
|
+
const logData = { ...checkpoint, reason };
|
|
586
|
+
this.log.debug(`Building invalidate checkpoint ${checkpoint.checkpointNumber} request`, logData);
|
|
488
587
|
|
|
489
588
|
const attestationsAndSigners = new CommitteeAttestationsAndSigners(
|
|
490
589
|
validationResult.attestations,
|
|
@@ -492,14 +591,14 @@ export class SequencerPublisher {
|
|
|
492
591
|
|
|
493
592
|
if (reason === 'invalid-attestation') {
|
|
494
593
|
return this.rollupContract.buildInvalidateBadAttestationRequest(
|
|
495
|
-
|
|
594
|
+
checkpoint.checkpointNumber,
|
|
496
595
|
attestationsAndSigners,
|
|
497
596
|
committee,
|
|
498
597
|
validationResult.invalidIndex,
|
|
499
598
|
);
|
|
500
599
|
} else if (reason === 'insufficient-attestations') {
|
|
501
600
|
return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
|
|
502
|
-
|
|
601
|
+
checkpoint.checkpointNumber,
|
|
503
602
|
attestationsAndSigners,
|
|
504
603
|
committee,
|
|
505
604
|
);
|
|
@@ -509,45 +608,39 @@ export class SequencerPublisher {
|
|
|
509
608
|
}
|
|
510
609
|
}
|
|
511
610
|
|
|
512
|
-
/**
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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,
|
|
611
|
+
/** Simulates `propose` to make sure that the checkpoint is valid for submission */
|
|
612
|
+
@trackSpan('SequencerPublisher.validateCheckpointForSubmission')
|
|
613
|
+
public async validateCheckpointForSubmission(
|
|
614
|
+
checkpoint: Checkpoint,
|
|
523
615
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
524
616
|
attestationsAndSignersSignature: Signature,
|
|
525
|
-
options: {
|
|
617
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
526
618
|
): Promise<bigint> {
|
|
527
619
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
528
620
|
|
|
621
|
+
// TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
|
|
529
622
|
// If we have no attestations, we still need to provide the empty attestations
|
|
530
623
|
// 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 =
|
|
624
|
+
// const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
625
|
+
// if (ignoreSignatures) {
|
|
626
|
+
// const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
|
|
627
|
+
// if (!committee) {
|
|
628
|
+
// this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
629
|
+
// throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
630
|
+
// }
|
|
631
|
+
// attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
632
|
+
// CommitteeAttestation.fromAddress(committeeMember),
|
|
633
|
+
// );
|
|
634
|
+
// }
|
|
635
|
+
|
|
636
|
+
const blobFields = checkpoint.toBlobFields();
|
|
544
637
|
const blobs = getBlobsPerL1Block(blobFields);
|
|
545
638
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
546
639
|
|
|
547
640
|
const args = [
|
|
548
641
|
{
|
|
549
|
-
header:
|
|
550
|
-
archive: toHex(
|
|
642
|
+
header: checkpoint.header.toViem(),
|
|
643
|
+
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
551
644
|
oracleInput: {
|
|
552
645
|
feeAssetPriceModifier: 0n,
|
|
553
646
|
},
|
|
@@ -585,10 +678,19 @@ export class SequencerPublisher {
|
|
|
585
678
|
const round = await base.computeRound(slotNumber);
|
|
586
679
|
const roundInfo = await base.getRoundInfo(this.rollupContract.address, round);
|
|
587
680
|
|
|
681
|
+
if (roundInfo.quorumReached) {
|
|
682
|
+
return false;
|
|
683
|
+
}
|
|
684
|
+
|
|
588
685
|
if (roundInfo.lastSignalSlot >= slotNumber) {
|
|
589
686
|
return false;
|
|
590
687
|
}
|
|
591
688
|
|
|
689
|
+
if (await this.isPayloadEmpty(payload)) {
|
|
690
|
+
this.log.warn(`Skipping vote cast for payload with empty code`);
|
|
691
|
+
return false;
|
|
692
|
+
}
|
|
693
|
+
|
|
592
694
|
const cachedLastVote = this.lastActions[signalType];
|
|
593
695
|
this.lastActions[signalType] = slotNumber;
|
|
594
696
|
const action = signalType;
|
|
@@ -608,7 +710,7 @@ export class SequencerPublisher {
|
|
|
608
710
|
});
|
|
609
711
|
|
|
610
712
|
try {
|
|
611
|
-
await this.l1TxUtils.simulate(request, { time: timestamp }, [], ErrorsAbi);
|
|
713
|
+
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
612
714
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
613
715
|
} catch (err) {
|
|
614
716
|
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, err);
|
|
@@ -631,14 +733,14 @@ export class SequencerPublisher {
|
|
|
631
733
|
const logData = { ...result, slotNumber, round, payload: payload.toString() };
|
|
632
734
|
if (!success) {
|
|
633
735
|
this.log.error(
|
|
634
|
-
`Signaling in
|
|
736
|
+
`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} failed`,
|
|
635
737
|
logData,
|
|
636
738
|
);
|
|
637
739
|
this.lastActions[signalType] = cachedLastVote;
|
|
638
740
|
return false;
|
|
639
741
|
} else {
|
|
640
742
|
this.log.info(
|
|
641
|
-
`Signaling in
|
|
743
|
+
`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} succeeded`,
|
|
642
744
|
logData,
|
|
643
745
|
);
|
|
644
746
|
return true;
|
|
@@ -648,6 +750,17 @@ export class SequencerPublisher {
|
|
|
648
750
|
return true;
|
|
649
751
|
}
|
|
650
752
|
|
|
753
|
+
private async isPayloadEmpty(payload: EthAddress): Promise<boolean> {
|
|
754
|
+
const key = payload.toString();
|
|
755
|
+
const cached = this.isPayloadEmptyCache.get(key);
|
|
756
|
+
if (cached) {
|
|
757
|
+
return cached;
|
|
758
|
+
}
|
|
759
|
+
const isEmpty = !(await this.l1TxUtils.getCode(payload));
|
|
760
|
+
this.isPayloadEmptyCache.set(key, isEmpty);
|
|
761
|
+
return isEmpty;
|
|
762
|
+
}
|
|
763
|
+
|
|
651
764
|
/**
|
|
652
765
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
653
766
|
* @param slotNumber - The slot number to cast a signal for.
|
|
@@ -795,27 +908,21 @@ export class SequencerPublisher {
|
|
|
795
908
|
return true;
|
|
796
909
|
}
|
|
797
910
|
|
|
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,
|
|
911
|
+
/** Simulates and enqueues a proposal for a checkpoint on L1 */
|
|
912
|
+
public async enqueueProposeCheckpoint(
|
|
913
|
+
checkpoint: Checkpoint,
|
|
806
914
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
807
915
|
attestationsAndSignersSignature: Signature,
|
|
808
|
-
opts: { txTimeoutAt?: Date;
|
|
809
|
-
): Promise<
|
|
810
|
-
const checkpointHeader =
|
|
916
|
+
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
917
|
+
): Promise<void> {
|
|
918
|
+
const checkpointHeader = checkpoint.header;
|
|
811
919
|
|
|
812
|
-
const blobFields =
|
|
920
|
+
const blobFields = checkpoint.toBlobFields();
|
|
813
921
|
const blobs = getBlobsPerL1Block(blobFields);
|
|
814
922
|
|
|
815
923
|
const proposeTxArgs = {
|
|
816
924
|
header: checkpointHeader,
|
|
817
|
-
archive:
|
|
818
|
-
body: block.body.toBuffer(),
|
|
925
|
+
archive: checkpoint.archive.root.toBuffer(),
|
|
819
926
|
blobs,
|
|
820
927
|
attestationsAndSigners,
|
|
821
928
|
attestationsAndSignersSignature,
|
|
@@ -829,22 +936,29 @@ export class SequencerPublisher {
|
|
|
829
936
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
830
937
|
// make time consistency checks break.
|
|
831
938
|
// 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.
|
|
939
|
+
ts = await this.validateCheckpointForSubmission(
|
|
940
|
+
checkpoint,
|
|
941
|
+
attestationsAndSigners,
|
|
942
|
+
attestationsAndSignersSignature,
|
|
943
|
+
opts,
|
|
944
|
+
);
|
|
833
945
|
} catch (err: any) {
|
|
834
|
-
this.log.error(`
|
|
835
|
-
...
|
|
836
|
-
slotNumber:
|
|
837
|
-
|
|
946
|
+
this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
947
|
+
...checkpoint.getStats(),
|
|
948
|
+
slotNumber: checkpoint.header.slotNumber,
|
|
949
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
838
950
|
});
|
|
839
951
|
throw err;
|
|
840
952
|
}
|
|
841
953
|
|
|
842
|
-
this.log.verbose(`Enqueuing
|
|
843
|
-
await this.addProposeTx(
|
|
844
|
-
return true;
|
|
954
|
+
this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
|
|
955
|
+
await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
|
|
845
956
|
}
|
|
846
957
|
|
|
847
|
-
public
|
|
958
|
+
public enqueueInvalidateCheckpoint(
|
|
959
|
+
request: InvalidateCheckpointRequest | undefined,
|
|
960
|
+
opts: { txTimeoutAt?: Date } = {},
|
|
961
|
+
) {
|
|
848
962
|
if (!request) {
|
|
849
963
|
return;
|
|
850
964
|
}
|
|
@@ -852,9 +966,9 @@ export class SequencerPublisher {
|
|
|
852
966
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
853
967
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(request.gasUsed) * 64) / 63)));
|
|
854
968
|
|
|
855
|
-
const { gasUsed,
|
|
856
|
-
const logData = { gasUsed,
|
|
857
|
-
this.log.verbose(`Enqueuing invalidate
|
|
969
|
+
const { gasUsed, checkpointNumber } = request;
|
|
970
|
+
const logData = { gasUsed, checkpointNumber, gasLimit, opts };
|
|
971
|
+
this.log.verbose(`Enqueuing invalidate checkpoint request`, logData);
|
|
858
972
|
this.addRequest({
|
|
859
973
|
action: `invalidate-by-${request.reason}`,
|
|
860
974
|
request: request.request,
|
|
@@ -867,9 +981,9 @@ export class SequencerPublisher {
|
|
|
867
981
|
result.receipt.status === 'success' &&
|
|
868
982
|
tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointInvalidated');
|
|
869
983
|
if (!success) {
|
|
870
|
-
this.log.warn(`Invalidate
|
|
984
|
+
this.log.warn(`Invalidate checkpoint ${request.checkpointNumber} failed`, { ...result, ...logData });
|
|
871
985
|
} else {
|
|
872
|
-
this.log.info(`Invalidate
|
|
986
|
+
this.log.info(`Invalidate checkpoint ${request.checkpointNumber} succeeded`, { ...result, ...logData });
|
|
873
987
|
}
|
|
874
988
|
return !!success;
|
|
875
989
|
},
|
|
@@ -895,12 +1009,14 @@ export class SequencerPublisher {
|
|
|
895
1009
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
896
1010
|
|
|
897
1011
|
let gasUsed: bigint;
|
|
1012
|
+
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
898
1013
|
try {
|
|
899
|
-
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [],
|
|
1014
|
+
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi)); // TODO(palla/slash): Check the timestamp logic
|
|
900
1015
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
901
1016
|
} catch (err) {
|
|
902
|
-
const viemError = formatViemError(err);
|
|
1017
|
+
const viemError = formatViemError(err, simulateAbi);
|
|
903
1018
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1019
|
+
|
|
904
1020
|
return false;
|
|
905
1021
|
}
|
|
906
1022
|
|
|
@@ -908,10 +1024,14 @@ export class SequencerPublisher {
|
|
|
908
1024
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(gasUsed) * 64) / 63)));
|
|
909
1025
|
logData.gasLimit = gasLimit;
|
|
910
1026
|
|
|
1027
|
+
// Store the ABI used for simulation on the request so Multicall3.forward can decode errors
|
|
1028
|
+
// when the tx is sent and a revert is diagnosed via simulation.
|
|
1029
|
+
const requestWithAbi = { ...request, abi: simulateAbi };
|
|
1030
|
+
|
|
911
1031
|
this.log.debug(`Enqueuing ${action}`, logData);
|
|
912
1032
|
this.addRequest({
|
|
913
1033
|
action,
|
|
914
|
-
request,
|
|
1034
|
+
request: requestWithAbi,
|
|
915
1035
|
gasConfig: { gasLimit },
|
|
916
1036
|
lastValidL2Slot: slotNumber,
|
|
917
1037
|
checkSuccess: (_req, result) => {
|
|
@@ -948,7 +1068,7 @@ export class SequencerPublisher {
|
|
|
948
1068
|
private async prepareProposeTx(
|
|
949
1069
|
encodedData: L1ProcessArgs,
|
|
950
1070
|
timestamp: bigint,
|
|
951
|
-
options: {
|
|
1071
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
952
1072
|
) {
|
|
953
1073
|
const kzg = Blob.getViemKzgInstance();
|
|
954
1074
|
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
@@ -1029,7 +1149,7 @@ export class SequencerPublisher {
|
|
|
1029
1149
|
`0x${string}`,
|
|
1030
1150
|
],
|
|
1031
1151
|
timestamp: bigint,
|
|
1032
|
-
options: {
|
|
1152
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1033
1153
|
) {
|
|
1034
1154
|
const rollupData = encodeFunctionData({
|
|
1035
1155
|
abi: RollupAbi,
|
|
@@ -1038,13 +1158,9 @@ export class SequencerPublisher {
|
|
|
1038
1158
|
});
|
|
1039
1159
|
|
|
1040
1160
|
// override the pending checkpoint number if requested
|
|
1041
|
-
const optsForcePendingCheckpointNumber =
|
|
1042
|
-
options.forcePendingBlockNumber !== undefined
|
|
1043
|
-
? CheckpointNumber.fromBlockNumber(options.forcePendingBlockNumber)
|
|
1044
|
-
: undefined;
|
|
1045
1161
|
const forcePendingCheckpointNumberStateDiff = (
|
|
1046
|
-
|
|
1047
|
-
? await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
1162
|
+
options.forcePendingCheckpointNumber !== undefined
|
|
1163
|
+
? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber)
|
|
1048
1164
|
: []
|
|
1049
1165
|
).flatMap(override => override.stateDiff ?? []);
|
|
1050
1166
|
|
|
@@ -1071,20 +1187,20 @@ export class SequencerPublisher {
|
|
|
1071
1187
|
{
|
|
1072
1188
|
to: this.rollupContract.address,
|
|
1073
1189
|
data: rollupData,
|
|
1074
|
-
gas:
|
|
1190
|
+
gas: MAX_L1_TX_LIMIT,
|
|
1075
1191
|
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1076
1192
|
},
|
|
1077
1193
|
{
|
|
1078
1194
|
// @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
|
|
1079
1195
|
time: timestamp + 1n,
|
|
1080
1196
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1081
|
-
gasLimit:
|
|
1197
|
+
gasLimit: MAX_L1_TX_LIMIT * 2n,
|
|
1082
1198
|
},
|
|
1083
1199
|
stateOverrides,
|
|
1084
1200
|
RollupAbi,
|
|
1085
1201
|
{
|
|
1086
1202
|
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
1087
|
-
fallbackGasEstimate:
|
|
1203
|
+
fallbackGasEstimate: MAX_L1_TX_LIMIT,
|
|
1088
1204
|
},
|
|
1089
1205
|
)
|
|
1090
1206
|
.catch(err => {
|
|
@@ -1094,7 +1210,7 @@ export class SequencerPublisher {
|
|
|
1094
1210
|
this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
|
|
1095
1211
|
// Return a minimal simulation result with the fallback gas estimate
|
|
1096
1212
|
return {
|
|
1097
|
-
gasUsed:
|
|
1213
|
+
gasUsed: MAX_L1_TX_LIMIT,
|
|
1098
1214
|
logs: [],
|
|
1099
1215
|
};
|
|
1100
1216
|
}
|
|
@@ -1106,11 +1222,12 @@ export class SequencerPublisher {
|
|
|
1106
1222
|
}
|
|
1107
1223
|
|
|
1108
1224
|
private async addProposeTx(
|
|
1109
|
-
|
|
1225
|
+
checkpoint: Checkpoint,
|
|
1110
1226
|
encodedData: L1ProcessArgs,
|
|
1111
|
-
opts: { txTimeoutAt?: Date;
|
|
1227
|
+
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
1112
1228
|
timestamp: bigint,
|
|
1113
1229
|
): Promise<void> {
|
|
1230
|
+
const slot = checkpoint.header.slotNumber;
|
|
1114
1231
|
const timer = new Timer();
|
|
1115
1232
|
const kzg = Blob.getViemKzgInstance();
|
|
1116
1233
|
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
@@ -1125,11 +1242,13 @@ export class SequencerPublisher {
|
|
|
1125
1242
|
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
1243
|
);
|
|
1127
1244
|
|
|
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
|
-
|
|
1245
|
+
// Send the blobs to the blob client preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
|
|
1246
|
+
// tx fails but it does get mined. We make sure that the blobs are sent to the blob client regardless of the tx outcome.
|
|
1247
|
+
void Promise.resolve().then(() =>
|
|
1248
|
+
this.blobClient.sendBlobsToFilestore(encodedData.blobs).catch(_err => {
|
|
1249
|
+
this.log.error('Failed to send blobs to blob client');
|
|
1250
|
+
}),
|
|
1251
|
+
);
|
|
1133
1252
|
|
|
1134
1253
|
return this.addRequest({
|
|
1135
1254
|
action: 'propose',
|
|
@@ -1137,7 +1256,7 @@ export class SequencerPublisher {
|
|
|
1137
1256
|
to: this.rollupContract.address,
|
|
1138
1257
|
data: rollupData,
|
|
1139
1258
|
},
|
|
1140
|
-
lastValidL2Slot:
|
|
1259
|
+
lastValidL2Slot: checkpoint.header.slotNumber,
|
|
1141
1260
|
gasConfig: { ...opts, gasLimit },
|
|
1142
1261
|
blobConfig: {
|
|
1143
1262
|
blobs: encodedData.blobs.map(b => b.data),
|
|
@@ -1152,11 +1271,12 @@ export class SequencerPublisher {
|
|
|
1152
1271
|
receipt &&
|
|
1153
1272
|
receipt.status === 'success' &&
|
|
1154
1273
|
tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointProposed');
|
|
1274
|
+
|
|
1155
1275
|
if (success) {
|
|
1156
1276
|
const endBlock = receipt.blockNumber;
|
|
1157
1277
|
const inclusionBlocks = Number(endBlock - startBlock);
|
|
1158
1278
|
const { calldataGas, calldataSize, sender } = stats!;
|
|
1159
|
-
const publishStats:
|
|
1279
|
+
const publishStats: L1PublishCheckpointStats = {
|
|
1160
1280
|
gasPrice: receipt.effectiveGasPrice,
|
|
1161
1281
|
gasUsed: receipt.gasUsed,
|
|
1162
1282
|
blobGasUsed: receipt.blobGasUsed ?? 0n,
|
|
@@ -1165,23 +1285,26 @@ export class SequencerPublisher {
|
|
|
1165
1285
|
calldataGas,
|
|
1166
1286
|
calldataSize,
|
|
1167
1287
|
sender,
|
|
1168
|
-
...
|
|
1288
|
+
...checkpoint.getStats(),
|
|
1169
1289
|
eventName: 'rollup-published-to-l1',
|
|
1170
1290
|
blobCount: encodedData.blobs.length,
|
|
1171
1291
|
inclusionBlocks,
|
|
1172
1292
|
};
|
|
1173
|
-
this.log.info(`Published
|
|
1293
|
+
this.log.info(`Published checkpoint ${checkpoint.number} at slot ${slot} to rollup contract`, {
|
|
1294
|
+
...stats,
|
|
1295
|
+
...checkpoint.getStats(),
|
|
1296
|
+
...pick(receipt, 'transactionHash', 'blockHash'),
|
|
1297
|
+
});
|
|
1174
1298
|
this.metrics.recordProcessBlockTx(timer.ms(), publishStats);
|
|
1175
1299
|
|
|
1176
1300
|
return true;
|
|
1177
1301
|
} else {
|
|
1178
1302
|
this.metrics.recordFailedTx('process');
|
|
1179
|
-
this.log.error(
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
});
|
|
1303
|
+
this.log.error(
|
|
1304
|
+
`Publishing checkpoint at slot ${slot} failed with ${errorMsg ?? 'no error message'}`,
|
|
1305
|
+
undefined,
|
|
1306
|
+
{ ...checkpoint.getStats(), ...receipt },
|
|
1307
|
+
);
|
|
1185
1308
|
return false;
|
|
1186
1309
|
}
|
|
1187
1310
|
},
|