@aztec/sequencer-client 0.0.1-commit.b655e406 → 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/index.d.ts +1 -1
- 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 -24
- package/dest/config.d.ts +12 -5
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +75 -28
- package/dest/global_variable_builder/global_builder.d.ts +19 -13
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +41 -28
- package/dest/global_variable_builder/index.d.ts +1 -1
- 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 +9 -4
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +14 -3
- package/dest/publisher/index.d.ts +1 -1
- 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 +66 -53
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +230 -120
- 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 +9 -10
- 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/errors.d.ts +1 -1
- package/dest/sequencer/errors.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 +32 -3
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +192 -0
- package/dest/sequencer/sequencer.d.ts +96 -138
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +247 -479
- 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/nullifier_cache.d.ts +1 -1
- package/dest/tx_validator/nullifier_cache.d.ts.map +1 -1
- package/dest/tx_validator/tx_validator_factory.d.ts +4 -3
- 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 +31 -30
- package/src/client/sequencer-client.ts +28 -38
- package/src/config.ts +81 -32
- package/src/global_variable_builder/global_builder.ts +56 -48
- package/src/index.ts +2 -0
- package/src/publisher/config.ts +20 -9
- package/src/publisher/sequencer-publisher-factory.ts +7 -5
- package/src/publisher/sequencer-publisher-metrics.ts +2 -2
- package/src/publisher/sequencer-publisher.ts +328 -161
- package/src/sequencer/README.md +531 -0
- package/src/sequencer/block_builder.ts +12 -13
- 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 +254 -3
- package/src/sequencer/sequencer.ts +360 -674
- 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,46 +1,48 @@
|
|
|
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,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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,
|
|
22
|
+
WEI_CONST,
|
|
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 { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
29
|
+
import { pick } from '@aztec/foundation/collection';
|
|
30
|
+
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
28
31
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
29
32
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
30
|
-
import type { Fr } from '@aztec/foundation/fields';
|
|
31
33
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
32
34
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
33
35
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
34
36
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
35
37
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
36
|
-
import {
|
|
38
|
+
import { CommitteeAttestationsAndSigners, type ValidateBlockResult } from '@aztec/stdlib/block';
|
|
39
|
+
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
37
40
|
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
38
41
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
39
|
-
import type {
|
|
40
|
-
import { StateReference } from '@aztec/stdlib/tx';
|
|
42
|
+
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
41
43
|
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
42
44
|
|
|
43
|
-
import { type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
|
|
45
|
+
import { type StateOverride, type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
|
|
44
46
|
|
|
45
47
|
import type { PublisherConfig, TxSenderConfig } from './config.js';
|
|
46
48
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
@@ -51,8 +53,6 @@ type L1ProcessArgs = {
|
|
|
51
53
|
header: CheckpointHeader;
|
|
52
54
|
/** A root of the archive tree after the L2 block is applied. */
|
|
53
55
|
archive: Buffer;
|
|
54
|
-
/** State reference after the L2 block is applied. */
|
|
55
|
-
stateReference: StateReference;
|
|
56
56
|
/** L2 block blobs containing all tx effects. */
|
|
57
57
|
blobs: Blob[];
|
|
58
58
|
/** Attestations */
|
|
@@ -84,14 +84,14 @@ export type InvalidateBlockRequest = {
|
|
|
84
84
|
request: L1TxRequest;
|
|
85
85
|
reason: 'invalid-attestation' | 'insufficient-attestations';
|
|
86
86
|
gasUsed: bigint;
|
|
87
|
-
blockNumber:
|
|
88
|
-
forcePendingBlockNumber:
|
|
87
|
+
blockNumber: BlockNumber;
|
|
88
|
+
forcePendingBlockNumber: BlockNumber;
|
|
89
89
|
};
|
|
90
90
|
|
|
91
91
|
interface RequestWithExpiry {
|
|
92
92
|
action: Action;
|
|
93
93
|
request: L1TxRequest;
|
|
94
|
-
lastValidL2Slot:
|
|
94
|
+
lastValidL2Slot: SlotNumber;
|
|
95
95
|
gasConfig?: Pick<L1TxConfig, 'txTimeoutAt' | 'gasLimit'>;
|
|
96
96
|
blobConfig?: L1BlobInputs;
|
|
97
97
|
checkSuccess: (
|
|
@@ -108,12 +108,20 @@ export class SequencerPublisher {
|
|
|
108
108
|
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
109
109
|
protected slashingLog = createLogger('sequencer:publisher:slashing');
|
|
110
110
|
|
|
111
|
-
protected lastActions: Partial<Record<Action,
|
|
111
|
+
protected lastActions: Partial<Record<Action, SlotNumber>> = {};
|
|
112
|
+
|
|
113
|
+
private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
|
|
112
114
|
|
|
113
115
|
protected log: Logger;
|
|
114
116
|
protected ethereumSlotDuration: bigint;
|
|
115
117
|
|
|
116
|
-
private
|
|
118
|
+
private blobClient: BlobClientInterface;
|
|
119
|
+
|
|
120
|
+
/** Address to use for simulations in fisherman mode (actual proposer's address) */
|
|
121
|
+
private proposerAddressForSimulation?: EthAddress;
|
|
122
|
+
|
|
123
|
+
/** L1 fee analyzer for fisherman mode */
|
|
124
|
+
private l1FeeAnalyzer?: L1FeeAnalyzer;
|
|
117
125
|
// @note - with blobs, the below estimate seems too large.
|
|
118
126
|
// Total used for full block from int_l1_pub e2e test: 1m (of which 86k is 1x blob)
|
|
119
127
|
// Total used for emptier block from above test: 429k (of which 84k is 1x blob)
|
|
@@ -137,7 +145,7 @@ export class SequencerPublisher {
|
|
|
137
145
|
private config: TxSenderConfig & PublisherConfig & Pick<L1ContractsConfig, 'ethereumSlotDuration'>,
|
|
138
146
|
deps: {
|
|
139
147
|
telemetry?: TelemetryClient;
|
|
140
|
-
|
|
148
|
+
blobClient: BlobClientInterface;
|
|
141
149
|
l1TxUtils: L1TxUtilsWithBlobs;
|
|
142
150
|
rollupContract: RollupContract;
|
|
143
151
|
slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
@@ -146,7 +154,7 @@ export class SequencerPublisher {
|
|
|
146
154
|
epochCache: EpochCache;
|
|
147
155
|
dateProvider: DateProvider;
|
|
148
156
|
metrics: SequencerPublisherMetrics;
|
|
149
|
-
lastActions: Partial<Record<Action,
|
|
157
|
+
lastActions: Partial<Record<Action, SlotNumber>>;
|
|
150
158
|
log?: Logger;
|
|
151
159
|
},
|
|
152
160
|
) {
|
|
@@ -155,8 +163,7 @@ export class SequencerPublisher {
|
|
|
155
163
|
this.epochCache = deps.epochCache;
|
|
156
164
|
this.lastActions = deps.lastActions;
|
|
157
165
|
|
|
158
|
-
this.
|
|
159
|
-
deps.blobSinkClient ?? createBlobSinkClient(config, { logger: createLogger('sequencer:blob-sink:client') });
|
|
166
|
+
this.blobClient = deps.blobClient;
|
|
160
167
|
|
|
161
168
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
162
169
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
@@ -173,6 +180,15 @@ export class SequencerPublisher {
|
|
|
173
180
|
this.slashingProposerContract = newSlashingProposer;
|
|
174
181
|
});
|
|
175
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
|
+
}
|
|
176
192
|
}
|
|
177
193
|
|
|
178
194
|
public getRollupContract(): RollupContract {
|
|
@@ -183,14 +199,96 @@ export class SequencerPublisher {
|
|
|
183
199
|
return this.l1TxUtils.getSenderAddress();
|
|
184
200
|
}
|
|
185
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
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Sets the proposer address to use for simulations in fisherman mode.
|
|
211
|
+
* @param proposerAddress - The actual proposer's address to use for balance lookups in simulations
|
|
212
|
+
*/
|
|
213
|
+
public setProposerAddressForSimulation(proposerAddress: EthAddress | undefined) {
|
|
214
|
+
this.proposerAddressForSimulation = proposerAddress;
|
|
215
|
+
}
|
|
216
|
+
|
|
186
217
|
public addRequest(request: RequestWithExpiry) {
|
|
187
218
|
this.requests.push(request);
|
|
188
219
|
}
|
|
189
220
|
|
|
190
|
-
public getCurrentL2Slot():
|
|
221
|
+
public getCurrentL2Slot(): SlotNumber {
|
|
191
222
|
return this.epochCache.getEpochAndSlotNow().slot;
|
|
192
223
|
}
|
|
193
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Clears all pending requests without sending them.
|
|
227
|
+
*/
|
|
228
|
+
public clearPendingRequests(): void {
|
|
229
|
+
const count = this.requests.length;
|
|
230
|
+
this.requests = [];
|
|
231
|
+
if (count > 0) {
|
|
232
|
+
this.log.debug(`Cleared ${count} pending request(s)`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
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
|
+
|
|
194
292
|
/**
|
|
195
293
|
* Sends all requests that are still valid.
|
|
196
294
|
* @returns one of:
|
|
@@ -201,7 +299,7 @@ export class SequencerPublisher {
|
|
|
201
299
|
public async sendRequests() {
|
|
202
300
|
const requestsToProcess = [...this.requests];
|
|
203
301
|
this.requests = [];
|
|
204
|
-
if (this.interrupted) {
|
|
302
|
+
if (this.interrupted || requestsToProcess.length === 0) {
|
|
205
303
|
return undefined;
|
|
206
304
|
}
|
|
207
305
|
const currentL2Slot = this.getCurrentL2Slot();
|
|
@@ -315,13 +413,18 @@ export class SequencerPublisher {
|
|
|
315
413
|
public canProposeAtNextEthBlock(
|
|
316
414
|
tipArchive: Fr,
|
|
317
415
|
msgSender: EthAddress,
|
|
318
|
-
opts: { forcePendingBlockNumber?:
|
|
416
|
+
opts: { forcePendingBlockNumber?: BlockNumber } = {},
|
|
319
417
|
) {
|
|
320
418
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
321
419
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
322
420
|
|
|
323
421
|
return this.rollupContract
|
|
324
|
-
.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), this.ethereumSlotDuration,
|
|
422
|
+
.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
|
|
423
|
+
forcePendingCheckpointNumber:
|
|
424
|
+
opts.forcePendingBlockNumber !== undefined
|
|
425
|
+
? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber)
|
|
426
|
+
: undefined,
|
|
427
|
+
})
|
|
325
428
|
.catch(err => {
|
|
326
429
|
if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
|
|
327
430
|
this.log.warn(`Failed canProposeAtTime check with ${ignoredErrors.find(e => err.message.includes(e))}`, {
|
|
@@ -339,7 +442,10 @@ export class SequencerPublisher {
|
|
|
339
442
|
* It will throw if the block header is invalid.
|
|
340
443
|
* @param header - The block header to validate
|
|
341
444
|
*/
|
|
342
|
-
public async validateBlockHeader(
|
|
445
|
+
public async validateBlockHeader(
|
|
446
|
+
header: CheckpointHeader,
|
|
447
|
+
opts?: { forcePendingBlockNumber: BlockNumber | undefined },
|
|
448
|
+
) {
|
|
343
449
|
const flags = { ignoreDA: true, ignoreSignatures: true };
|
|
344
450
|
|
|
345
451
|
const args = [
|
|
@@ -353,10 +459,26 @@ export class SequencerPublisher {
|
|
|
353
459
|
] as const;
|
|
354
460
|
|
|
355
461
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
462
|
+
const optsForcePendingCheckpointNumber =
|
|
463
|
+
opts?.forcePendingBlockNumber !== undefined
|
|
464
|
+
? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber)
|
|
465
|
+
: undefined;
|
|
466
|
+
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
467
|
+
optsForcePendingCheckpointNumber,
|
|
468
|
+
);
|
|
469
|
+
let balance = 0n;
|
|
470
|
+
if (this.config.fishermanMode) {
|
|
471
|
+
// In fisherman mode, we can't know where the proposer is publishing from
|
|
472
|
+
// so we just add sufficient balance to the multicall3 address
|
|
473
|
+
balance = 10n * WEI_CONST * WEI_CONST; // 10 ETH
|
|
474
|
+
} else {
|
|
475
|
+
balance = await this.l1TxUtils.getSenderBalance();
|
|
476
|
+
}
|
|
477
|
+
stateOverrides.push({
|
|
478
|
+
address: MULTI_CALL_3_ADDRESS,
|
|
479
|
+
balance,
|
|
480
|
+
});
|
|
356
481
|
|
|
357
|
-
// use sender balance to simulate
|
|
358
|
-
const balance = await this.l1TxUtils.getSenderBalance();
|
|
359
|
-
this.log.debug(`Simulating validateHeader with balance: ${balance}`);
|
|
360
482
|
await this.l1TxUtils.simulate(
|
|
361
483
|
{
|
|
362
484
|
to: this.rollupContract.address,
|
|
@@ -364,10 +486,7 @@ export class SequencerPublisher {
|
|
|
364
486
|
from: MULTI_CALL_3_ADDRESS,
|
|
365
487
|
},
|
|
366
488
|
{ time: ts + 1n },
|
|
367
|
-
|
|
368
|
-
{ address: MULTI_CALL_3_ADDRESS, balance },
|
|
369
|
-
...(await this.rollupContract.makePendingBlockNumberOverride(opts?.forcePendingBlockNumber)),
|
|
370
|
-
],
|
|
489
|
+
stateOverrides,
|
|
371
490
|
);
|
|
372
491
|
this.log.debug(`Simulated validateHeader`);
|
|
373
492
|
}
|
|
@@ -387,7 +506,7 @@ export class SequencerPublisher {
|
|
|
387
506
|
const blockNumber = block.blockNumber;
|
|
388
507
|
const logData = { ...block, reason };
|
|
389
508
|
|
|
390
|
-
const currentBlockNumber = await this.rollupContract.
|
|
509
|
+
const currentBlockNumber = await this.rollupContract.getCheckpointNumber();
|
|
391
510
|
if (currentBlockNumber < validationResult.block.blockNumber) {
|
|
392
511
|
this.log.verbose(
|
|
393
512
|
`Skipping block ${blockNumber} invalidation since it has already been removed from the pending chain`,
|
|
@@ -403,7 +522,7 @@ export class SequencerPublisher {
|
|
|
403
522
|
const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, ErrorsAbi);
|
|
404
523
|
this.log.verbose(`Simulation for invalidate block ${blockNumber} succeeded`, { ...logData, request, gasUsed });
|
|
405
524
|
|
|
406
|
-
return { request, gasUsed, blockNumber, forcePendingBlockNumber: blockNumber - 1, reason };
|
|
525
|
+
return { request, gasUsed, blockNumber, forcePendingBlockNumber: BlockNumber(blockNumber - 1), reason };
|
|
407
526
|
} catch (err) {
|
|
408
527
|
const viemError = formatViemError(err);
|
|
409
528
|
|
|
@@ -414,7 +533,7 @@ export class SequencerPublisher {
|
|
|
414
533
|
`Simulation for invalidate block ${blockNumber} failed due to block not being in pending chain`,
|
|
415
534
|
{ ...logData, request, error: viemError.message },
|
|
416
535
|
);
|
|
417
|
-
const latestPendingBlockNumber = await this.rollupContract.
|
|
536
|
+
const latestPendingBlockNumber = await this.rollupContract.getCheckpointNumber();
|
|
418
537
|
if (latestPendingBlockNumber < blockNumber) {
|
|
419
538
|
this.log.verbose(`Block number ${blockNumber} has already been invalidated`, { ...logData });
|
|
420
539
|
return undefined;
|
|
@@ -451,14 +570,14 @@ export class SequencerPublisher {
|
|
|
451
570
|
|
|
452
571
|
if (reason === 'invalid-attestation') {
|
|
453
572
|
return this.rollupContract.buildInvalidateBadAttestationRequest(
|
|
454
|
-
block.blockNumber,
|
|
573
|
+
CheckpointNumber.fromBlockNumber(block.blockNumber),
|
|
455
574
|
attestationsAndSigners,
|
|
456
575
|
committee,
|
|
457
576
|
validationResult.invalidIndex,
|
|
458
577
|
);
|
|
459
578
|
} else if (reason === 'insufficient-attestations') {
|
|
460
579
|
return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
|
|
461
|
-
block.blockNumber,
|
|
580
|
+
CheckpointNumber.fromBlockNumber(block.blockNumber),
|
|
462
581
|
attestationsAndSigners,
|
|
463
582
|
committee,
|
|
464
583
|
);
|
|
@@ -468,46 +587,38 @@ export class SequencerPublisher {
|
|
|
468
587
|
}
|
|
469
588
|
}
|
|
470
589
|
|
|
471
|
-
/**
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
* @dev Throws if unable to propose
|
|
475
|
-
*
|
|
476
|
-
* @param block - The block to propose
|
|
477
|
-
* @param attestationData - The block's attestation data
|
|
478
|
-
*
|
|
479
|
-
*/
|
|
480
|
-
public async validateBlockForSubmission(
|
|
481
|
-
block: L2Block,
|
|
590
|
+
/** Simulates `propose` to make sure that the checkpoint is valid for submission */
|
|
591
|
+
public async validateCheckpointForSubmission(
|
|
592
|
+
checkpoint: Checkpoint,
|
|
482
593
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
483
594
|
attestationsAndSignersSignature: Signature,
|
|
484
|
-
options: { forcePendingBlockNumber?:
|
|
595
|
+
options: { forcePendingBlockNumber?: BlockNumber }, // TODO(palla/mbps): Should this be forcePendingCheckpointNumber?
|
|
485
596
|
): Promise<bigint> {
|
|
486
597
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
487
598
|
|
|
599
|
+
// TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
|
|
488
600
|
// If we have no attestations, we still need to provide the empty attestations
|
|
489
601
|
// so that the committee is recalculated correctly
|
|
490
|
-
const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
491
|
-
if (ignoreSignatures) {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
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();
|
|
503
615
|
const blobs = getBlobsPerL1Block(blobFields);
|
|
504
616
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
505
617
|
|
|
506
618
|
const args = [
|
|
507
619
|
{
|
|
508
|
-
header:
|
|
509
|
-
archive: toHex(
|
|
510
|
-
stateReference: block.header.state.toViem(),
|
|
620
|
+
header: checkpoint.header.toViem(),
|
|
621
|
+
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
511
622
|
oracleInput: {
|
|
512
623
|
feeAssetPriceModifier: 0n,
|
|
513
624
|
},
|
|
@@ -523,7 +634,7 @@ export class SequencerPublisher {
|
|
|
523
634
|
}
|
|
524
635
|
|
|
525
636
|
private async enqueueCastSignalHelper(
|
|
526
|
-
slotNumber:
|
|
637
|
+
slotNumber: SlotNumber,
|
|
527
638
|
timestamp: bigint,
|
|
528
639
|
signalType: GovernanceSignalAction,
|
|
529
640
|
payload: EthAddress,
|
|
@@ -545,10 +656,19 @@ export class SequencerPublisher {
|
|
|
545
656
|
const round = await base.computeRound(slotNumber);
|
|
546
657
|
const roundInfo = await base.getRoundInfo(this.rollupContract.address, round);
|
|
547
658
|
|
|
659
|
+
if (roundInfo.quorumReached) {
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
|
|
548
663
|
if (roundInfo.lastSignalSlot >= slotNumber) {
|
|
549
664
|
return false;
|
|
550
665
|
}
|
|
551
666
|
|
|
667
|
+
if (await this.isPayloadEmpty(payload)) {
|
|
668
|
+
this.log.warn(`Skipping vote cast for payload with empty code`);
|
|
669
|
+
return false;
|
|
670
|
+
}
|
|
671
|
+
|
|
552
672
|
const cachedLastVote = this.lastActions[signalType];
|
|
553
673
|
this.lastActions[signalType] = slotNumber;
|
|
554
674
|
const action = signalType;
|
|
@@ -591,14 +711,14 @@ export class SequencerPublisher {
|
|
|
591
711
|
const logData = { ...result, slotNumber, round, payload: payload.toString() };
|
|
592
712
|
if (!success) {
|
|
593
713
|
this.log.error(
|
|
594
|
-
`Signaling in
|
|
714
|
+
`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} failed`,
|
|
595
715
|
logData,
|
|
596
716
|
);
|
|
597
717
|
this.lastActions[signalType] = cachedLastVote;
|
|
598
718
|
return false;
|
|
599
719
|
} else {
|
|
600
720
|
this.log.info(
|
|
601
|
-
`Signaling in
|
|
721
|
+
`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} succeeded`,
|
|
602
722
|
logData,
|
|
603
723
|
);
|
|
604
724
|
return true;
|
|
@@ -608,6 +728,17 @@ export class SequencerPublisher {
|
|
|
608
728
|
return true;
|
|
609
729
|
}
|
|
610
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
|
+
|
|
611
742
|
/**
|
|
612
743
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
613
744
|
* @param slotNumber - The slot number to cast a signal for.
|
|
@@ -616,7 +747,7 @@ export class SequencerPublisher {
|
|
|
616
747
|
*/
|
|
617
748
|
public enqueueGovernanceCastSignal(
|
|
618
749
|
governancePayload: EthAddress,
|
|
619
|
-
slotNumber:
|
|
750
|
+
slotNumber: SlotNumber,
|
|
620
751
|
timestamp: bigint,
|
|
621
752
|
signerAddress: EthAddress,
|
|
622
753
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
@@ -635,7 +766,7 @@ export class SequencerPublisher {
|
|
|
635
766
|
/** Enqueues all slashing actions as returned by the slasher client. */
|
|
636
767
|
public async enqueueSlashingActions(
|
|
637
768
|
actions: ProposerSlashAction[],
|
|
638
|
-
slotNumber:
|
|
769
|
+
slotNumber: SlotNumber,
|
|
639
770
|
timestamp: bigint,
|
|
640
771
|
signerAddress: EthAddress,
|
|
641
772
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
@@ -755,28 +886,21 @@ export class SequencerPublisher {
|
|
|
755
886
|
return true;
|
|
756
887
|
}
|
|
757
888
|
|
|
758
|
-
/**
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
* @param block - L2 block to propose.
|
|
762
|
-
* @returns True if the tx has been enqueued, throws otherwise. See #9315
|
|
763
|
-
*/
|
|
764
|
-
public async enqueueProposeL2Block(
|
|
765
|
-
block: L2Block,
|
|
889
|
+
/** Simulates and enqueues a proposal for a checkpoint on L1 */
|
|
890
|
+
public async enqueueProposeCheckpoint(
|
|
891
|
+
checkpoint: Checkpoint,
|
|
766
892
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
767
893
|
attestationsAndSignersSignature: Signature,
|
|
768
|
-
opts: { txTimeoutAt?: Date; forcePendingBlockNumber?:
|
|
769
|
-
): Promise<
|
|
770
|
-
const checkpointHeader =
|
|
894
|
+
opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: BlockNumber } = {},
|
|
895
|
+
): Promise<void> {
|
|
896
|
+
const checkpointHeader = checkpoint.header;
|
|
771
897
|
|
|
772
|
-
const blobFields =
|
|
898
|
+
const blobFields = checkpoint.toBlobFields();
|
|
773
899
|
const blobs = getBlobsPerL1Block(blobFields);
|
|
774
900
|
|
|
775
901
|
const proposeTxArgs = {
|
|
776
902
|
header: checkpointHeader,
|
|
777
|
-
archive:
|
|
778
|
-
stateReference: block.header.state,
|
|
779
|
-
body: block.body.toBuffer(),
|
|
903
|
+
archive: checkpoint.archive.root.toBuffer(),
|
|
780
904
|
blobs,
|
|
781
905
|
attestationsAndSigners,
|
|
782
906
|
attestationsAndSignersSignature,
|
|
@@ -790,19 +914,23 @@ export class SequencerPublisher {
|
|
|
790
914
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
791
915
|
// make time consistency checks break.
|
|
792
916
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
793
|
-
ts = await this.
|
|
917
|
+
ts = await this.validateCheckpointForSubmission(
|
|
918
|
+
checkpoint,
|
|
919
|
+
attestationsAndSigners,
|
|
920
|
+
attestationsAndSignersSignature,
|
|
921
|
+
opts,
|
|
922
|
+
);
|
|
794
923
|
} catch (err: any) {
|
|
795
|
-
this.log.error(`
|
|
796
|
-
...
|
|
797
|
-
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,
|
|
798
927
|
forcePendingBlockNumber: opts.forcePendingBlockNumber,
|
|
799
928
|
});
|
|
800
929
|
throw err;
|
|
801
930
|
}
|
|
802
931
|
|
|
803
|
-
this.log.verbose(`Enqueuing
|
|
804
|
-
await this.addProposeTx(
|
|
805
|
-
return true;
|
|
932
|
+
this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
|
|
933
|
+
await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
|
|
806
934
|
}
|
|
807
935
|
|
|
808
936
|
public enqueueInvalidateBlock(request: InvalidateBlockRequest | undefined, opts: { txTimeoutAt?: Date } = {}) {
|
|
@@ -820,13 +948,13 @@ export class SequencerPublisher {
|
|
|
820
948
|
action: `invalidate-by-${request.reason}`,
|
|
821
949
|
request: request.request,
|
|
822
950
|
gasConfig: { gasLimit, txTimeoutAt: opts.txTimeoutAt },
|
|
823
|
-
lastValidL2Slot: this.getCurrentL2Slot() +
|
|
951
|
+
lastValidL2Slot: SlotNumber(this.getCurrentL2Slot() + 2),
|
|
824
952
|
checkSuccess: (_req, result) => {
|
|
825
953
|
const success =
|
|
826
954
|
result &&
|
|
827
955
|
result.receipt &&
|
|
828
956
|
result.receipt.status === 'success' &&
|
|
829
|
-
tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, '
|
|
957
|
+
tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointInvalidated');
|
|
830
958
|
if (!success) {
|
|
831
959
|
this.log.warn(`Invalidate block ${request.blockNumber} failed`, { ...result, ...logData });
|
|
832
960
|
} else {
|
|
@@ -841,7 +969,7 @@ export class SequencerPublisher {
|
|
|
841
969
|
action: Action,
|
|
842
970
|
request: L1TxRequest,
|
|
843
971
|
checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
|
|
844
|
-
slotNumber:
|
|
972
|
+
slotNumber: SlotNumber,
|
|
845
973
|
timestamp: bigint,
|
|
846
974
|
) {
|
|
847
975
|
const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
|
|
@@ -909,41 +1037,50 @@ export class SequencerPublisher {
|
|
|
909
1037
|
private async prepareProposeTx(
|
|
910
1038
|
encodedData: L1ProcessArgs,
|
|
911
1039
|
timestamp: bigint,
|
|
912
|
-
options: { forcePendingBlockNumber?:
|
|
1040
|
+
options: { forcePendingBlockNumber?: BlockNumber },
|
|
913
1041
|
) {
|
|
914
1042
|
const kzg = Blob.getViemKzgInstance();
|
|
915
1043
|
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
916
1044
|
this.log.debug('Validating blob input', { blobInput });
|
|
917
|
-
const blobEvaluationGas = await this.l1TxUtils
|
|
918
|
-
.estimateGas(
|
|
919
|
-
this.getSenderAddress().toString(),
|
|
920
|
-
{
|
|
921
|
-
to: this.rollupContract.address,
|
|
922
|
-
data: encodeFunctionData({
|
|
923
|
-
abi: RollupAbi,
|
|
924
|
-
functionName: 'validateBlobs',
|
|
925
|
-
args: [blobInput],
|
|
926
|
-
}),
|
|
927
|
-
},
|
|
928
|
-
{},
|
|
929
|
-
{
|
|
930
|
-
blobs: encodedData.blobs.map(b => b.data),
|
|
931
|
-
kzg,
|
|
932
|
-
},
|
|
933
|
-
)
|
|
934
|
-
.catch(err => {
|
|
935
|
-
const { message, metaMessages } = formatViemError(err);
|
|
936
|
-
this.log.error(`Failed to validate blobs`, message, { metaMessages });
|
|
937
|
-
throw new Error('Failed to validate blobs');
|
|
938
|
-
});
|
|
939
1045
|
|
|
1046
|
+
// Get blob evaluation gas
|
|
1047
|
+
let blobEvaluationGas: bigint;
|
|
1048
|
+
if (this.config.fishermanMode) {
|
|
1049
|
+
// In fisherman mode, we can't estimate blob gas because estimateGas doesn't support state overrides
|
|
1050
|
+
// Use a fixed estimate.
|
|
1051
|
+
blobEvaluationGas = BigInt(encodedData.blobs.length) * 21_000n;
|
|
1052
|
+
this.log.debug(`Using fixed blob evaluation gas estimate in fisherman mode: ${blobEvaluationGas}`);
|
|
1053
|
+
} else {
|
|
1054
|
+
// Normal mode - use estimateGas with blob inputs
|
|
1055
|
+
blobEvaluationGas = await this.l1TxUtils
|
|
1056
|
+
.estimateGas(
|
|
1057
|
+
this.getSenderAddress().toString(),
|
|
1058
|
+
{
|
|
1059
|
+
to: this.rollupContract.address,
|
|
1060
|
+
data: encodeFunctionData({
|
|
1061
|
+
abi: RollupAbi,
|
|
1062
|
+
functionName: 'validateBlobs',
|
|
1063
|
+
args: [blobInput],
|
|
1064
|
+
}),
|
|
1065
|
+
},
|
|
1066
|
+
{},
|
|
1067
|
+
{
|
|
1068
|
+
blobs: encodedData.blobs.map(b => b.data),
|
|
1069
|
+
kzg,
|
|
1070
|
+
},
|
|
1071
|
+
)
|
|
1072
|
+
.catch(err => {
|
|
1073
|
+
const { message, metaMessages } = formatViemError(err);
|
|
1074
|
+
this.log.error(`Failed to validate blobs`, message, { metaMessages });
|
|
1075
|
+
throw new Error('Failed to validate blobs');
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
940
1078
|
const signers = encodedData.attestationsAndSigners.getSigners().map(signer => signer.toString());
|
|
941
1079
|
|
|
942
1080
|
const args = [
|
|
943
1081
|
{
|
|
944
1082
|
header: encodedData.header.toViem(),
|
|
945
1083
|
archive: toHex(encodedData.archive),
|
|
946
|
-
stateReference: encodedData.stateReference.toViem(),
|
|
947
1084
|
oracleInput: {
|
|
948
1085
|
// We are currently not modifying these. See #9963
|
|
949
1086
|
feeAssetPriceModifier: 0n,
|
|
@@ -971,7 +1108,6 @@ export class SequencerPublisher {
|
|
|
971
1108
|
{
|
|
972
1109
|
readonly header: ViemHeader;
|
|
973
1110
|
readonly archive: `0x${string}`;
|
|
974
|
-
readonly stateReference: ViemStateReference;
|
|
975
1111
|
readonly oracleInput: {
|
|
976
1112
|
readonly feeAssetPriceModifier: 0n;
|
|
977
1113
|
};
|
|
@@ -982,7 +1118,7 @@ export class SequencerPublisher {
|
|
|
982
1118
|
`0x${string}`,
|
|
983
1119
|
],
|
|
984
1120
|
timestamp: bigint,
|
|
985
|
-
options: { forcePendingBlockNumber?:
|
|
1121
|
+
options: { forcePendingBlockNumber?: BlockNumber },
|
|
986
1122
|
) {
|
|
987
1123
|
const rollupData = encodeFunctionData({
|
|
988
1124
|
abi: RollupAbi,
|
|
@@ -990,19 +1126,42 @@ export class SequencerPublisher {
|
|
|
990
1126
|
args,
|
|
991
1127
|
});
|
|
992
1128
|
|
|
993
|
-
// override the pending
|
|
994
|
-
const
|
|
1129
|
+
// override the pending checkpoint number if requested
|
|
1130
|
+
const optsForcePendingCheckpointNumber =
|
|
995
1131
|
options.forcePendingBlockNumber !== undefined
|
|
996
|
-
?
|
|
1132
|
+
? CheckpointNumber.fromBlockNumber(options.forcePendingBlockNumber)
|
|
1133
|
+
: undefined;
|
|
1134
|
+
const forcePendingCheckpointNumberStateDiff = (
|
|
1135
|
+
optsForcePendingCheckpointNumber !== undefined
|
|
1136
|
+
? await this.rollupContract.makePendingCheckpointNumberOverride(optsForcePendingCheckpointNumber)
|
|
997
1137
|
: []
|
|
998
1138
|
).flatMap(override => override.stateDiff ?? []);
|
|
999
1139
|
|
|
1140
|
+
const stateOverrides: StateOverride = [
|
|
1141
|
+
{
|
|
1142
|
+
address: this.rollupContract.address,
|
|
1143
|
+
// @note we override checkBlob to false since blobs are not part simulate()
|
|
1144
|
+
stateDiff: [
|
|
1145
|
+
{ slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
|
|
1146
|
+
...forcePendingCheckpointNumberStateDiff,
|
|
1147
|
+
],
|
|
1148
|
+
},
|
|
1149
|
+
];
|
|
1150
|
+
// In fisherman mode, simulate as the proposer but with sufficient balance
|
|
1151
|
+
if (this.proposerAddressForSimulation) {
|
|
1152
|
+
stateOverrides.push({
|
|
1153
|
+
address: this.proposerAddressForSimulation.toString(),
|
|
1154
|
+
balance: 10n * WEI_CONST * WEI_CONST, // 10 ETH
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1000
1158
|
const simulationResult = await this.l1TxUtils
|
|
1001
1159
|
.simulate(
|
|
1002
1160
|
{
|
|
1003
1161
|
to: this.rollupContract.address,
|
|
1004
1162
|
data: rollupData,
|
|
1005
1163
|
gas: SequencerPublisher.PROPOSE_GAS_GUESS,
|
|
1164
|
+
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1006
1165
|
},
|
|
1007
1166
|
{
|
|
1008
1167
|
// @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
|
|
@@ -1010,16 +1169,7 @@ export class SequencerPublisher {
|
|
|
1010
1169
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1011
1170
|
gasLimit: SequencerPublisher.PROPOSE_GAS_GUESS * 2n,
|
|
1012
1171
|
},
|
|
1013
|
-
|
|
1014
|
-
{
|
|
1015
|
-
address: this.rollupContract.address,
|
|
1016
|
-
// @note we override checkBlob to false since blobs are not part simulate()
|
|
1017
|
-
stateDiff: [
|
|
1018
|
-
{ slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
|
|
1019
|
-
...forcePendingBlockNumberStateDiff,
|
|
1020
|
-
],
|
|
1021
|
-
},
|
|
1022
|
-
],
|
|
1172
|
+
stateOverrides,
|
|
1023
1173
|
RollupAbi,
|
|
1024
1174
|
{
|
|
1025
1175
|
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
@@ -1027,7 +1177,17 @@ export class SequencerPublisher {
|
|
|
1027
1177
|
},
|
|
1028
1178
|
)
|
|
1029
1179
|
.catch(err => {
|
|
1030
|
-
|
|
1180
|
+
// In fisherman mode, we expect ValidatorSelection__MissingProposerSignature since fisherman doesn't have proposer signature
|
|
1181
|
+
const viemError = formatViemError(err);
|
|
1182
|
+
if (this.config.fishermanMode && viemError.message?.includes('ValidatorSelection__MissingProposerSignature')) {
|
|
1183
|
+
this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
|
|
1184
|
+
// Return a minimal simulation result with the fallback gas estimate
|
|
1185
|
+
return {
|
|
1186
|
+
gasUsed: SequencerPublisher.PROPOSE_GAS_GUESS,
|
|
1187
|
+
logs: [],
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1031
1191
|
throw err;
|
|
1032
1192
|
});
|
|
1033
1193
|
|
|
@@ -1035,11 +1195,12 @@ export class SequencerPublisher {
|
|
|
1035
1195
|
}
|
|
1036
1196
|
|
|
1037
1197
|
private async addProposeTx(
|
|
1038
|
-
|
|
1198
|
+
checkpoint: Checkpoint,
|
|
1039
1199
|
encodedData: L1ProcessArgs,
|
|
1040
|
-
opts: { txTimeoutAt?: Date; forcePendingBlockNumber?:
|
|
1200
|
+
opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: BlockNumber } = {},
|
|
1041
1201
|
timestamp: bigint,
|
|
1042
1202
|
): Promise<void> {
|
|
1203
|
+
const slot = checkpoint.header.slotNumber;
|
|
1043
1204
|
const timer = new Timer();
|
|
1044
1205
|
const kzg = Blob.getViemKzgInstance();
|
|
1045
1206
|
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
@@ -1054,11 +1215,13 @@ export class SequencerPublisher {
|
|
|
1054
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
|
|
1055
1216
|
);
|
|
1056
1217
|
|
|
1057
|
-
// Send the blobs to the blob
|
|
1058
|
-
// tx fails but it does get mined. We make sure that the blobs are sent to the blob
|
|
1059
|
-
void
|
|
1060
|
-
this.
|
|
1061
|
-
|
|
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
|
+
);
|
|
1062
1225
|
|
|
1063
1226
|
return this.addRequest({
|
|
1064
1227
|
action: 'propose',
|
|
@@ -1066,7 +1229,7 @@ export class SequencerPublisher {
|
|
|
1066
1229
|
to: this.rollupContract.address,
|
|
1067
1230
|
data: rollupData,
|
|
1068
1231
|
},
|
|
1069
|
-
lastValidL2Slot:
|
|
1232
|
+
lastValidL2Slot: checkpoint.header.slotNumber,
|
|
1070
1233
|
gasConfig: { ...opts, gasLimit },
|
|
1071
1234
|
blobConfig: {
|
|
1072
1235
|
blobs: encodedData.blobs.map(b => b.data),
|
|
@@ -1080,12 +1243,13 @@ export class SequencerPublisher {
|
|
|
1080
1243
|
const success =
|
|
1081
1244
|
receipt &&
|
|
1082
1245
|
receipt.status === 'success' &&
|
|
1083
|
-
tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, '
|
|
1246
|
+
tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointProposed');
|
|
1247
|
+
|
|
1084
1248
|
if (success) {
|
|
1085
1249
|
const endBlock = receipt.blockNumber;
|
|
1086
1250
|
const inclusionBlocks = Number(endBlock - startBlock);
|
|
1087
1251
|
const { calldataGas, calldataSize, sender } = stats!;
|
|
1088
|
-
const publishStats:
|
|
1252
|
+
const publishStats: L1PublishCheckpointStats = {
|
|
1089
1253
|
gasPrice: receipt.effectiveGasPrice,
|
|
1090
1254
|
gasUsed: receipt.gasUsed,
|
|
1091
1255
|
blobGasUsed: receipt.blobGasUsed ?? 0n,
|
|
@@ -1094,23 +1258,26 @@ export class SequencerPublisher {
|
|
|
1094
1258
|
calldataGas,
|
|
1095
1259
|
calldataSize,
|
|
1096
1260
|
sender,
|
|
1097
|
-
...
|
|
1261
|
+
...checkpoint.getStats(),
|
|
1098
1262
|
eventName: 'rollup-published-to-l1',
|
|
1099
1263
|
blobCount: encodedData.blobs.length,
|
|
1100
1264
|
inclusionBlocks,
|
|
1101
1265
|
};
|
|
1102
|
-
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
|
+
});
|
|
1103
1271
|
this.metrics.recordProcessBlockTx(timer.ms(), publishStats);
|
|
1104
1272
|
|
|
1105
1273
|
return true;
|
|
1106
1274
|
} else {
|
|
1107
1275
|
this.metrics.recordFailedTx('process');
|
|
1108
|
-
this.log.error(
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
});
|
|
1276
|
+
this.log.error(
|
|
1277
|
+
`Publishing checkpoint at slot ${slot} failed with ${errorMsg ?? 'no error message'}`,
|
|
1278
|
+
undefined,
|
|
1279
|
+
{ ...checkpoint.getStats(), ...receipt },
|
|
1280
|
+
);
|
|
1114
1281
|
return false;
|
|
1115
1282
|
}
|
|
1116
1283
|
},
|