@aztec/sequencer-client 0.0.1-commit.9b94fc1 → 0.0.1-commit.c7c42ec
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 +10 -9
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +32 -25
- package/dest/config.d.ts +12 -5
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +68 -30
- package/dest/global_variable_builder/global_builder.d.ts +19 -10
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +39 -29
- package/dest/index.d.ts +2 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -1
- package/dest/publisher/config.d.ts +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.d.ts +40 -33
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +133 -74
- package/dest/sequencer/block_builder.d.ts +4 -5
- package/dest/sequencer/block_builder.d.ts.map +1 -1
- package/dest/sequencer/block_builder.js +8 -13
- package/dest/sequencer/checkpoint_builder.d.ts +63 -0
- package/dest/sequencer/checkpoint_builder.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_builder.js +131 -0
- package/dest/sequencer/checkpoint_proposal_job.d.ts +74 -0
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_proposal_job.js +642 -0
- package/dest/sequencer/checkpoint_voter.d.ts +34 -0
- package/dest/sequencer/checkpoint_voter.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_voter.js +85 -0
- package/dest/sequencer/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 +5 -1
- package/dest/sequencer/index.d.ts.map +1 -1
- package/dest/sequencer/index.js +4 -0
- package/dest/sequencer/metrics.d.ts +22 -2
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +154 -0
- package/dest/sequencer/sequencer.d.ts +93 -127
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +223 -578
- 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 -2
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts +83 -0
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -0
- package/dest/test/mock_checkpoint_builder.js +179 -0
- package/dest/test/utils.d.ts +49 -0
- package/dest/test/utils.d.ts.map +1 -0
- package/dest/test/utils.js +94 -0
- package/dest/tx_validator/tx_validator_factory.d.ts +3 -2
- package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
- package/dest/tx_validator/tx_validator_factory.js +1 -1
- package/package.json +29 -29
- package/src/client/sequencer-client.ts +28 -38
- package/src/config.ts +73 -34
- package/src/global_variable_builder/global_builder.ts +54 -50
- package/src/index.ts +2 -0
- package/src/publisher/config.ts +12 -9
- package/src/publisher/sequencer-publisher-factory.ts +5 -4
- package/src/publisher/sequencer-publisher-metrics.ts +2 -2
- package/src/publisher/sequencer-publisher.ts +214 -102
- package/src/sequencer/README.md +531 -0
- package/src/sequencer/block_builder.ts +11 -16
- package/src/sequencer/checkpoint_builder.ts +217 -0
- package/src/sequencer/checkpoint_proposal_job.ts +706 -0
- package/src/sequencer/checkpoint_voter.ts +105 -0
- package/src/sequencer/config.ts +2 -1
- package/src/sequencer/events.ts +27 -0
- package/src/sequencer/index.ts +4 -0
- package/src/sequencer/metrics.ts +203 -1
- package/src/sequencer/sequencer.ts +330 -788
- 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 -1
- package/src/test/mock_checkpoint_builder.ts +247 -0
- package/src/test/utils.ts +137 -0
- package/src/tx_validator/tx_validator_factory.ts +3 -2
|
@@ -1,43 +1,45 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { BlobClientInterface } from '@aztec/blob-client/client';
|
|
2
2
|
import { Blob, getBlobsPerL1Block, getPrefixedEthBlobCommitments } from '@aztec/blob-lib';
|
|
3
|
-
import { type BlobSinkClientInterface, createBlobSinkClient } from '@aztec/blob-sink/client';
|
|
4
3
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
4
|
+
import type { L1ContractsConfig } from '@aztec/ethereum/config';
|
|
5
5
|
import {
|
|
6
6
|
type EmpireSlashingProposerContract,
|
|
7
|
-
FormattedViemError,
|
|
8
7
|
type GovernanceProposerContract,
|
|
9
8
|
type IEmpireBase,
|
|
10
|
-
type L1BlobInputs,
|
|
11
|
-
type L1ContractsConfig,
|
|
12
|
-
type L1TxConfig,
|
|
13
|
-
type L1TxRequest,
|
|
14
9
|
MULTI_CALL_3_ADDRESS,
|
|
15
10
|
Multicall3,
|
|
16
11
|
RollupContract,
|
|
17
12
|
type TallySlashingProposerContract,
|
|
18
|
-
type TransactionStats,
|
|
19
13
|
type ViemCommitteeAttestations,
|
|
20
14
|
type ViemHeader,
|
|
15
|
+
} from '@aztec/ethereum/contracts';
|
|
16
|
+
import { type L1FeeAnalysisResult, L1FeeAnalyzer } from '@aztec/ethereum/l1-fee-analysis';
|
|
17
|
+
import {
|
|
18
|
+
type L1BlobInputs,
|
|
19
|
+
type L1TxConfig,
|
|
20
|
+
type L1TxRequest,
|
|
21
|
+
type TransactionStats,
|
|
21
22
|
WEI_CONST,
|
|
22
|
-
|
|
23
|
-
tryExtractEvent,
|
|
24
|
-
} from '@aztec/ethereum';
|
|
23
|
+
} from '@aztec/ethereum/l1-tx-utils';
|
|
25
24
|
import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs';
|
|
25
|
+
import { FormattedViemError, formatViemError, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
26
26
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
27
27
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
28
|
-
import { SlotNumber } from '@aztec/foundation/branded-types';
|
|
28
|
+
import { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
29
|
+
import { pick } from '@aztec/foundation/collection';
|
|
30
|
+
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
29
31
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
30
32
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
31
|
-
import type { Fr } from '@aztec/foundation/fields';
|
|
32
33
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
33
34
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
34
35
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
35
36
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
36
37
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
37
|
-
import {
|
|
38
|
+
import { CommitteeAttestationsAndSigners, type ValidateBlockResult } from '@aztec/stdlib/block';
|
|
39
|
+
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
38
40
|
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
39
41
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
40
|
-
import type {
|
|
42
|
+
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
41
43
|
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
42
44
|
|
|
43
45
|
import { type StateOverride, type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
|
|
@@ -82,8 +84,8 @@ export type InvalidateBlockRequest = {
|
|
|
82
84
|
request: L1TxRequest;
|
|
83
85
|
reason: 'invalid-attestation' | 'insufficient-attestations';
|
|
84
86
|
gasUsed: bigint;
|
|
85
|
-
blockNumber:
|
|
86
|
-
forcePendingBlockNumber:
|
|
87
|
+
blockNumber: BlockNumber;
|
|
88
|
+
forcePendingBlockNumber: BlockNumber;
|
|
87
89
|
};
|
|
88
90
|
|
|
89
91
|
interface RequestWithExpiry {
|
|
@@ -108,13 +110,18 @@ export class SequencerPublisher {
|
|
|
108
110
|
|
|
109
111
|
protected lastActions: Partial<Record<Action, SlotNumber>> = {};
|
|
110
112
|
|
|
113
|
+
private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
|
|
114
|
+
|
|
111
115
|
protected log: Logger;
|
|
112
116
|
protected ethereumSlotDuration: bigint;
|
|
113
117
|
|
|
114
|
-
private
|
|
118
|
+
private blobClient: BlobClientInterface;
|
|
115
119
|
|
|
116
120
|
/** Address to use for simulations in fisherman mode (actual proposer's address) */
|
|
117
121
|
private proposerAddressForSimulation?: EthAddress;
|
|
122
|
+
|
|
123
|
+
/** L1 fee analyzer for fisherman mode */
|
|
124
|
+
private l1FeeAnalyzer?: L1FeeAnalyzer;
|
|
118
125
|
// @note - with blobs, the below estimate seems too large.
|
|
119
126
|
// Total used for full block from int_l1_pub e2e test: 1m (of which 86k is 1x blob)
|
|
120
127
|
// Total used for emptier block from above test: 429k (of which 84k is 1x blob)
|
|
@@ -138,7 +145,7 @@ export class SequencerPublisher {
|
|
|
138
145
|
private config: TxSenderConfig & PublisherConfig & Pick<L1ContractsConfig, 'ethereumSlotDuration'>,
|
|
139
146
|
deps: {
|
|
140
147
|
telemetry?: TelemetryClient;
|
|
141
|
-
|
|
148
|
+
blobClient: BlobClientInterface;
|
|
142
149
|
l1TxUtils: L1TxUtilsWithBlobs;
|
|
143
150
|
rollupContract: RollupContract;
|
|
144
151
|
slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
@@ -156,8 +163,7 @@ export class SequencerPublisher {
|
|
|
156
163
|
this.epochCache = deps.epochCache;
|
|
157
164
|
this.lastActions = deps.lastActions;
|
|
158
165
|
|
|
159
|
-
this.
|
|
160
|
-
deps.blobSinkClient ?? createBlobSinkClient(config, { logger: createLogger('sequencer:blob-sink:client') });
|
|
166
|
+
this.blobClient = deps.blobClient;
|
|
161
167
|
|
|
162
168
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
163
169
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
@@ -174,6 +180,15 @@ export class SequencerPublisher {
|
|
|
174
180
|
this.slashingProposerContract = newSlashingProposer;
|
|
175
181
|
});
|
|
176
182
|
this.slashFactoryContract = deps.slashFactoryContract;
|
|
183
|
+
|
|
184
|
+
// Initialize L1 fee analyzer for fisherman mode
|
|
185
|
+
if (config.fishermanMode) {
|
|
186
|
+
this.l1FeeAnalyzer = new L1FeeAnalyzer(
|
|
187
|
+
this.l1TxUtils.client,
|
|
188
|
+
deps.dateProvider,
|
|
189
|
+
createLogger('sequencer:publisher:fee-analyzer'),
|
|
190
|
+
);
|
|
191
|
+
}
|
|
177
192
|
}
|
|
178
193
|
|
|
179
194
|
public getRollupContract(): RollupContract {
|
|
@@ -184,6 +199,13 @@ export class SequencerPublisher {
|
|
|
184
199
|
return this.l1TxUtils.getSenderAddress();
|
|
185
200
|
}
|
|
186
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Gets the L1 fee analyzer instance (only available in fisherman mode)
|
|
204
|
+
*/
|
|
205
|
+
public getL1FeeAnalyzer(): L1FeeAnalyzer | undefined {
|
|
206
|
+
return this.l1FeeAnalyzer;
|
|
207
|
+
}
|
|
208
|
+
|
|
187
209
|
/**
|
|
188
210
|
* Sets the proposer address to use for simulations in fisherman mode.
|
|
189
211
|
* @param proposerAddress - The actual proposer's address to use for balance lookups in simulations
|
|
@@ -211,6 +233,62 @@ export class SequencerPublisher {
|
|
|
211
233
|
}
|
|
212
234
|
}
|
|
213
235
|
|
|
236
|
+
/**
|
|
237
|
+
* Analyzes L1 fees for the pending requests without sending them.
|
|
238
|
+
* This is used in fisherman mode to validate fee calculations.
|
|
239
|
+
* @param l2SlotNumber - The L2 slot number for this analysis
|
|
240
|
+
* @param onComplete - Optional callback to invoke when analysis completes (after block is mined)
|
|
241
|
+
* @returns The analysis result (incomplete until block mines), or undefined if no requests
|
|
242
|
+
*/
|
|
243
|
+
public async analyzeL1Fees(
|
|
244
|
+
l2SlotNumber: SlotNumber,
|
|
245
|
+
onComplete?: (analysis: L1FeeAnalysisResult) => void,
|
|
246
|
+
): Promise<L1FeeAnalysisResult | undefined> {
|
|
247
|
+
if (!this.l1FeeAnalyzer) {
|
|
248
|
+
this.log.warn('L1 fee analyzer not available (not in fisherman mode)');
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const requestsToAnalyze = [...this.requests];
|
|
253
|
+
if (requestsToAnalyze.length === 0) {
|
|
254
|
+
this.log.debug('No requests to analyze for L1 fees');
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Extract blob config from requests (if any)
|
|
259
|
+
const blobConfigs = requestsToAnalyze.filter(request => request.blobConfig).map(request => request.blobConfig);
|
|
260
|
+
const blobConfig = blobConfigs[0];
|
|
261
|
+
|
|
262
|
+
// Get gas configs
|
|
263
|
+
const gasConfigs = requestsToAnalyze.filter(request => request.gasConfig).map(request => request.gasConfig);
|
|
264
|
+
const gasLimits = gasConfigs.map(g => g?.gasLimit).filter((g): g is bigint => g !== undefined);
|
|
265
|
+
const gasLimit = gasLimits.length > 0 ? gasLimits.reduce((sum, g) => sum + g, 0n) : 0n;
|
|
266
|
+
|
|
267
|
+
// Get the transaction requests
|
|
268
|
+
const l1Requests = requestsToAnalyze.map(r => r.request);
|
|
269
|
+
|
|
270
|
+
// Start the analysis
|
|
271
|
+
const analysisId = await this.l1FeeAnalyzer.startAnalysis(
|
|
272
|
+
l2SlotNumber,
|
|
273
|
+
gasLimit > 0n ? gasLimit : SequencerPublisher.PROPOSE_GAS_GUESS,
|
|
274
|
+
l1Requests,
|
|
275
|
+
blobConfig,
|
|
276
|
+
onComplete,
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
this.log.info('Started L1 fee analysis', {
|
|
280
|
+
analysisId,
|
|
281
|
+
l2SlotNumber: l2SlotNumber.toString(),
|
|
282
|
+
requestCount: requestsToAnalyze.length,
|
|
283
|
+
hasBlobConfig: !!blobConfig,
|
|
284
|
+
gasLimit: gasLimit.toString(),
|
|
285
|
+
actions: requestsToAnalyze.map(r => r.action),
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// Return the analysis result (will be incomplete until block mines)
|
|
289
|
+
return this.l1FeeAnalyzer.getAnalysis(analysisId);
|
|
290
|
+
}
|
|
291
|
+
|
|
214
292
|
/**
|
|
215
293
|
* Sends all requests that are still valid.
|
|
216
294
|
* @returns one of:
|
|
@@ -221,7 +299,7 @@ export class SequencerPublisher {
|
|
|
221
299
|
public async sendRequests() {
|
|
222
300
|
const requestsToProcess = [...this.requests];
|
|
223
301
|
this.requests = [];
|
|
224
|
-
if (this.interrupted) {
|
|
302
|
+
if (this.interrupted || requestsToProcess.length === 0) {
|
|
225
303
|
return undefined;
|
|
226
304
|
}
|
|
227
305
|
const currentL2Slot = this.getCurrentL2Slot();
|
|
@@ -335,14 +413,17 @@ export class SequencerPublisher {
|
|
|
335
413
|
public canProposeAtNextEthBlock(
|
|
336
414
|
tipArchive: Fr,
|
|
337
415
|
msgSender: EthAddress,
|
|
338
|
-
opts: { forcePendingBlockNumber?:
|
|
416
|
+
opts: { forcePendingBlockNumber?: BlockNumber } = {},
|
|
339
417
|
) {
|
|
340
418
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
341
419
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
342
420
|
|
|
343
421
|
return this.rollupContract
|
|
344
422
|
.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
|
|
345
|
-
forcePendingCheckpointNumber:
|
|
423
|
+
forcePendingCheckpointNumber:
|
|
424
|
+
opts.forcePendingBlockNumber !== undefined
|
|
425
|
+
? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber)
|
|
426
|
+
: undefined,
|
|
346
427
|
})
|
|
347
428
|
.catch(err => {
|
|
348
429
|
if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
|
|
@@ -361,7 +442,10 @@ export class SequencerPublisher {
|
|
|
361
442
|
* It will throw if the block header is invalid.
|
|
362
443
|
* @param header - The block header to validate
|
|
363
444
|
*/
|
|
364
|
-
public async validateBlockHeader(
|
|
445
|
+
public async validateBlockHeader(
|
|
446
|
+
header: CheckpointHeader,
|
|
447
|
+
opts?: { forcePendingBlockNumber: BlockNumber | undefined },
|
|
448
|
+
) {
|
|
365
449
|
const flags = { ignoreDA: true, ignoreSignatures: true };
|
|
366
450
|
|
|
367
451
|
const args = [
|
|
@@ -375,7 +459,13 @@ export class SequencerPublisher {
|
|
|
375
459
|
] as const;
|
|
376
460
|
|
|
377
461
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
378
|
-
const
|
|
462
|
+
const optsForcePendingCheckpointNumber =
|
|
463
|
+
opts?.forcePendingBlockNumber !== undefined
|
|
464
|
+
? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber)
|
|
465
|
+
: undefined;
|
|
466
|
+
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
467
|
+
optsForcePendingCheckpointNumber,
|
|
468
|
+
);
|
|
379
469
|
let balance = 0n;
|
|
380
470
|
if (this.config.fishermanMode) {
|
|
381
471
|
// In fisherman mode, we can't know where the proposer is publishing from
|
|
@@ -432,7 +522,7 @@ export class SequencerPublisher {
|
|
|
432
522
|
const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, ErrorsAbi);
|
|
433
523
|
this.log.verbose(`Simulation for invalidate block ${blockNumber} succeeded`, { ...logData, request, gasUsed });
|
|
434
524
|
|
|
435
|
-
return { request, gasUsed, blockNumber, forcePendingBlockNumber: blockNumber - 1, reason };
|
|
525
|
+
return { request, gasUsed, blockNumber, forcePendingBlockNumber: BlockNumber(blockNumber - 1), reason };
|
|
436
526
|
} catch (err) {
|
|
437
527
|
const viemError = formatViemError(err);
|
|
438
528
|
|
|
@@ -480,14 +570,14 @@ export class SequencerPublisher {
|
|
|
480
570
|
|
|
481
571
|
if (reason === 'invalid-attestation') {
|
|
482
572
|
return this.rollupContract.buildInvalidateBadAttestationRequest(
|
|
483
|
-
block.blockNumber,
|
|
573
|
+
CheckpointNumber.fromBlockNumber(block.blockNumber),
|
|
484
574
|
attestationsAndSigners,
|
|
485
575
|
committee,
|
|
486
576
|
validationResult.invalidIndex,
|
|
487
577
|
);
|
|
488
578
|
} else if (reason === 'insufficient-attestations') {
|
|
489
579
|
return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
|
|
490
|
-
block.blockNumber,
|
|
580
|
+
CheckpointNumber.fromBlockNumber(block.blockNumber),
|
|
491
581
|
attestationsAndSigners,
|
|
492
582
|
committee,
|
|
493
583
|
);
|
|
@@ -497,45 +587,38 @@ export class SequencerPublisher {
|
|
|
497
587
|
}
|
|
498
588
|
}
|
|
499
589
|
|
|
500
|
-
/**
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
* @dev Throws if unable to propose
|
|
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,
|
|
590
|
+
/** Simulates `propose` to make sure that the checkpoint is valid for submission */
|
|
591
|
+
public async validateCheckpointForSubmission(
|
|
592
|
+
checkpoint: Checkpoint,
|
|
511
593
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
512
594
|
attestationsAndSignersSignature: Signature,
|
|
513
|
-
options: { forcePendingBlockNumber?:
|
|
595
|
+
options: { forcePendingBlockNumber?: BlockNumber }, // TODO(palla/mbps): Should this be forcePendingCheckpointNumber?
|
|
514
596
|
): Promise<bigint> {
|
|
515
597
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
516
598
|
|
|
599
|
+
// TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
|
|
517
600
|
// If we have no attestations, we still need to provide the empty attestations
|
|
518
601
|
// 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 =
|
|
602
|
+
// const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
603
|
+
// if (ignoreSignatures) {
|
|
604
|
+
// const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
|
|
605
|
+
// if (!committee) {
|
|
606
|
+
// this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
607
|
+
// throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
608
|
+
// }
|
|
609
|
+
// attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
610
|
+
// CommitteeAttestation.fromAddress(committeeMember),
|
|
611
|
+
// );
|
|
612
|
+
// }
|
|
613
|
+
|
|
614
|
+
const blobFields = checkpoint.toBlobFields();
|
|
532
615
|
const blobs = getBlobsPerL1Block(blobFields);
|
|
533
616
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
534
617
|
|
|
535
618
|
const args = [
|
|
536
619
|
{
|
|
537
|
-
header:
|
|
538
|
-
archive: toHex(
|
|
620
|
+
header: checkpoint.header.toViem(),
|
|
621
|
+
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
539
622
|
oracleInput: {
|
|
540
623
|
feeAssetPriceModifier: 0n,
|
|
541
624
|
},
|
|
@@ -573,10 +656,19 @@ export class SequencerPublisher {
|
|
|
573
656
|
const round = await base.computeRound(slotNumber);
|
|
574
657
|
const roundInfo = await base.getRoundInfo(this.rollupContract.address, round);
|
|
575
658
|
|
|
659
|
+
if (roundInfo.quorumReached) {
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
|
|
576
663
|
if (roundInfo.lastSignalSlot >= slotNumber) {
|
|
577
664
|
return false;
|
|
578
665
|
}
|
|
579
666
|
|
|
667
|
+
if (await this.isPayloadEmpty(payload)) {
|
|
668
|
+
this.log.warn(`Skipping vote cast for payload with empty code`);
|
|
669
|
+
return false;
|
|
670
|
+
}
|
|
671
|
+
|
|
580
672
|
const cachedLastVote = this.lastActions[signalType];
|
|
581
673
|
this.lastActions[signalType] = slotNumber;
|
|
582
674
|
const action = signalType;
|
|
@@ -619,14 +711,14 @@ export class SequencerPublisher {
|
|
|
619
711
|
const logData = { ...result, slotNumber, round, payload: payload.toString() };
|
|
620
712
|
if (!success) {
|
|
621
713
|
this.log.error(
|
|
622
|
-
`Signaling in
|
|
714
|
+
`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} failed`,
|
|
623
715
|
logData,
|
|
624
716
|
);
|
|
625
717
|
this.lastActions[signalType] = cachedLastVote;
|
|
626
718
|
return false;
|
|
627
719
|
} else {
|
|
628
720
|
this.log.info(
|
|
629
|
-
`Signaling in
|
|
721
|
+
`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} succeeded`,
|
|
630
722
|
logData,
|
|
631
723
|
);
|
|
632
724
|
return true;
|
|
@@ -636,6 +728,17 @@ export class SequencerPublisher {
|
|
|
636
728
|
return true;
|
|
637
729
|
}
|
|
638
730
|
|
|
731
|
+
private async isPayloadEmpty(payload: EthAddress): Promise<boolean> {
|
|
732
|
+
const key = payload.toString();
|
|
733
|
+
const cached = this.isPayloadEmptyCache.get(key);
|
|
734
|
+
if (cached) {
|
|
735
|
+
return cached;
|
|
736
|
+
}
|
|
737
|
+
const isEmpty = !(await this.l1TxUtils.getCode(payload));
|
|
738
|
+
this.isPayloadEmptyCache.set(key, isEmpty);
|
|
739
|
+
return isEmpty;
|
|
740
|
+
}
|
|
741
|
+
|
|
639
742
|
/**
|
|
640
743
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
641
744
|
* @param slotNumber - The slot number to cast a signal for.
|
|
@@ -783,27 +886,21 @@ export class SequencerPublisher {
|
|
|
783
886
|
return true;
|
|
784
887
|
}
|
|
785
888
|
|
|
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,
|
|
889
|
+
/** Simulates and enqueues a proposal for a checkpoint on L1 */
|
|
890
|
+
public async enqueueProposeCheckpoint(
|
|
891
|
+
checkpoint: Checkpoint,
|
|
794
892
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
795
893
|
attestationsAndSignersSignature: Signature,
|
|
796
|
-
opts: { txTimeoutAt?: Date; forcePendingBlockNumber?:
|
|
797
|
-
): Promise<
|
|
798
|
-
const checkpointHeader =
|
|
894
|
+
opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: BlockNumber } = {},
|
|
895
|
+
): Promise<void> {
|
|
896
|
+
const checkpointHeader = checkpoint.header;
|
|
799
897
|
|
|
800
|
-
const blobFields =
|
|
898
|
+
const blobFields = checkpoint.toBlobFields();
|
|
801
899
|
const blobs = getBlobsPerL1Block(blobFields);
|
|
802
900
|
|
|
803
901
|
const proposeTxArgs = {
|
|
804
902
|
header: checkpointHeader,
|
|
805
|
-
archive:
|
|
806
|
-
body: block.body.toBuffer(),
|
|
903
|
+
archive: checkpoint.archive.root.toBuffer(),
|
|
807
904
|
blobs,
|
|
808
905
|
attestationsAndSigners,
|
|
809
906
|
attestationsAndSignersSignature,
|
|
@@ -817,19 +914,23 @@ export class SequencerPublisher {
|
|
|
817
914
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
818
915
|
// make time consistency checks break.
|
|
819
916
|
// 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.
|
|
917
|
+
ts = await this.validateCheckpointForSubmission(
|
|
918
|
+
checkpoint,
|
|
919
|
+
attestationsAndSigners,
|
|
920
|
+
attestationsAndSignersSignature,
|
|
921
|
+
opts,
|
|
922
|
+
);
|
|
821
923
|
} catch (err: any) {
|
|
822
|
-
this.log.error(`
|
|
823
|
-
...
|
|
824
|
-
slotNumber:
|
|
924
|
+
this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
925
|
+
...checkpoint.getStats(),
|
|
926
|
+
slotNumber: checkpoint.header.slotNumber,
|
|
825
927
|
forcePendingBlockNumber: opts.forcePendingBlockNumber,
|
|
826
928
|
});
|
|
827
929
|
throw err;
|
|
828
930
|
}
|
|
829
931
|
|
|
830
|
-
this.log.verbose(`Enqueuing
|
|
831
|
-
await this.addProposeTx(
|
|
832
|
-
return true;
|
|
932
|
+
this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
|
|
933
|
+
await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
|
|
833
934
|
}
|
|
834
935
|
|
|
835
936
|
public enqueueInvalidateBlock(request: InvalidateBlockRequest | undefined, opts: { txTimeoutAt?: Date } = {}) {
|
|
@@ -936,7 +1037,7 @@ export class SequencerPublisher {
|
|
|
936
1037
|
private async prepareProposeTx(
|
|
937
1038
|
encodedData: L1ProcessArgs,
|
|
938
1039
|
timestamp: bigint,
|
|
939
|
-
options: { forcePendingBlockNumber?:
|
|
1040
|
+
options: { forcePendingBlockNumber?: BlockNumber },
|
|
940
1041
|
) {
|
|
941
1042
|
const kzg = Blob.getViemKzgInstance();
|
|
942
1043
|
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
@@ -1017,7 +1118,7 @@ export class SequencerPublisher {
|
|
|
1017
1118
|
`0x${string}`,
|
|
1018
1119
|
],
|
|
1019
1120
|
timestamp: bigint,
|
|
1020
|
-
options: { forcePendingBlockNumber?:
|
|
1121
|
+
options: { forcePendingBlockNumber?: BlockNumber },
|
|
1021
1122
|
) {
|
|
1022
1123
|
const rollupData = encodeFunctionData({
|
|
1023
1124
|
abi: RollupAbi,
|
|
@@ -1025,10 +1126,14 @@ export class SequencerPublisher {
|
|
|
1025
1126
|
args,
|
|
1026
1127
|
});
|
|
1027
1128
|
|
|
1028
|
-
// override the pending
|
|
1029
|
-
const
|
|
1129
|
+
// override the pending checkpoint number if requested
|
|
1130
|
+
const optsForcePendingCheckpointNumber =
|
|
1030
1131
|
options.forcePendingBlockNumber !== undefined
|
|
1031
|
-
?
|
|
1132
|
+
? CheckpointNumber.fromBlockNumber(options.forcePendingBlockNumber)
|
|
1133
|
+
: undefined;
|
|
1134
|
+
const forcePendingCheckpointNumberStateDiff = (
|
|
1135
|
+
optsForcePendingCheckpointNumber !== undefined
|
|
1136
|
+
? await this.rollupContract.makePendingCheckpointNumberOverride(optsForcePendingCheckpointNumber)
|
|
1032
1137
|
: []
|
|
1033
1138
|
).flatMap(override => override.stateDiff ?? []);
|
|
1034
1139
|
|
|
@@ -1038,7 +1143,7 @@ export class SequencerPublisher {
|
|
|
1038
1143
|
// @note we override checkBlob to false since blobs are not part simulate()
|
|
1039
1144
|
stateDiff: [
|
|
1040
1145
|
{ slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
|
|
1041
|
-
...
|
|
1146
|
+
...forcePendingCheckpointNumberStateDiff,
|
|
1042
1147
|
],
|
|
1043
1148
|
},
|
|
1044
1149
|
];
|
|
@@ -1090,11 +1195,12 @@ export class SequencerPublisher {
|
|
|
1090
1195
|
}
|
|
1091
1196
|
|
|
1092
1197
|
private async addProposeTx(
|
|
1093
|
-
|
|
1198
|
+
checkpoint: Checkpoint,
|
|
1094
1199
|
encodedData: L1ProcessArgs,
|
|
1095
|
-
opts: { txTimeoutAt?: Date; forcePendingBlockNumber?:
|
|
1200
|
+
opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: BlockNumber } = {},
|
|
1096
1201
|
timestamp: bigint,
|
|
1097
1202
|
): Promise<void> {
|
|
1203
|
+
const slot = checkpoint.header.slotNumber;
|
|
1098
1204
|
const timer = new Timer();
|
|
1099
1205
|
const kzg = Blob.getViemKzgInstance();
|
|
1100
1206
|
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
@@ -1109,11 +1215,13 @@ export class SequencerPublisher {
|
|
|
1109
1215
|
SequencerPublisher.MULTICALL_OVERHEAD_GAS_GUESS, // We issue the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
1110
1216
|
);
|
|
1111
1217
|
|
|
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
|
-
|
|
1218
|
+
// Send the blobs to the blob client preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
|
|
1219
|
+
// tx fails but it does get mined. We make sure that the blobs are sent to the blob client regardless of the tx outcome.
|
|
1220
|
+
void Promise.resolve().then(() =>
|
|
1221
|
+
this.blobClient.sendBlobsToFilestore(encodedData.blobs).catch(_err => {
|
|
1222
|
+
this.log.error('Failed to send blobs to blob client');
|
|
1223
|
+
}),
|
|
1224
|
+
);
|
|
1117
1225
|
|
|
1118
1226
|
return this.addRequest({
|
|
1119
1227
|
action: 'propose',
|
|
@@ -1121,7 +1229,7 @@ export class SequencerPublisher {
|
|
|
1121
1229
|
to: this.rollupContract.address,
|
|
1122
1230
|
data: rollupData,
|
|
1123
1231
|
},
|
|
1124
|
-
lastValidL2Slot:
|
|
1232
|
+
lastValidL2Slot: checkpoint.header.slotNumber,
|
|
1125
1233
|
gasConfig: { ...opts, gasLimit },
|
|
1126
1234
|
blobConfig: {
|
|
1127
1235
|
blobs: encodedData.blobs.map(b => b.data),
|
|
@@ -1136,11 +1244,12 @@ export class SequencerPublisher {
|
|
|
1136
1244
|
receipt &&
|
|
1137
1245
|
receipt.status === 'success' &&
|
|
1138
1246
|
tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointProposed');
|
|
1247
|
+
|
|
1139
1248
|
if (success) {
|
|
1140
1249
|
const endBlock = receipt.blockNumber;
|
|
1141
1250
|
const inclusionBlocks = Number(endBlock - startBlock);
|
|
1142
1251
|
const { calldataGas, calldataSize, sender } = stats!;
|
|
1143
|
-
const publishStats:
|
|
1252
|
+
const publishStats: L1PublishCheckpointStats = {
|
|
1144
1253
|
gasPrice: receipt.effectiveGasPrice,
|
|
1145
1254
|
gasUsed: receipt.gasUsed,
|
|
1146
1255
|
blobGasUsed: receipt.blobGasUsed ?? 0n,
|
|
@@ -1149,23 +1258,26 @@ export class SequencerPublisher {
|
|
|
1149
1258
|
calldataGas,
|
|
1150
1259
|
calldataSize,
|
|
1151
1260
|
sender,
|
|
1152
|
-
...
|
|
1261
|
+
...checkpoint.getStats(),
|
|
1153
1262
|
eventName: 'rollup-published-to-l1',
|
|
1154
1263
|
blobCount: encodedData.blobs.length,
|
|
1155
1264
|
inclusionBlocks,
|
|
1156
1265
|
};
|
|
1157
|
-
this.log.info(`Published
|
|
1266
|
+
this.log.info(`Published checkpoint ${checkpoint.number} at slot ${slot} to rollup contract`, {
|
|
1267
|
+
...stats,
|
|
1268
|
+
...checkpoint.getStats(),
|
|
1269
|
+
...pick(receipt, 'transactionHash', 'blockHash'),
|
|
1270
|
+
});
|
|
1158
1271
|
this.metrics.recordProcessBlockTx(timer.ms(), publishStats);
|
|
1159
1272
|
|
|
1160
1273
|
return true;
|
|
1161
1274
|
} else {
|
|
1162
1275
|
this.metrics.recordFailedTx('process');
|
|
1163
|
-
this.log.error(
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
});
|
|
1276
|
+
this.log.error(
|
|
1277
|
+
`Publishing checkpoint at slot ${slot} failed with ${errorMsg ?? 'no error message'}`,
|
|
1278
|
+
undefined,
|
|
1279
|
+
{ ...checkpoint.getStats(), ...receipt },
|
|
1280
|
+
);
|
|
1169
1281
|
return false;
|
|
1170
1282
|
}
|
|
1171
1283
|
},
|