@aztec/sequencer-client 0.0.1-commit.9b94fc1 → 0.0.1-commit.b468ad8
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 +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 +23 -86
- package/dest/publisher/sequencer-publisher.d.ts +49 -42
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +602 -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 +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 +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 +32 -30
- package/src/client/sequencer-client.ts +31 -42
- 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 +19 -71
- package/src/publisher/sequencer-publisher.ts +300 -161
- 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 +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 +309 -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,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 { SlotNumber } from '@aztec/foundation/branded-types';
|
|
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,14 +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: opts.
|
|
432
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
346
433
|
})
|
|
347
434
|
.catch(err => {
|
|
348
435
|
if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
|
|
@@ -361,7 +448,11 @@ export class SequencerPublisher {
|
|
|
361
448
|
* It will throw if the block header is invalid.
|
|
362
449
|
* @param header - The block header to validate
|
|
363
450
|
*/
|
|
364
|
-
|
|
451
|
+
@trackSpan('SequencerPublisher.validateBlockHeader')
|
|
452
|
+
public async validateBlockHeader(
|
|
453
|
+
header: CheckpointHeader,
|
|
454
|
+
opts?: { forcePendingCheckpointNumber: CheckpointNumber | undefined },
|
|
455
|
+
): Promise<void> {
|
|
365
456
|
const flags = { ignoreDA: true, ignoreSignatures: true };
|
|
366
457
|
|
|
367
458
|
const args = [
|
|
@@ -370,12 +461,14 @@ export class SequencerPublisher {
|
|
|
370
461
|
[], // no signers
|
|
371
462
|
Signature.empty().toViemSignature(),
|
|
372
463
|
`0x${'0'.repeat(64)}`, // 32 empty bytes
|
|
373
|
-
header.
|
|
464
|
+
header.blobsHash.toString(),
|
|
374
465
|
flags,
|
|
375
466
|
] as const;
|
|
376
467
|
|
|
377
468
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
378
|
-
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
469
|
+
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
470
|
+
opts?.forcePendingCheckpointNumber,
|
|
471
|
+
);
|
|
379
472
|
let balance = 0n;
|
|
380
473
|
if (this.config.fishermanMode) {
|
|
381
474
|
// In fisherman mode, we can't know where the proposer is publishing from
|
|
@@ -402,77 +495,95 @@ export class SequencerPublisher {
|
|
|
402
495
|
}
|
|
403
496
|
|
|
404
497
|
/**
|
|
405
|
-
* Simulate making a call to invalidate a
|
|
406
|
-
* @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)
|
|
407
500
|
*/
|
|
408
|
-
public async
|
|
409
|
-
validationResult:
|
|
410
|
-
): Promise<
|
|
501
|
+
public async simulateInvalidateCheckpoint(
|
|
502
|
+
validationResult: ValidateCheckpointResult,
|
|
503
|
+
): Promise<InvalidateCheckpointRequest | undefined> {
|
|
411
504
|
if (validationResult.valid) {
|
|
412
505
|
return undefined;
|
|
413
506
|
}
|
|
414
507
|
|
|
415
|
-
const { reason,
|
|
416
|
-
const
|
|
417
|
-
const logData = { ...
|
|
508
|
+
const { reason, checkpoint } = validationResult;
|
|
509
|
+
const checkpointNumber = checkpoint.checkpointNumber;
|
|
510
|
+
const logData = { ...checkpoint, reason };
|
|
418
511
|
|
|
419
|
-
const
|
|
420
|
-
if (
|
|
512
|
+
const currentCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
513
|
+
if (currentCheckpointNumber < checkpointNumber) {
|
|
421
514
|
this.log.verbose(
|
|
422
|
-
`Skipping
|
|
423
|
-
{
|
|
515
|
+
`Skipping checkpoint ${checkpointNumber} invalidation since it has already been removed from the pending chain`,
|
|
516
|
+
{ currentCheckpointNumber, ...logData },
|
|
424
517
|
);
|
|
425
518
|
return undefined;
|
|
426
519
|
}
|
|
427
520
|
|
|
428
|
-
const request = this.
|
|
429
|
-
this.log.debug(`Simulating invalidate
|
|
521
|
+
const request = this.buildInvalidateCheckpointRequest(validationResult);
|
|
522
|
+
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
|
|
430
523
|
|
|
431
524
|
try {
|
|
432
|
-
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
433
|
-
|
|
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
|
+
});
|
|
434
536
|
|
|
435
|
-
return {
|
|
537
|
+
return {
|
|
538
|
+
request,
|
|
539
|
+
gasUsed,
|
|
540
|
+
checkpointNumber,
|
|
541
|
+
forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
|
|
542
|
+
reason,
|
|
543
|
+
};
|
|
436
544
|
} catch (err) {
|
|
437
545
|
const viemError = formatViemError(err);
|
|
438
546
|
|
|
439
|
-
// If the error is due to the
|
|
440
|
-
// we can safely ignore it and return undefined so we go ahead with
|
|
441
|
-
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')) {
|
|
442
550
|
this.log.verbose(
|
|
443
|
-
`Simulation for invalidate
|
|
551
|
+
`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
|
|
444
552
|
{ ...logData, request, error: viemError.message },
|
|
445
553
|
);
|
|
446
|
-
const
|
|
447
|
-
if (
|
|
448
|
-
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 });
|
|
449
557
|
return undefined;
|
|
450
558
|
} else {
|
|
451
559
|
this.log.error(
|
|
452
|
-
`Simulation for invalidate ${
|
|
560
|
+
`Simulation for invalidate checkpoint ${checkpointNumber} failed and it is still in pending chain`,
|
|
453
561
|
viemError,
|
|
454
562
|
logData,
|
|
455
563
|
);
|
|
456
|
-
throw new Error(
|
|
457
|
-
|
|
458
|
-
|
|
564
|
+
throw new Error(
|
|
565
|
+
`Failed to simulate invalidate checkpoint ${checkpointNumber} while it is still in pending chain`,
|
|
566
|
+
{
|
|
567
|
+
cause: viemError,
|
|
568
|
+
},
|
|
569
|
+
);
|
|
459
570
|
}
|
|
460
571
|
}
|
|
461
572
|
|
|
462
|
-
// Otherwise, throw. We cannot build the next
|
|
463
|
-
this.log.error(`Simulation for invalidate
|
|
464
|
-
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 });
|
|
465
576
|
}
|
|
466
577
|
}
|
|
467
578
|
|
|
468
|
-
private
|
|
579
|
+
private buildInvalidateCheckpointRequest(validationResult: ValidateCheckpointResult) {
|
|
469
580
|
if (validationResult.valid) {
|
|
470
|
-
throw new Error('Cannot invalidate a valid
|
|
581
|
+
throw new Error('Cannot invalidate a valid checkpoint');
|
|
471
582
|
}
|
|
472
583
|
|
|
473
|
-
const {
|
|
474
|
-
const logData = { ...
|
|
475
|
-
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);
|
|
476
587
|
|
|
477
588
|
const attestationsAndSigners = new CommitteeAttestationsAndSigners(
|
|
478
589
|
validationResult.attestations,
|
|
@@ -480,14 +591,14 @@ export class SequencerPublisher {
|
|
|
480
591
|
|
|
481
592
|
if (reason === 'invalid-attestation') {
|
|
482
593
|
return this.rollupContract.buildInvalidateBadAttestationRequest(
|
|
483
|
-
|
|
594
|
+
checkpoint.checkpointNumber,
|
|
484
595
|
attestationsAndSigners,
|
|
485
596
|
committee,
|
|
486
597
|
validationResult.invalidIndex,
|
|
487
598
|
);
|
|
488
599
|
} else if (reason === 'insufficient-attestations') {
|
|
489
600
|
return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
|
|
490
|
-
|
|
601
|
+
checkpoint.checkpointNumber,
|
|
491
602
|
attestationsAndSigners,
|
|
492
603
|
committee,
|
|
493
604
|
);
|
|
@@ -497,45 +608,39 @@ export class SequencerPublisher {
|
|
|
497
608
|
}
|
|
498
609
|
}
|
|
499
610
|
|
|
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,
|
|
611
|
+
/** Simulates `propose` to make sure that the checkpoint is valid for submission */
|
|
612
|
+
@trackSpan('SequencerPublisher.validateCheckpointForSubmission')
|
|
613
|
+
public async validateCheckpointForSubmission(
|
|
614
|
+
checkpoint: Checkpoint,
|
|
511
615
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
512
616
|
attestationsAndSignersSignature: Signature,
|
|
513
|
-
options: {
|
|
617
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
514
618
|
): Promise<bigint> {
|
|
515
619
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
516
620
|
|
|
621
|
+
// TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
|
|
517
622
|
// If we have no attestations, we still need to provide the empty attestations
|
|
518
623
|
// 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 =
|
|
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();
|
|
532
637
|
const blobs = getBlobsPerL1Block(blobFields);
|
|
533
638
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
534
639
|
|
|
535
640
|
const args = [
|
|
536
641
|
{
|
|
537
|
-
header:
|
|
538
|
-
archive: toHex(
|
|
642
|
+
header: checkpoint.header.toViem(),
|
|
643
|
+
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
539
644
|
oracleInput: {
|
|
540
645
|
feeAssetPriceModifier: 0n,
|
|
541
646
|
},
|
|
@@ -573,10 +678,19 @@ export class SequencerPublisher {
|
|
|
573
678
|
const round = await base.computeRound(slotNumber);
|
|
574
679
|
const roundInfo = await base.getRoundInfo(this.rollupContract.address, round);
|
|
575
680
|
|
|
681
|
+
if (roundInfo.quorumReached) {
|
|
682
|
+
return false;
|
|
683
|
+
}
|
|
684
|
+
|
|
576
685
|
if (roundInfo.lastSignalSlot >= slotNumber) {
|
|
577
686
|
return false;
|
|
578
687
|
}
|
|
579
688
|
|
|
689
|
+
if (await this.isPayloadEmpty(payload)) {
|
|
690
|
+
this.log.warn(`Skipping vote cast for payload with empty code`);
|
|
691
|
+
return false;
|
|
692
|
+
}
|
|
693
|
+
|
|
580
694
|
const cachedLastVote = this.lastActions[signalType];
|
|
581
695
|
this.lastActions[signalType] = slotNumber;
|
|
582
696
|
const action = signalType;
|
|
@@ -596,7 +710,7 @@ export class SequencerPublisher {
|
|
|
596
710
|
});
|
|
597
711
|
|
|
598
712
|
try {
|
|
599
|
-
await this.l1TxUtils.simulate(request, { time: timestamp }, [], ErrorsAbi);
|
|
713
|
+
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
600
714
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
601
715
|
} catch (err) {
|
|
602
716
|
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, err);
|
|
@@ -619,14 +733,14 @@ export class SequencerPublisher {
|
|
|
619
733
|
const logData = { ...result, slotNumber, round, payload: payload.toString() };
|
|
620
734
|
if (!success) {
|
|
621
735
|
this.log.error(
|
|
622
|
-
`Signaling in
|
|
736
|
+
`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} failed`,
|
|
623
737
|
logData,
|
|
624
738
|
);
|
|
625
739
|
this.lastActions[signalType] = cachedLastVote;
|
|
626
740
|
return false;
|
|
627
741
|
} else {
|
|
628
742
|
this.log.info(
|
|
629
|
-
`Signaling in
|
|
743
|
+
`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} succeeded`,
|
|
630
744
|
logData,
|
|
631
745
|
);
|
|
632
746
|
return true;
|
|
@@ -636,6 +750,17 @@ export class SequencerPublisher {
|
|
|
636
750
|
return true;
|
|
637
751
|
}
|
|
638
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
|
+
|
|
639
764
|
/**
|
|
640
765
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
641
766
|
* @param slotNumber - The slot number to cast a signal for.
|
|
@@ -783,27 +908,21 @@ export class SequencerPublisher {
|
|
|
783
908
|
return true;
|
|
784
909
|
}
|
|
785
910
|
|
|
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,
|
|
911
|
+
/** Simulates and enqueues a proposal for a checkpoint on L1 */
|
|
912
|
+
public async enqueueProposeCheckpoint(
|
|
913
|
+
checkpoint: Checkpoint,
|
|
794
914
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
795
915
|
attestationsAndSignersSignature: Signature,
|
|
796
|
-
opts: { txTimeoutAt?: Date;
|
|
797
|
-
): Promise<
|
|
798
|
-
const checkpointHeader =
|
|
916
|
+
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
917
|
+
): Promise<void> {
|
|
918
|
+
const checkpointHeader = checkpoint.header;
|
|
799
919
|
|
|
800
|
-
const blobFields =
|
|
920
|
+
const blobFields = checkpoint.toBlobFields();
|
|
801
921
|
const blobs = getBlobsPerL1Block(blobFields);
|
|
802
922
|
|
|
803
923
|
const proposeTxArgs = {
|
|
804
924
|
header: checkpointHeader,
|
|
805
|
-
archive:
|
|
806
|
-
body: block.body.toBuffer(),
|
|
925
|
+
archive: checkpoint.archive.root.toBuffer(),
|
|
807
926
|
blobs,
|
|
808
927
|
attestationsAndSigners,
|
|
809
928
|
attestationsAndSignersSignature,
|
|
@@ -817,22 +936,29 @@ export class SequencerPublisher {
|
|
|
817
936
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
818
937
|
// make time consistency checks break.
|
|
819
938
|
// 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.
|
|
939
|
+
ts = await this.validateCheckpointForSubmission(
|
|
940
|
+
checkpoint,
|
|
941
|
+
attestationsAndSigners,
|
|
942
|
+
attestationsAndSignersSignature,
|
|
943
|
+
opts,
|
|
944
|
+
);
|
|
821
945
|
} catch (err: any) {
|
|
822
|
-
this.log.error(`
|
|
823
|
-
...
|
|
824
|
-
slotNumber:
|
|
825
|
-
|
|
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,
|
|
826
950
|
});
|
|
827
951
|
throw err;
|
|
828
952
|
}
|
|
829
953
|
|
|
830
|
-
this.log.verbose(`Enqueuing
|
|
831
|
-
await this.addProposeTx(
|
|
832
|
-
return true;
|
|
954
|
+
this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
|
|
955
|
+
await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
|
|
833
956
|
}
|
|
834
957
|
|
|
835
|
-
public
|
|
958
|
+
public enqueueInvalidateCheckpoint(
|
|
959
|
+
request: InvalidateCheckpointRequest | undefined,
|
|
960
|
+
opts: { txTimeoutAt?: Date } = {},
|
|
961
|
+
) {
|
|
836
962
|
if (!request) {
|
|
837
963
|
return;
|
|
838
964
|
}
|
|
@@ -840,9 +966,9 @@ export class SequencerPublisher {
|
|
|
840
966
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
841
967
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(request.gasUsed) * 64) / 63)));
|
|
842
968
|
|
|
843
|
-
const { gasUsed,
|
|
844
|
-
const logData = { gasUsed,
|
|
845
|
-
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);
|
|
846
972
|
this.addRequest({
|
|
847
973
|
action: `invalidate-by-${request.reason}`,
|
|
848
974
|
request: request.request,
|
|
@@ -855,9 +981,9 @@ export class SequencerPublisher {
|
|
|
855
981
|
result.receipt.status === 'success' &&
|
|
856
982
|
tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointInvalidated');
|
|
857
983
|
if (!success) {
|
|
858
|
-
this.log.warn(`Invalidate
|
|
984
|
+
this.log.warn(`Invalidate checkpoint ${request.checkpointNumber} failed`, { ...result, ...logData });
|
|
859
985
|
} else {
|
|
860
|
-
this.log.info(`Invalidate
|
|
986
|
+
this.log.info(`Invalidate checkpoint ${request.checkpointNumber} succeeded`, { ...result, ...logData });
|
|
861
987
|
}
|
|
862
988
|
return !!success;
|
|
863
989
|
},
|
|
@@ -883,12 +1009,14 @@ export class SequencerPublisher {
|
|
|
883
1009
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
884
1010
|
|
|
885
1011
|
let gasUsed: bigint;
|
|
1012
|
+
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
886
1013
|
try {
|
|
887
|
-
({ 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
|
|
888
1015
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
889
1016
|
} catch (err) {
|
|
890
|
-
const viemError = formatViemError(err);
|
|
1017
|
+
const viemError = formatViemError(err, simulateAbi);
|
|
891
1018
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1019
|
+
|
|
892
1020
|
return false;
|
|
893
1021
|
}
|
|
894
1022
|
|
|
@@ -896,10 +1024,14 @@ export class SequencerPublisher {
|
|
|
896
1024
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(gasUsed) * 64) / 63)));
|
|
897
1025
|
logData.gasLimit = gasLimit;
|
|
898
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
|
+
|
|
899
1031
|
this.log.debug(`Enqueuing ${action}`, logData);
|
|
900
1032
|
this.addRequest({
|
|
901
1033
|
action,
|
|
902
|
-
request,
|
|
1034
|
+
request: requestWithAbi,
|
|
903
1035
|
gasConfig: { gasLimit },
|
|
904
1036
|
lastValidL2Slot: slotNumber,
|
|
905
1037
|
checkSuccess: (_req, result) => {
|
|
@@ -936,7 +1068,7 @@ export class SequencerPublisher {
|
|
|
936
1068
|
private async prepareProposeTx(
|
|
937
1069
|
encodedData: L1ProcessArgs,
|
|
938
1070
|
timestamp: bigint,
|
|
939
|
-
options: {
|
|
1071
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
940
1072
|
) {
|
|
941
1073
|
const kzg = Blob.getViemKzgInstance();
|
|
942
1074
|
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
@@ -1017,7 +1149,7 @@ export class SequencerPublisher {
|
|
|
1017
1149
|
`0x${string}`,
|
|
1018
1150
|
],
|
|
1019
1151
|
timestamp: bigint,
|
|
1020
|
-
options: {
|
|
1152
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1021
1153
|
) {
|
|
1022
1154
|
const rollupData = encodeFunctionData({
|
|
1023
1155
|
abi: RollupAbi,
|
|
@@ -1025,10 +1157,10 @@ export class SequencerPublisher {
|
|
|
1025
1157
|
args,
|
|
1026
1158
|
});
|
|
1027
1159
|
|
|
1028
|
-
// override the pending
|
|
1029
|
-
const
|
|
1030
|
-
options.
|
|
1031
|
-
? await this.rollupContract.makePendingCheckpointNumberOverride(options.
|
|
1160
|
+
// override the pending checkpoint number if requested
|
|
1161
|
+
const forcePendingCheckpointNumberStateDiff = (
|
|
1162
|
+
options.forcePendingCheckpointNumber !== undefined
|
|
1163
|
+
? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber)
|
|
1032
1164
|
: []
|
|
1033
1165
|
).flatMap(override => override.stateDiff ?? []);
|
|
1034
1166
|
|
|
@@ -1038,7 +1170,7 @@ export class SequencerPublisher {
|
|
|
1038
1170
|
// @note we override checkBlob to false since blobs are not part simulate()
|
|
1039
1171
|
stateDiff: [
|
|
1040
1172
|
{ slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
|
|
1041
|
-
...
|
|
1173
|
+
...forcePendingCheckpointNumberStateDiff,
|
|
1042
1174
|
],
|
|
1043
1175
|
},
|
|
1044
1176
|
];
|
|
@@ -1055,20 +1187,20 @@ export class SequencerPublisher {
|
|
|
1055
1187
|
{
|
|
1056
1188
|
to: this.rollupContract.address,
|
|
1057
1189
|
data: rollupData,
|
|
1058
|
-
gas:
|
|
1190
|
+
gas: MAX_L1_TX_LIMIT,
|
|
1059
1191
|
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1060
1192
|
},
|
|
1061
1193
|
{
|
|
1062
1194
|
// @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
|
|
1063
1195
|
time: timestamp + 1n,
|
|
1064
1196
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1065
|
-
gasLimit:
|
|
1197
|
+
gasLimit: MAX_L1_TX_LIMIT * 2n,
|
|
1066
1198
|
},
|
|
1067
1199
|
stateOverrides,
|
|
1068
1200
|
RollupAbi,
|
|
1069
1201
|
{
|
|
1070
1202
|
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
1071
|
-
fallbackGasEstimate:
|
|
1203
|
+
fallbackGasEstimate: MAX_L1_TX_LIMIT,
|
|
1072
1204
|
},
|
|
1073
1205
|
)
|
|
1074
1206
|
.catch(err => {
|
|
@@ -1078,7 +1210,7 @@ export class SequencerPublisher {
|
|
|
1078
1210
|
this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
|
|
1079
1211
|
// Return a minimal simulation result with the fallback gas estimate
|
|
1080
1212
|
return {
|
|
1081
|
-
gasUsed:
|
|
1213
|
+
gasUsed: MAX_L1_TX_LIMIT,
|
|
1082
1214
|
logs: [],
|
|
1083
1215
|
};
|
|
1084
1216
|
}
|
|
@@ -1090,11 +1222,12 @@ export class SequencerPublisher {
|
|
|
1090
1222
|
}
|
|
1091
1223
|
|
|
1092
1224
|
private async addProposeTx(
|
|
1093
|
-
|
|
1225
|
+
checkpoint: Checkpoint,
|
|
1094
1226
|
encodedData: L1ProcessArgs,
|
|
1095
|
-
opts: { txTimeoutAt?: Date;
|
|
1227
|
+
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
1096
1228
|
timestamp: bigint,
|
|
1097
1229
|
): Promise<void> {
|
|
1230
|
+
const slot = checkpoint.header.slotNumber;
|
|
1098
1231
|
const timer = new Timer();
|
|
1099
1232
|
const kzg = Blob.getViemKzgInstance();
|
|
1100
1233
|
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
@@ -1109,11 +1242,13 @@ export class SequencerPublisher {
|
|
|
1109
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
|
|
1110
1243
|
);
|
|
1111
1244
|
|
|
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
|
-
|
|
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
|
+
);
|
|
1117
1252
|
|
|
1118
1253
|
return this.addRequest({
|
|
1119
1254
|
action: 'propose',
|
|
@@ -1121,7 +1256,7 @@ export class SequencerPublisher {
|
|
|
1121
1256
|
to: this.rollupContract.address,
|
|
1122
1257
|
data: rollupData,
|
|
1123
1258
|
},
|
|
1124
|
-
lastValidL2Slot:
|
|
1259
|
+
lastValidL2Slot: checkpoint.header.slotNumber,
|
|
1125
1260
|
gasConfig: { ...opts, gasLimit },
|
|
1126
1261
|
blobConfig: {
|
|
1127
1262
|
blobs: encodedData.blobs.map(b => b.data),
|
|
@@ -1136,11 +1271,12 @@ export class SequencerPublisher {
|
|
|
1136
1271
|
receipt &&
|
|
1137
1272
|
receipt.status === 'success' &&
|
|
1138
1273
|
tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointProposed');
|
|
1274
|
+
|
|
1139
1275
|
if (success) {
|
|
1140
1276
|
const endBlock = receipt.blockNumber;
|
|
1141
1277
|
const inclusionBlocks = Number(endBlock - startBlock);
|
|
1142
1278
|
const { calldataGas, calldataSize, sender } = stats!;
|
|
1143
|
-
const publishStats:
|
|
1279
|
+
const publishStats: L1PublishCheckpointStats = {
|
|
1144
1280
|
gasPrice: receipt.effectiveGasPrice,
|
|
1145
1281
|
gasUsed: receipt.gasUsed,
|
|
1146
1282
|
blobGasUsed: receipt.blobGasUsed ?? 0n,
|
|
@@ -1149,23 +1285,26 @@ export class SequencerPublisher {
|
|
|
1149
1285
|
calldataGas,
|
|
1150
1286
|
calldataSize,
|
|
1151
1287
|
sender,
|
|
1152
|
-
...
|
|
1288
|
+
...checkpoint.getStats(),
|
|
1153
1289
|
eventName: 'rollup-published-to-l1',
|
|
1154
1290
|
blobCount: encodedData.blobs.length,
|
|
1155
1291
|
inclusionBlocks,
|
|
1156
1292
|
};
|
|
1157
|
-
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
|
+
});
|
|
1158
1298
|
this.metrics.recordProcessBlockTx(timer.ms(), publishStats);
|
|
1159
1299
|
|
|
1160
1300
|
return true;
|
|
1161
1301
|
} else {
|
|
1162
1302
|
this.metrics.recordFailedTx('process');
|
|
1163
|
-
this.log.error(
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
});
|
|
1303
|
+
this.log.error(
|
|
1304
|
+
`Publishing checkpoint at slot ${slot} failed with ${errorMsg ?? 'no error message'}`,
|
|
1305
|
+
undefined,
|
|
1306
|
+
{ ...checkpoint.getStats(), ...receipt },
|
|
1307
|
+
);
|
|
1169
1308
|
return false;
|
|
1170
1309
|
}
|
|
1171
1310
|
},
|