@aztec/sequencer-client 0.0.1-commit.b655e406 → 0.0.1-commit.d1f2d6c
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 +12 -12
- 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 +80 -28
- package/dest/global_variable_builder/global_builder.d.ts +22 -16
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +52 -39
- package/dest/global_variable_builder/index.d.ts +1 -1
- 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 +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-metrics.js +15 -86
- package/dest/publisher/sequencer-publisher.d.ts +75 -61
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +661 -158
- package/dest/sequencer/checkpoint_proposal_job.d.ts +79 -0
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_proposal_job.js +1164 -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/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 +4 -2
- package/dest/sequencer/index.d.ts.map +1 -1
- package/dest/sequencer/index.js +3 -1
- package/dest/sequencer/metrics.d.ts +32 -3
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +147 -46
- package/dest/sequencer/sequencer.d.ts +110 -142
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +716 -504
- 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 +92 -0
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -0
- package/dest/test/mock_checkpoint_builder.js +208 -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 +33 -30
- package/src/client/sequencer-client.ts +30 -41
- package/src/config.ts +86 -32
- package/src/global_variable_builder/global_builder.ts +67 -59
- package/src/index.ts +1 -7
- package/src/publisher/config.ts +20 -9
- package/src/publisher/sequencer-publisher-factory.ts +7 -5
- package/src/publisher/sequencer-publisher-metrics.ts +16 -72
- package/src/publisher/sequencer-publisher.ts +381 -203
- package/src/sequencer/README.md +531 -0
- package/src/sequencer/checkpoint_proposal_job.ts +843 -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 +198 -55
- package/src/sequencer/sequencer.ts +465 -696
- 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 +295 -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 -130
- 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 -218
- package/src/tx_validator/nullifier_cache.ts +0 -30
- package/src/tx_validator/tx_validator_factory.ts +0 -132
|
@@ -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 { 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 ValidateCheckpointResult } 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 {
|
|
41
|
-
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
42
|
+
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
43
|
+
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
42
44
|
|
|
43
|
-
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 */
|
|
@@ -80,18 +80,18 @@ type GovernanceSignalAction = Extract<Action, 'governance-signal' | 'empire-slas
|
|
|
80
80
|
// Sorting for actions such that invalidations go before proposals, and proposals go before votes
|
|
81
81
|
export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
|
|
82
82
|
|
|
83
|
-
export type
|
|
83
|
+
export type InvalidateCheckpointRequest = {
|
|
84
84
|
request: L1TxRequest;
|
|
85
85
|
reason: 'invalid-attestation' | 'insufficient-attestations';
|
|
86
86
|
gasUsed: bigint;
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
checkpointNumber: CheckpointNumber;
|
|
88
|
+
forcePendingCheckpointNumber: CheckpointNumber;
|
|
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)
|
|
@@ -131,13 +139,15 @@ export class SequencerPublisher {
|
|
|
131
139
|
public slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
132
140
|
public slashFactoryContract: SlashFactoryContract;
|
|
133
141
|
|
|
142
|
+
public readonly tracer: Tracer;
|
|
143
|
+
|
|
134
144
|
protected requests: RequestWithExpiry[] = [];
|
|
135
145
|
|
|
136
146
|
constructor(
|
|
137
147
|
private config: TxSenderConfig & PublisherConfig & Pick<L1ContractsConfig, 'ethereumSlotDuration'>,
|
|
138
148
|
deps: {
|
|
139
149
|
telemetry?: TelemetryClient;
|
|
140
|
-
|
|
150
|
+
blobClient: BlobClientInterface;
|
|
141
151
|
l1TxUtils: L1TxUtilsWithBlobs;
|
|
142
152
|
rollupContract: RollupContract;
|
|
143
153
|
slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
@@ -146,7 +156,7 @@ export class SequencerPublisher {
|
|
|
146
156
|
epochCache: EpochCache;
|
|
147
157
|
dateProvider: DateProvider;
|
|
148
158
|
metrics: SequencerPublisherMetrics;
|
|
149
|
-
lastActions: Partial<Record<Action,
|
|
159
|
+
lastActions: Partial<Record<Action, SlotNumber>>;
|
|
150
160
|
log?: Logger;
|
|
151
161
|
},
|
|
152
162
|
) {
|
|
@@ -155,11 +165,11 @@ export class SequencerPublisher {
|
|
|
155
165
|
this.epochCache = deps.epochCache;
|
|
156
166
|
this.lastActions = deps.lastActions;
|
|
157
167
|
|
|
158
|
-
this.
|
|
159
|
-
deps.blobSinkClient ?? createBlobSinkClient(config, { logger: createLogger('sequencer:blob-sink:client') });
|
|
168
|
+
this.blobClient = deps.blobClient;
|
|
160
169
|
|
|
161
170
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
162
171
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
172
|
+
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
163
173
|
this.l1TxUtils = deps.l1TxUtils;
|
|
164
174
|
|
|
165
175
|
this.rollupContract = deps.rollupContract;
|
|
@@ -173,6 +183,15 @@ export class SequencerPublisher {
|
|
|
173
183
|
this.slashingProposerContract = newSlashingProposer;
|
|
174
184
|
});
|
|
175
185
|
this.slashFactoryContract = deps.slashFactoryContract;
|
|
186
|
+
|
|
187
|
+
// Initialize L1 fee analyzer for fisherman mode
|
|
188
|
+
if (config.fishermanMode) {
|
|
189
|
+
this.l1FeeAnalyzer = new L1FeeAnalyzer(
|
|
190
|
+
this.l1TxUtils.client,
|
|
191
|
+
deps.dateProvider,
|
|
192
|
+
createLogger('sequencer:publisher:fee-analyzer'),
|
|
193
|
+
);
|
|
194
|
+
}
|
|
176
195
|
}
|
|
177
196
|
|
|
178
197
|
public getRollupContract(): RollupContract {
|
|
@@ -183,14 +202,96 @@ export class SequencerPublisher {
|
|
|
183
202
|
return this.l1TxUtils.getSenderAddress();
|
|
184
203
|
}
|
|
185
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Gets the L1 fee analyzer instance (only available in fisherman mode)
|
|
207
|
+
*/
|
|
208
|
+
public getL1FeeAnalyzer(): L1FeeAnalyzer | undefined {
|
|
209
|
+
return this.l1FeeAnalyzer;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Sets the proposer address to use for simulations in fisherman mode.
|
|
214
|
+
* @param proposerAddress - The actual proposer's address to use for balance lookups in simulations
|
|
215
|
+
*/
|
|
216
|
+
public setProposerAddressForSimulation(proposerAddress: EthAddress | undefined) {
|
|
217
|
+
this.proposerAddressForSimulation = proposerAddress;
|
|
218
|
+
}
|
|
219
|
+
|
|
186
220
|
public addRequest(request: RequestWithExpiry) {
|
|
187
221
|
this.requests.push(request);
|
|
188
222
|
}
|
|
189
223
|
|
|
190
|
-
public getCurrentL2Slot():
|
|
224
|
+
public getCurrentL2Slot(): SlotNumber {
|
|
191
225
|
return this.epochCache.getEpochAndSlotNow().slot;
|
|
192
226
|
}
|
|
193
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Clears all pending requests without sending them.
|
|
230
|
+
*/
|
|
231
|
+
public clearPendingRequests(): void {
|
|
232
|
+
const count = this.requests.length;
|
|
233
|
+
this.requests = [];
|
|
234
|
+
if (count > 0) {
|
|
235
|
+
this.log.debug(`Cleared ${count} pending request(s)`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Analyzes L1 fees for the pending requests without sending them.
|
|
241
|
+
* This is used in fisherman mode to validate fee calculations.
|
|
242
|
+
* @param l2SlotNumber - The L2 slot number for this analysis
|
|
243
|
+
* @param onComplete - Optional callback to invoke when analysis completes (after block is mined)
|
|
244
|
+
* @returns The analysis result (incomplete until block mines), or undefined if no requests
|
|
245
|
+
*/
|
|
246
|
+
public async analyzeL1Fees(
|
|
247
|
+
l2SlotNumber: SlotNumber,
|
|
248
|
+
onComplete?: (analysis: L1FeeAnalysisResult) => void,
|
|
249
|
+
): Promise<L1FeeAnalysisResult | undefined> {
|
|
250
|
+
if (!this.l1FeeAnalyzer) {
|
|
251
|
+
this.log.warn('L1 fee analyzer not available (not in fisherman mode)');
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const requestsToAnalyze = [...this.requests];
|
|
256
|
+
if (requestsToAnalyze.length === 0) {
|
|
257
|
+
this.log.debug('No requests to analyze for L1 fees');
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Extract blob config from requests (if any)
|
|
262
|
+
const blobConfigs = requestsToAnalyze.filter(request => request.blobConfig).map(request => request.blobConfig);
|
|
263
|
+
const blobConfig = blobConfigs[0];
|
|
264
|
+
|
|
265
|
+
// Get gas configs
|
|
266
|
+
const gasConfigs = requestsToAnalyze.filter(request => request.gasConfig).map(request => request.gasConfig);
|
|
267
|
+
const gasLimits = gasConfigs.map(g => g?.gasLimit).filter((g): g is bigint => g !== undefined);
|
|
268
|
+
const gasLimit = gasLimits.length > 0 ? gasLimits.reduce((sum, g) => sum + g, 0n) : 0n;
|
|
269
|
+
|
|
270
|
+
// Get the transaction requests
|
|
271
|
+
const l1Requests = requestsToAnalyze.map(r => r.request);
|
|
272
|
+
|
|
273
|
+
// Start the analysis
|
|
274
|
+
const analysisId = await this.l1FeeAnalyzer.startAnalysis(
|
|
275
|
+
l2SlotNumber,
|
|
276
|
+
gasLimit > 0n ? gasLimit : SequencerPublisher.PROPOSE_GAS_GUESS,
|
|
277
|
+
l1Requests,
|
|
278
|
+
blobConfig,
|
|
279
|
+
onComplete,
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
this.log.info('Started L1 fee analysis', {
|
|
283
|
+
analysisId,
|
|
284
|
+
l2SlotNumber: l2SlotNumber.toString(),
|
|
285
|
+
requestCount: requestsToAnalyze.length,
|
|
286
|
+
hasBlobConfig: !!blobConfig,
|
|
287
|
+
gasLimit: gasLimit.toString(),
|
|
288
|
+
actions: requestsToAnalyze.map(r => r.action),
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Return the analysis result (will be incomplete until block mines)
|
|
292
|
+
return this.l1FeeAnalyzer.getAnalysis(analysisId);
|
|
293
|
+
}
|
|
294
|
+
|
|
194
295
|
/**
|
|
195
296
|
* Sends all requests that are still valid.
|
|
196
297
|
* @returns one of:
|
|
@@ -198,10 +299,11 @@ export class SequencerPublisher {
|
|
|
198
299
|
* - a receipt and errorMsg if it failed on L1
|
|
199
300
|
* - undefined if no valid requests are found OR the tx failed to send.
|
|
200
301
|
*/
|
|
302
|
+
@trackSpan('SequencerPublisher.sendRequests')
|
|
201
303
|
public async sendRequests() {
|
|
202
304
|
const requestsToProcess = [...this.requests];
|
|
203
305
|
this.requests = [];
|
|
204
|
-
if (this.interrupted) {
|
|
306
|
+
if (this.interrupted || requestsToProcess.length === 0) {
|
|
205
307
|
return undefined;
|
|
206
308
|
}
|
|
207
309
|
const currentL2Slot = this.getCurrentL2Slot();
|
|
@@ -315,13 +417,15 @@ export class SequencerPublisher {
|
|
|
315
417
|
public canProposeAtNextEthBlock(
|
|
316
418
|
tipArchive: Fr,
|
|
317
419
|
msgSender: EthAddress,
|
|
318
|
-
opts: {
|
|
420
|
+
opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
319
421
|
) {
|
|
320
422
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
321
423
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
322
424
|
|
|
323
425
|
return this.rollupContract
|
|
324
|
-
.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), this.ethereumSlotDuration,
|
|
426
|
+
.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
|
|
427
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
428
|
+
})
|
|
325
429
|
.catch(err => {
|
|
326
430
|
if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
|
|
327
431
|
this.log.warn(`Failed canProposeAtTime check with ${ignoredErrors.find(e => err.message.includes(e))}`, {
|
|
@@ -339,7 +443,11 @@ export class SequencerPublisher {
|
|
|
339
443
|
* It will throw if the block header is invalid.
|
|
340
444
|
* @param header - The block header to validate
|
|
341
445
|
*/
|
|
342
|
-
|
|
446
|
+
@trackSpan('SequencerPublisher.validateBlockHeader')
|
|
447
|
+
public async validateBlockHeader(
|
|
448
|
+
header: CheckpointHeader,
|
|
449
|
+
opts?: { forcePendingCheckpointNumber: CheckpointNumber | undefined },
|
|
450
|
+
): Promise<void> {
|
|
343
451
|
const flags = { ignoreDA: true, ignoreSignatures: true };
|
|
344
452
|
|
|
345
453
|
const args = [
|
|
@@ -348,15 +456,27 @@ export class SequencerPublisher {
|
|
|
348
456
|
[], // no signers
|
|
349
457
|
Signature.empty().toViemSignature(),
|
|
350
458
|
`0x${'0'.repeat(64)}`, // 32 empty bytes
|
|
351
|
-
header.
|
|
459
|
+
header.blobsHash.toString(),
|
|
352
460
|
flags,
|
|
353
461
|
] as const;
|
|
354
462
|
|
|
355
463
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
464
|
+
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
465
|
+
opts?.forcePendingCheckpointNumber,
|
|
466
|
+
);
|
|
467
|
+
let balance = 0n;
|
|
468
|
+
if (this.config.fishermanMode) {
|
|
469
|
+
// In fisherman mode, we can't know where the proposer is publishing from
|
|
470
|
+
// so we just add sufficient balance to the multicall3 address
|
|
471
|
+
balance = 10n * WEI_CONST * WEI_CONST; // 10 ETH
|
|
472
|
+
} else {
|
|
473
|
+
balance = await this.l1TxUtils.getSenderBalance();
|
|
474
|
+
}
|
|
475
|
+
stateOverrides.push({
|
|
476
|
+
address: MULTI_CALL_3_ADDRESS,
|
|
477
|
+
balance,
|
|
478
|
+
});
|
|
356
479
|
|
|
357
|
-
// use sender balance to simulate
|
|
358
|
-
const balance = await this.l1TxUtils.getSenderBalance();
|
|
359
|
-
this.log.debug(`Simulating validateHeader with balance: ${balance}`);
|
|
360
480
|
await this.l1TxUtils.simulate(
|
|
361
481
|
{
|
|
362
482
|
to: this.rollupContract.address,
|
|
@@ -364,86 +484,96 @@ export class SequencerPublisher {
|
|
|
364
484
|
from: MULTI_CALL_3_ADDRESS,
|
|
365
485
|
},
|
|
366
486
|
{ time: ts + 1n },
|
|
367
|
-
|
|
368
|
-
{ address: MULTI_CALL_3_ADDRESS, balance },
|
|
369
|
-
...(await this.rollupContract.makePendingBlockNumberOverride(opts?.forcePendingBlockNumber)),
|
|
370
|
-
],
|
|
487
|
+
stateOverrides,
|
|
371
488
|
);
|
|
372
489
|
this.log.debug(`Simulated validateHeader`);
|
|
373
490
|
}
|
|
374
491
|
|
|
375
492
|
/**
|
|
376
|
-
* Simulate making a call to invalidate a
|
|
377
|
-
* @param
|
|
493
|
+
* Simulate making a call to invalidate a checkpoint with invalid attestations. Returns undefined if no need to invalidate.
|
|
494
|
+
* @param validationResult - The validation result indicating which checkpoint to invalidate (as returned by the archiver)
|
|
378
495
|
*/
|
|
379
|
-
public async
|
|
380
|
-
validationResult:
|
|
381
|
-
): Promise<
|
|
496
|
+
public async simulateInvalidateCheckpoint(
|
|
497
|
+
validationResult: ValidateCheckpointResult,
|
|
498
|
+
): Promise<InvalidateCheckpointRequest | undefined> {
|
|
382
499
|
if (validationResult.valid) {
|
|
383
500
|
return undefined;
|
|
384
501
|
}
|
|
385
502
|
|
|
386
|
-
const { reason,
|
|
387
|
-
const
|
|
388
|
-
const logData = { ...
|
|
503
|
+
const { reason, checkpoint } = validationResult;
|
|
504
|
+
const checkpointNumber = checkpoint.checkpointNumber;
|
|
505
|
+
const logData = { ...checkpoint, reason };
|
|
389
506
|
|
|
390
|
-
const
|
|
391
|
-
if (
|
|
507
|
+
const currentCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
508
|
+
if (currentCheckpointNumber < checkpointNumber) {
|
|
392
509
|
this.log.verbose(
|
|
393
|
-
`Skipping
|
|
394
|
-
{
|
|
510
|
+
`Skipping checkpoint ${checkpointNumber} invalidation since it has already been removed from the pending chain`,
|
|
511
|
+
{ currentCheckpointNumber, ...logData },
|
|
395
512
|
);
|
|
396
513
|
return undefined;
|
|
397
514
|
}
|
|
398
515
|
|
|
399
|
-
const request = this.
|
|
400
|
-
this.log.debug(`Simulating invalidate
|
|
516
|
+
const request = this.buildInvalidateCheckpointRequest(validationResult);
|
|
517
|
+
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
|
|
401
518
|
|
|
402
519
|
try {
|
|
403
520
|
const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, ErrorsAbi);
|
|
404
|
-
this.log.verbose(`Simulation for invalidate
|
|
521
|
+
this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} succeeded`, {
|
|
522
|
+
...logData,
|
|
523
|
+
request,
|
|
524
|
+
gasUsed,
|
|
525
|
+
});
|
|
405
526
|
|
|
406
|
-
return {
|
|
527
|
+
return {
|
|
528
|
+
request,
|
|
529
|
+
gasUsed,
|
|
530
|
+
checkpointNumber,
|
|
531
|
+
forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
|
|
532
|
+
reason,
|
|
533
|
+
};
|
|
407
534
|
} catch (err) {
|
|
408
535
|
const viemError = formatViemError(err);
|
|
409
536
|
|
|
410
|
-
// If the error is due to the
|
|
411
|
-
// we can safely ignore it and return undefined so we go ahead with
|
|
537
|
+
// If the error is due to the checkpoint not being in the pending chain, and it was indeed removed by someone else,
|
|
538
|
+
// we can safely ignore it and return undefined so we go ahead with checkpoint building.
|
|
412
539
|
if (viemError.message?.includes('Rollup__BlockNotInPendingChain')) {
|
|
413
540
|
this.log.verbose(
|
|
414
|
-
`Simulation for invalidate
|
|
541
|
+
`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
|
|
415
542
|
{ ...logData, request, error: viemError.message },
|
|
416
543
|
);
|
|
417
|
-
const
|
|
418
|
-
if (
|
|
419
|
-
this.log.verbose(`
|
|
544
|
+
const latestPendingCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
545
|
+
if (latestPendingCheckpointNumber < checkpointNumber) {
|
|
546
|
+
this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, { ...logData });
|
|
420
547
|
return undefined;
|
|
421
548
|
} else {
|
|
422
549
|
this.log.error(
|
|
423
|
-
`Simulation for invalidate ${
|
|
550
|
+
`Simulation for invalidate checkpoint ${checkpointNumber} failed and it is still in pending chain`,
|
|
424
551
|
viemError,
|
|
425
552
|
logData,
|
|
426
553
|
);
|
|
427
|
-
throw new Error(
|
|
428
|
-
|
|
429
|
-
|
|
554
|
+
throw new Error(
|
|
555
|
+
`Failed to simulate invalidate checkpoint ${checkpointNumber} while it is still in pending chain`,
|
|
556
|
+
{
|
|
557
|
+
cause: viemError,
|
|
558
|
+
},
|
|
559
|
+
);
|
|
430
560
|
}
|
|
431
561
|
}
|
|
432
562
|
|
|
433
|
-
// Otherwise, throw. We cannot build the next
|
|
434
|
-
this.log.error(`Simulation for invalidate
|
|
435
|
-
throw new Error(`Failed to simulate invalidate
|
|
563
|
+
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
564
|
+
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
|
|
565
|
+
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
|
|
436
566
|
}
|
|
437
567
|
}
|
|
438
568
|
|
|
439
|
-
private
|
|
569
|
+
private buildInvalidateCheckpointRequest(validationResult: ValidateCheckpointResult) {
|
|
440
570
|
if (validationResult.valid) {
|
|
441
|
-
throw new Error('Cannot invalidate a valid
|
|
571
|
+
throw new Error('Cannot invalidate a valid checkpoint');
|
|
442
572
|
}
|
|
443
573
|
|
|
444
|
-
const {
|
|
445
|
-
const logData = { ...
|
|
446
|
-
this.log.debug(`
|
|
574
|
+
const { checkpoint, committee, reason } = validationResult;
|
|
575
|
+
const logData = { ...checkpoint, reason };
|
|
576
|
+
this.log.debug(`Building invalidate checkpoint ${checkpoint.checkpointNumber} request`, logData);
|
|
447
577
|
|
|
448
578
|
const attestationsAndSigners = new CommitteeAttestationsAndSigners(
|
|
449
579
|
validationResult.attestations,
|
|
@@ -451,14 +581,14 @@ export class SequencerPublisher {
|
|
|
451
581
|
|
|
452
582
|
if (reason === 'invalid-attestation') {
|
|
453
583
|
return this.rollupContract.buildInvalidateBadAttestationRequest(
|
|
454
|
-
|
|
584
|
+
checkpoint.checkpointNumber,
|
|
455
585
|
attestationsAndSigners,
|
|
456
586
|
committee,
|
|
457
587
|
validationResult.invalidIndex,
|
|
458
588
|
);
|
|
459
589
|
} else if (reason === 'insufficient-attestations') {
|
|
460
590
|
return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
|
|
461
|
-
|
|
591
|
+
checkpoint.checkpointNumber,
|
|
462
592
|
attestationsAndSigners,
|
|
463
593
|
committee,
|
|
464
594
|
);
|
|
@@ -468,46 +598,39 @@ export class SequencerPublisher {
|
|
|
468
598
|
}
|
|
469
599
|
}
|
|
470
600
|
|
|
471
|
-
/**
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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,
|
|
601
|
+
/** Simulates `propose` to make sure that the checkpoint is valid for submission */
|
|
602
|
+
@trackSpan('SequencerPublisher.validateCheckpointForSubmission')
|
|
603
|
+
public async validateCheckpointForSubmission(
|
|
604
|
+
checkpoint: Checkpoint,
|
|
482
605
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
483
606
|
attestationsAndSignersSignature: Signature,
|
|
484
|
-
options: {
|
|
607
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
485
608
|
): Promise<bigint> {
|
|
486
609
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
487
610
|
|
|
611
|
+
// TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
|
|
488
612
|
// If we have no attestations, we still need to provide the empty attestations
|
|
489
613
|
// 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 =
|
|
614
|
+
// const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
615
|
+
// if (ignoreSignatures) {
|
|
616
|
+
// const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
|
|
617
|
+
// if (!committee) {
|
|
618
|
+
// this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
619
|
+
// throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
620
|
+
// }
|
|
621
|
+
// attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
622
|
+
// CommitteeAttestation.fromAddress(committeeMember),
|
|
623
|
+
// );
|
|
624
|
+
// }
|
|
625
|
+
|
|
626
|
+
const blobFields = checkpoint.toBlobFields();
|
|
503
627
|
const blobs = getBlobsPerL1Block(blobFields);
|
|
504
628
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
505
629
|
|
|
506
630
|
const args = [
|
|
507
631
|
{
|
|
508
|
-
header:
|
|
509
|
-
archive: toHex(
|
|
510
|
-
stateReference: block.header.state.toViem(),
|
|
632
|
+
header: checkpoint.header.toViem(),
|
|
633
|
+
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
511
634
|
oracleInput: {
|
|
512
635
|
feeAssetPriceModifier: 0n,
|
|
513
636
|
},
|
|
@@ -523,7 +646,7 @@ export class SequencerPublisher {
|
|
|
523
646
|
}
|
|
524
647
|
|
|
525
648
|
private async enqueueCastSignalHelper(
|
|
526
|
-
slotNumber:
|
|
649
|
+
slotNumber: SlotNumber,
|
|
527
650
|
timestamp: bigint,
|
|
528
651
|
signalType: GovernanceSignalAction,
|
|
529
652
|
payload: EthAddress,
|
|
@@ -545,10 +668,19 @@ export class SequencerPublisher {
|
|
|
545
668
|
const round = await base.computeRound(slotNumber);
|
|
546
669
|
const roundInfo = await base.getRoundInfo(this.rollupContract.address, round);
|
|
547
670
|
|
|
671
|
+
if (roundInfo.quorumReached) {
|
|
672
|
+
return false;
|
|
673
|
+
}
|
|
674
|
+
|
|
548
675
|
if (roundInfo.lastSignalSlot >= slotNumber) {
|
|
549
676
|
return false;
|
|
550
677
|
}
|
|
551
678
|
|
|
679
|
+
if (await this.isPayloadEmpty(payload)) {
|
|
680
|
+
this.log.warn(`Skipping vote cast for payload with empty code`);
|
|
681
|
+
return false;
|
|
682
|
+
}
|
|
683
|
+
|
|
552
684
|
const cachedLastVote = this.lastActions[signalType];
|
|
553
685
|
this.lastActions[signalType] = slotNumber;
|
|
554
686
|
const action = signalType;
|
|
@@ -591,14 +723,14 @@ export class SequencerPublisher {
|
|
|
591
723
|
const logData = { ...result, slotNumber, round, payload: payload.toString() };
|
|
592
724
|
if (!success) {
|
|
593
725
|
this.log.error(
|
|
594
|
-
`Signaling in
|
|
726
|
+
`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} failed`,
|
|
595
727
|
logData,
|
|
596
728
|
);
|
|
597
729
|
this.lastActions[signalType] = cachedLastVote;
|
|
598
730
|
return false;
|
|
599
731
|
} else {
|
|
600
732
|
this.log.info(
|
|
601
|
-
`Signaling in
|
|
733
|
+
`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} succeeded`,
|
|
602
734
|
logData,
|
|
603
735
|
);
|
|
604
736
|
return true;
|
|
@@ -608,6 +740,17 @@ export class SequencerPublisher {
|
|
|
608
740
|
return true;
|
|
609
741
|
}
|
|
610
742
|
|
|
743
|
+
private async isPayloadEmpty(payload: EthAddress): Promise<boolean> {
|
|
744
|
+
const key = payload.toString();
|
|
745
|
+
const cached = this.isPayloadEmptyCache.get(key);
|
|
746
|
+
if (cached) {
|
|
747
|
+
return cached;
|
|
748
|
+
}
|
|
749
|
+
const isEmpty = !(await this.l1TxUtils.getCode(payload));
|
|
750
|
+
this.isPayloadEmptyCache.set(key, isEmpty);
|
|
751
|
+
return isEmpty;
|
|
752
|
+
}
|
|
753
|
+
|
|
611
754
|
/**
|
|
612
755
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
613
756
|
* @param slotNumber - The slot number to cast a signal for.
|
|
@@ -616,7 +759,7 @@ export class SequencerPublisher {
|
|
|
616
759
|
*/
|
|
617
760
|
public enqueueGovernanceCastSignal(
|
|
618
761
|
governancePayload: EthAddress,
|
|
619
|
-
slotNumber:
|
|
762
|
+
slotNumber: SlotNumber,
|
|
620
763
|
timestamp: bigint,
|
|
621
764
|
signerAddress: EthAddress,
|
|
622
765
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
@@ -635,7 +778,7 @@ export class SequencerPublisher {
|
|
|
635
778
|
/** Enqueues all slashing actions as returned by the slasher client. */
|
|
636
779
|
public async enqueueSlashingActions(
|
|
637
780
|
actions: ProposerSlashAction[],
|
|
638
|
-
slotNumber:
|
|
781
|
+
slotNumber: SlotNumber,
|
|
639
782
|
timestamp: bigint,
|
|
640
783
|
signerAddress: EthAddress,
|
|
641
784
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
@@ -755,28 +898,21 @@ export class SequencerPublisher {
|
|
|
755
898
|
return true;
|
|
756
899
|
}
|
|
757
900
|
|
|
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,
|
|
901
|
+
/** Simulates and enqueues a proposal for a checkpoint on L1 */
|
|
902
|
+
public async enqueueProposeCheckpoint(
|
|
903
|
+
checkpoint: Checkpoint,
|
|
766
904
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
767
905
|
attestationsAndSignersSignature: Signature,
|
|
768
|
-
opts: { txTimeoutAt?: Date;
|
|
769
|
-
): Promise<
|
|
770
|
-
const checkpointHeader =
|
|
906
|
+
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
907
|
+
): Promise<void> {
|
|
908
|
+
const checkpointHeader = checkpoint.header;
|
|
771
909
|
|
|
772
|
-
const blobFields =
|
|
910
|
+
const blobFields = checkpoint.toBlobFields();
|
|
773
911
|
const blobs = getBlobsPerL1Block(blobFields);
|
|
774
912
|
|
|
775
913
|
const proposeTxArgs = {
|
|
776
914
|
header: checkpointHeader,
|
|
777
|
-
archive:
|
|
778
|
-
stateReference: block.header.state,
|
|
779
|
-
body: block.body.toBuffer(),
|
|
915
|
+
archive: checkpoint.archive.root.toBuffer(),
|
|
780
916
|
blobs,
|
|
781
917
|
attestationsAndSigners,
|
|
782
918
|
attestationsAndSignersSignature,
|
|
@@ -790,22 +926,29 @@ export class SequencerPublisher {
|
|
|
790
926
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
791
927
|
// make time consistency checks break.
|
|
792
928
|
// 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.
|
|
929
|
+
ts = await this.validateCheckpointForSubmission(
|
|
930
|
+
checkpoint,
|
|
931
|
+
attestationsAndSigners,
|
|
932
|
+
attestationsAndSignersSignature,
|
|
933
|
+
opts,
|
|
934
|
+
);
|
|
794
935
|
} catch (err: any) {
|
|
795
|
-
this.log.error(`
|
|
796
|
-
...
|
|
797
|
-
slotNumber:
|
|
798
|
-
|
|
936
|
+
this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
937
|
+
...checkpoint.getStats(),
|
|
938
|
+
slotNumber: checkpoint.header.slotNumber,
|
|
939
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
799
940
|
});
|
|
800
941
|
throw err;
|
|
801
942
|
}
|
|
802
943
|
|
|
803
|
-
this.log.verbose(`Enqueuing
|
|
804
|
-
await this.addProposeTx(
|
|
805
|
-
return true;
|
|
944
|
+
this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
|
|
945
|
+
await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
|
|
806
946
|
}
|
|
807
947
|
|
|
808
|
-
public
|
|
948
|
+
public enqueueInvalidateCheckpoint(
|
|
949
|
+
request: InvalidateCheckpointRequest | undefined,
|
|
950
|
+
opts: { txTimeoutAt?: Date } = {},
|
|
951
|
+
) {
|
|
809
952
|
if (!request) {
|
|
810
953
|
return;
|
|
811
954
|
}
|
|
@@ -813,24 +956,24 @@ export class SequencerPublisher {
|
|
|
813
956
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
814
957
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(request.gasUsed) * 64) / 63)));
|
|
815
958
|
|
|
816
|
-
const { gasUsed,
|
|
817
|
-
const logData = { gasUsed,
|
|
818
|
-
this.log.verbose(`Enqueuing invalidate
|
|
959
|
+
const { gasUsed, checkpointNumber } = request;
|
|
960
|
+
const logData = { gasUsed, checkpointNumber, gasLimit, opts };
|
|
961
|
+
this.log.verbose(`Enqueuing invalidate checkpoint request`, logData);
|
|
819
962
|
this.addRequest({
|
|
820
963
|
action: `invalidate-by-${request.reason}`,
|
|
821
964
|
request: request.request,
|
|
822
965
|
gasConfig: { gasLimit, txTimeoutAt: opts.txTimeoutAt },
|
|
823
|
-
lastValidL2Slot: this.getCurrentL2Slot() +
|
|
966
|
+
lastValidL2Slot: SlotNumber(this.getCurrentL2Slot() + 2),
|
|
824
967
|
checkSuccess: (_req, result) => {
|
|
825
968
|
const success =
|
|
826
969
|
result &&
|
|
827
970
|
result.receipt &&
|
|
828
971
|
result.receipt.status === 'success' &&
|
|
829
|
-
tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, '
|
|
972
|
+
tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointInvalidated');
|
|
830
973
|
if (!success) {
|
|
831
|
-
this.log.warn(`Invalidate
|
|
974
|
+
this.log.warn(`Invalidate checkpoint ${request.checkpointNumber} failed`, { ...result, ...logData });
|
|
832
975
|
} else {
|
|
833
|
-
this.log.info(`Invalidate
|
|
976
|
+
this.log.info(`Invalidate checkpoint ${request.checkpointNumber} succeeded`, { ...result, ...logData });
|
|
834
977
|
}
|
|
835
978
|
return !!success;
|
|
836
979
|
},
|
|
@@ -841,7 +984,7 @@ export class SequencerPublisher {
|
|
|
841
984
|
action: Action,
|
|
842
985
|
request: L1TxRequest,
|
|
843
986
|
checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
|
|
844
|
-
slotNumber:
|
|
987
|
+
slotNumber: SlotNumber,
|
|
845
988
|
timestamp: bigint,
|
|
846
989
|
) {
|
|
847
990
|
const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
|
|
@@ -909,41 +1052,50 @@ export class SequencerPublisher {
|
|
|
909
1052
|
private async prepareProposeTx(
|
|
910
1053
|
encodedData: L1ProcessArgs,
|
|
911
1054
|
timestamp: bigint,
|
|
912
|
-
options: {
|
|
1055
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
913
1056
|
) {
|
|
914
1057
|
const kzg = Blob.getViemKzgInstance();
|
|
915
1058
|
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
916
1059
|
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
1060
|
|
|
1061
|
+
// Get blob evaluation gas
|
|
1062
|
+
let blobEvaluationGas: bigint;
|
|
1063
|
+
if (this.config.fishermanMode) {
|
|
1064
|
+
// In fisherman mode, we can't estimate blob gas because estimateGas doesn't support state overrides
|
|
1065
|
+
// Use a fixed estimate.
|
|
1066
|
+
blobEvaluationGas = BigInt(encodedData.blobs.length) * 21_000n;
|
|
1067
|
+
this.log.debug(`Using fixed blob evaluation gas estimate in fisherman mode: ${blobEvaluationGas}`);
|
|
1068
|
+
} else {
|
|
1069
|
+
// Normal mode - use estimateGas with blob inputs
|
|
1070
|
+
blobEvaluationGas = await this.l1TxUtils
|
|
1071
|
+
.estimateGas(
|
|
1072
|
+
this.getSenderAddress().toString(),
|
|
1073
|
+
{
|
|
1074
|
+
to: this.rollupContract.address,
|
|
1075
|
+
data: encodeFunctionData({
|
|
1076
|
+
abi: RollupAbi,
|
|
1077
|
+
functionName: 'validateBlobs',
|
|
1078
|
+
args: [blobInput],
|
|
1079
|
+
}),
|
|
1080
|
+
},
|
|
1081
|
+
{},
|
|
1082
|
+
{
|
|
1083
|
+
blobs: encodedData.blobs.map(b => b.data),
|
|
1084
|
+
kzg,
|
|
1085
|
+
},
|
|
1086
|
+
)
|
|
1087
|
+
.catch(err => {
|
|
1088
|
+
const { message, metaMessages } = formatViemError(err);
|
|
1089
|
+
this.log.error(`Failed to validate blobs`, message, { metaMessages });
|
|
1090
|
+
throw new Error('Failed to validate blobs');
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
940
1093
|
const signers = encodedData.attestationsAndSigners.getSigners().map(signer => signer.toString());
|
|
941
1094
|
|
|
942
1095
|
const args = [
|
|
943
1096
|
{
|
|
944
1097
|
header: encodedData.header.toViem(),
|
|
945
1098
|
archive: toHex(encodedData.archive),
|
|
946
|
-
stateReference: encodedData.stateReference.toViem(),
|
|
947
1099
|
oracleInput: {
|
|
948
1100
|
// We are currently not modifying these. See #9963
|
|
949
1101
|
feeAssetPriceModifier: 0n,
|
|
@@ -971,7 +1123,6 @@ export class SequencerPublisher {
|
|
|
971
1123
|
{
|
|
972
1124
|
readonly header: ViemHeader;
|
|
973
1125
|
readonly archive: `0x${string}`;
|
|
974
|
-
readonly stateReference: ViemStateReference;
|
|
975
1126
|
readonly oracleInput: {
|
|
976
1127
|
readonly feeAssetPriceModifier: 0n;
|
|
977
1128
|
};
|
|
@@ -982,7 +1133,7 @@ export class SequencerPublisher {
|
|
|
982
1133
|
`0x${string}`,
|
|
983
1134
|
],
|
|
984
1135
|
timestamp: bigint,
|
|
985
|
-
options: {
|
|
1136
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
986
1137
|
) {
|
|
987
1138
|
const rollupData = encodeFunctionData({
|
|
988
1139
|
abi: RollupAbi,
|
|
@@ -990,19 +1141,38 @@ export class SequencerPublisher {
|
|
|
990
1141
|
args,
|
|
991
1142
|
});
|
|
992
1143
|
|
|
993
|
-
// override the pending
|
|
994
|
-
const
|
|
995
|
-
options.
|
|
996
|
-
? await this.rollupContract.
|
|
1144
|
+
// override the pending checkpoint number if requested
|
|
1145
|
+
const forcePendingCheckpointNumberStateDiff = (
|
|
1146
|
+
options.forcePendingCheckpointNumber !== undefined
|
|
1147
|
+
? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber)
|
|
997
1148
|
: []
|
|
998
1149
|
).flatMap(override => override.stateDiff ?? []);
|
|
999
1150
|
|
|
1151
|
+
const stateOverrides: StateOverride = [
|
|
1152
|
+
{
|
|
1153
|
+
address: this.rollupContract.address,
|
|
1154
|
+
// @note we override checkBlob to false since blobs are not part simulate()
|
|
1155
|
+
stateDiff: [
|
|
1156
|
+
{ slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
|
|
1157
|
+
...forcePendingCheckpointNumberStateDiff,
|
|
1158
|
+
],
|
|
1159
|
+
},
|
|
1160
|
+
];
|
|
1161
|
+
// In fisherman mode, simulate as the proposer but with sufficient balance
|
|
1162
|
+
if (this.proposerAddressForSimulation) {
|
|
1163
|
+
stateOverrides.push({
|
|
1164
|
+
address: this.proposerAddressForSimulation.toString(),
|
|
1165
|
+
balance: 10n * WEI_CONST * WEI_CONST, // 10 ETH
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1000
1169
|
const simulationResult = await this.l1TxUtils
|
|
1001
1170
|
.simulate(
|
|
1002
1171
|
{
|
|
1003
1172
|
to: this.rollupContract.address,
|
|
1004
1173
|
data: rollupData,
|
|
1005
1174
|
gas: SequencerPublisher.PROPOSE_GAS_GUESS,
|
|
1175
|
+
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1006
1176
|
},
|
|
1007
1177
|
{
|
|
1008
1178
|
// @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 +1180,7 @@ export class SequencerPublisher {
|
|
|
1010
1180
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1011
1181
|
gasLimit: SequencerPublisher.PROPOSE_GAS_GUESS * 2n,
|
|
1012
1182
|
},
|
|
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
|
-
],
|
|
1183
|
+
stateOverrides,
|
|
1023
1184
|
RollupAbi,
|
|
1024
1185
|
{
|
|
1025
1186
|
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
@@ -1027,7 +1188,17 @@ export class SequencerPublisher {
|
|
|
1027
1188
|
},
|
|
1028
1189
|
)
|
|
1029
1190
|
.catch(err => {
|
|
1030
|
-
|
|
1191
|
+
// In fisherman mode, we expect ValidatorSelection__MissingProposerSignature since fisherman doesn't have proposer signature
|
|
1192
|
+
const viemError = formatViemError(err);
|
|
1193
|
+
if (this.config.fishermanMode && viemError.message?.includes('ValidatorSelection__MissingProposerSignature')) {
|
|
1194
|
+
this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
|
|
1195
|
+
// Return a minimal simulation result with the fallback gas estimate
|
|
1196
|
+
return {
|
|
1197
|
+
gasUsed: SequencerPublisher.PROPOSE_GAS_GUESS,
|
|
1198
|
+
logs: [],
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1031
1202
|
throw err;
|
|
1032
1203
|
});
|
|
1033
1204
|
|
|
@@ -1035,11 +1206,12 @@ export class SequencerPublisher {
|
|
|
1035
1206
|
}
|
|
1036
1207
|
|
|
1037
1208
|
private async addProposeTx(
|
|
1038
|
-
|
|
1209
|
+
checkpoint: Checkpoint,
|
|
1039
1210
|
encodedData: L1ProcessArgs,
|
|
1040
|
-
opts: { txTimeoutAt?: Date;
|
|
1211
|
+
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
1041
1212
|
timestamp: bigint,
|
|
1042
1213
|
): Promise<void> {
|
|
1214
|
+
const slot = checkpoint.header.slotNumber;
|
|
1043
1215
|
const timer = new Timer();
|
|
1044
1216
|
const kzg = Blob.getViemKzgInstance();
|
|
1045
1217
|
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
@@ -1054,11 +1226,13 @@ export class SequencerPublisher {
|
|
|
1054
1226
|
SequencerPublisher.MULTICALL_OVERHEAD_GAS_GUESS, // We issue the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
1055
1227
|
);
|
|
1056
1228
|
|
|
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
|
-
|
|
1229
|
+
// Send the blobs to the blob client preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
|
|
1230
|
+
// tx fails but it does get mined. We make sure that the blobs are sent to the blob client regardless of the tx outcome.
|
|
1231
|
+
void Promise.resolve().then(() =>
|
|
1232
|
+
this.blobClient.sendBlobsToFilestore(encodedData.blobs).catch(_err => {
|
|
1233
|
+
this.log.error('Failed to send blobs to blob client');
|
|
1234
|
+
}),
|
|
1235
|
+
);
|
|
1062
1236
|
|
|
1063
1237
|
return this.addRequest({
|
|
1064
1238
|
action: 'propose',
|
|
@@ -1066,7 +1240,7 @@ export class SequencerPublisher {
|
|
|
1066
1240
|
to: this.rollupContract.address,
|
|
1067
1241
|
data: rollupData,
|
|
1068
1242
|
},
|
|
1069
|
-
lastValidL2Slot:
|
|
1243
|
+
lastValidL2Slot: checkpoint.header.slotNumber,
|
|
1070
1244
|
gasConfig: { ...opts, gasLimit },
|
|
1071
1245
|
blobConfig: {
|
|
1072
1246
|
blobs: encodedData.blobs.map(b => b.data),
|
|
@@ -1080,12 +1254,13 @@ export class SequencerPublisher {
|
|
|
1080
1254
|
const success =
|
|
1081
1255
|
receipt &&
|
|
1082
1256
|
receipt.status === 'success' &&
|
|
1083
|
-
tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, '
|
|
1257
|
+
tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointProposed');
|
|
1258
|
+
|
|
1084
1259
|
if (success) {
|
|
1085
1260
|
const endBlock = receipt.blockNumber;
|
|
1086
1261
|
const inclusionBlocks = Number(endBlock - startBlock);
|
|
1087
1262
|
const { calldataGas, calldataSize, sender } = stats!;
|
|
1088
|
-
const publishStats:
|
|
1263
|
+
const publishStats: L1PublishCheckpointStats = {
|
|
1089
1264
|
gasPrice: receipt.effectiveGasPrice,
|
|
1090
1265
|
gasUsed: receipt.gasUsed,
|
|
1091
1266
|
blobGasUsed: receipt.blobGasUsed ?? 0n,
|
|
@@ -1094,23 +1269,26 @@ export class SequencerPublisher {
|
|
|
1094
1269
|
calldataGas,
|
|
1095
1270
|
calldataSize,
|
|
1096
1271
|
sender,
|
|
1097
|
-
...
|
|
1272
|
+
...checkpoint.getStats(),
|
|
1098
1273
|
eventName: 'rollup-published-to-l1',
|
|
1099
1274
|
blobCount: encodedData.blobs.length,
|
|
1100
1275
|
inclusionBlocks,
|
|
1101
1276
|
};
|
|
1102
|
-
this.log.info(`Published
|
|
1277
|
+
this.log.info(`Published checkpoint ${checkpoint.number} at slot ${slot} to rollup contract`, {
|
|
1278
|
+
...stats,
|
|
1279
|
+
...checkpoint.getStats(),
|
|
1280
|
+
...pick(receipt, 'transactionHash', 'blockHash'),
|
|
1281
|
+
});
|
|
1103
1282
|
this.metrics.recordProcessBlockTx(timer.ms(), publishStats);
|
|
1104
1283
|
|
|
1105
1284
|
return true;
|
|
1106
1285
|
} else {
|
|
1107
1286
|
this.metrics.recordFailedTx('process');
|
|
1108
|
-
this.log.error(
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
});
|
|
1287
|
+
this.log.error(
|
|
1288
|
+
`Publishing checkpoint at slot ${slot} failed with ${errorMsg ?? 'no error message'}`,
|
|
1289
|
+
undefined,
|
|
1290
|
+
{ ...checkpoint.getStats(), ...receipt },
|
|
1291
|
+
);
|
|
1114
1292
|
return false;
|
|
1115
1293
|
}
|
|
1116
1294
|
},
|