@aztec/sequencer-client 0.0.1-commit.21caa21 → 0.0.1-commit.21ecf947b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/client/sequencer-client.d.ts +12 -12
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +33 -26
- package/dest/config.d.ts +12 -6
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +71 -32
- package/dest/global_variable_builder/global_builder.d.ts +22 -13
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +51 -41
- package/dest/index.d.ts +2 -3
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -2
- package/dest/publisher/config.d.ts +7 -4
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +9 -3
- package/dest/publisher/sequencer-publisher-factory.d.ts +5 -4
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +1 -1
- package/dest/publisher/sequencer-publisher-metrics.d.ts +3 -3
- package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-metrics.js +23 -86
- package/dest/publisher/sequencer-publisher.d.ts +56 -42
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +615 -133
- package/dest/sequencer/checkpoint_proposal_job.d.ts +102 -0
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_proposal_job.js +1193 -0
- package/dest/sequencer/checkpoint_voter.d.ts +35 -0
- package/dest/sequencer/checkpoint_voter.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_voter.js +109 -0
- package/dest/sequencer/config.d.ts +3 -2
- package/dest/sequencer/config.d.ts.map +1 -1
- package/dest/sequencer/events.d.ts +46 -0
- package/dest/sequencer/events.d.ts.map +1 -0
- package/dest/sequencer/events.js +1 -0
- package/dest/sequencer/index.d.ts +4 -2
- package/dest/sequencer/index.d.ts.map +1 -1
- package/dest/sequencer/index.js +3 -1
- package/dest/sequencer/metrics.d.ts +27 -3
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +188 -66
- package/dest/sequencer/sequencer.d.ts +109 -131
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +700 -606
- package/dest/sequencer/timetable.d.ts +51 -14
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +145 -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 +97 -0
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -0
- package/dest/test/mock_checkpoint_builder.js +222 -0
- package/dest/test/utils.d.ts +53 -0
- package/dest/test/utils.d.ts.map +1 -0
- package/dest/test/utils.js +104 -0
- package/package.json +32 -30
- package/src/client/sequencer-client.ts +31 -42
- package/src/config.ts +78 -36
- package/src/global_variable_builder/global_builder.ts +65 -61
- package/src/index.ts +1 -7
- package/src/publisher/config.ts +12 -9
- package/src/publisher/sequencer-publisher-factory.ts +5 -4
- package/src/publisher/sequencer-publisher-metrics.ts +19 -71
- package/src/publisher/sequencer-publisher.ts +327 -166
- package/src/sequencer/README.md +531 -0
- package/src/sequencer/checkpoint_proposal_job.ts +882 -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 +236 -76
- package/src/sequencer/sequencer.ts +445 -813
- package/src/sequencer/timetable.ts +175 -80
- 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 +320 -0
- package/src/test/utils.ts +167 -0
- package/dest/sequencer/block_builder.d.ts +0 -27
- package/dest/sequencer/block_builder.d.ts.map +0 -1
- package/dest/sequencer/block_builder.js +0 -134
- package/dest/tx_validator/nullifier_cache.d.ts +0 -14
- package/dest/tx_validator/nullifier_cache.d.ts.map +0 -1
- package/dest/tx_validator/nullifier_cache.js +0 -24
- package/dest/tx_validator/tx_validator_factory.d.ts +0 -17
- package/dest/tx_validator/tx_validator_factory.d.ts.map +0 -1
- package/dest/tx_validator/tx_validator_factory.js +0 -53
- package/src/sequencer/block_builder.ts +0 -222
- package/src/tx_validator/nullifier_cache.ts +0 -30
- package/src/tx_validator/tx_validator_factory.ts +0 -132
|
@@ -1,44 +1,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
|
-
|
|
7
|
+
FeeAssetPriceOracle,
|
|
8
8
|
type GovernanceProposerContract,
|
|
9
9
|
type IEmpireBase,
|
|
10
|
-
type L1BlobInputs,
|
|
11
|
-
type L1ContractsConfig,
|
|
12
|
-
type L1TxConfig,
|
|
13
|
-
type L1TxRequest,
|
|
14
10
|
MULTI_CALL_3_ADDRESS,
|
|
15
11
|
Multicall3,
|
|
16
12
|
RollupContract,
|
|
17
13
|
type TallySlashingProposerContract,
|
|
18
|
-
type TransactionStats,
|
|
19
14
|
type ViemCommitteeAttestations,
|
|
20
15
|
type ViemHeader,
|
|
16
|
+
} from '@aztec/ethereum/contracts';
|
|
17
|
+
import { type L1FeeAnalysisResult, L1FeeAnalyzer } from '@aztec/ethereum/l1-fee-analysis';
|
|
18
|
+
import {
|
|
19
|
+
type L1BlobInputs,
|
|
20
|
+
type L1TxConfig,
|
|
21
|
+
type L1TxRequest,
|
|
22
|
+
MAX_L1_TX_LIMIT,
|
|
23
|
+
type TransactionStats,
|
|
21
24
|
WEI_CONST,
|
|
22
|
-
|
|
23
|
-
tryExtractEvent,
|
|
24
|
-
} from '@aztec/ethereum';
|
|
25
|
+
} from '@aztec/ethereum/l1-tx-utils';
|
|
25
26
|
import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs';
|
|
27
|
+
import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
26
28
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
27
29
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
28
|
-
import { SlotNumber } from '@aztec/foundation/branded-types';
|
|
30
|
+
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
31
|
+
import { pick } from '@aztec/foundation/collection';
|
|
32
|
+
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
29
33
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
30
34
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
31
|
-
import type { Fr } from '@aztec/foundation/fields';
|
|
32
35
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
33
36
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
34
37
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
35
38
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
36
39
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
37
|
-
import {
|
|
40
|
+
import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
41
|
+
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
38
42
|
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
39
43
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
40
|
-
import type {
|
|
41
|
-
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
44
|
+
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
45
|
+
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
42
46
|
|
|
43
47
|
import { type StateOverride, type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
|
|
44
48
|
|
|
@@ -57,6 +61,8 @@ type L1ProcessArgs = {
|
|
|
57
61
|
attestationsAndSigners: CommitteeAttestationsAndSigners;
|
|
58
62
|
/** Attestations and signers signature */
|
|
59
63
|
attestationsAndSignersSignature: Signature;
|
|
64
|
+
/** The fee asset price modifier in basis points (from oracle) */
|
|
65
|
+
feeAssetPriceModifier: bigint;
|
|
60
66
|
};
|
|
61
67
|
|
|
62
68
|
export const Actions = [
|
|
@@ -78,12 +84,12 @@ type GovernanceSignalAction = Extract<Action, 'governance-signal' | 'empire-slas
|
|
|
78
84
|
// Sorting for actions such that invalidations go before proposals, and proposals go before votes
|
|
79
85
|
export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
|
|
80
86
|
|
|
81
|
-
export type
|
|
87
|
+
export type InvalidateCheckpointRequest = {
|
|
82
88
|
request: L1TxRequest;
|
|
83
89
|
reason: 'invalid-attestation' | 'insufficient-attestations';
|
|
84
90
|
gasUsed: bigint;
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
checkpointNumber: CheckpointNumber;
|
|
92
|
+
forcePendingCheckpointNumber: CheckpointNumber;
|
|
87
93
|
};
|
|
88
94
|
|
|
89
95
|
interface RequestWithExpiry {
|
|
@@ -108,17 +114,21 @@ export class SequencerPublisher {
|
|
|
108
114
|
|
|
109
115
|
protected lastActions: Partial<Record<Action, SlotNumber>> = {};
|
|
110
116
|
|
|
117
|
+
private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
|
|
118
|
+
|
|
111
119
|
protected log: Logger;
|
|
112
120
|
protected ethereumSlotDuration: bigint;
|
|
113
121
|
|
|
114
|
-
private
|
|
122
|
+
private blobClient: BlobClientInterface;
|
|
115
123
|
|
|
116
124
|
/** Address to use for simulations in fisherman mode (actual proposer's address) */
|
|
117
125
|
private proposerAddressForSimulation?: EthAddress;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
126
|
+
|
|
127
|
+
/** L1 fee analyzer for fisherman mode */
|
|
128
|
+
private l1FeeAnalyzer?: L1FeeAnalyzer;
|
|
129
|
+
|
|
130
|
+
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */
|
|
131
|
+
private feeAssetPriceOracle: FeeAssetPriceOracle;
|
|
122
132
|
|
|
123
133
|
// A CALL to a cold address is 2700 gas
|
|
124
134
|
public static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
@@ -132,13 +142,15 @@ export class SequencerPublisher {
|
|
|
132
142
|
public slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
133
143
|
public slashFactoryContract: SlashFactoryContract;
|
|
134
144
|
|
|
145
|
+
public readonly tracer: Tracer;
|
|
146
|
+
|
|
135
147
|
protected requests: RequestWithExpiry[] = [];
|
|
136
148
|
|
|
137
149
|
constructor(
|
|
138
150
|
private config: TxSenderConfig & PublisherConfig & Pick<L1ContractsConfig, 'ethereumSlotDuration'>,
|
|
139
151
|
deps: {
|
|
140
152
|
telemetry?: TelemetryClient;
|
|
141
|
-
|
|
153
|
+
blobClient: BlobClientInterface;
|
|
142
154
|
l1TxUtils: L1TxUtilsWithBlobs;
|
|
143
155
|
rollupContract: RollupContract;
|
|
144
156
|
slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
@@ -156,11 +168,11 @@ export class SequencerPublisher {
|
|
|
156
168
|
this.epochCache = deps.epochCache;
|
|
157
169
|
this.lastActions = deps.lastActions;
|
|
158
170
|
|
|
159
|
-
this.
|
|
160
|
-
deps.blobSinkClient ?? createBlobSinkClient(config, { logger: createLogger('sequencer:blob-sink:client') });
|
|
171
|
+
this.blobClient = deps.blobClient;
|
|
161
172
|
|
|
162
173
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
163
174
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
175
|
+
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
164
176
|
this.l1TxUtils = deps.l1TxUtils;
|
|
165
177
|
|
|
166
178
|
this.rollupContract = deps.rollupContract;
|
|
@@ -174,16 +186,47 @@ export class SequencerPublisher {
|
|
|
174
186
|
this.slashingProposerContract = newSlashingProposer;
|
|
175
187
|
});
|
|
176
188
|
this.slashFactoryContract = deps.slashFactoryContract;
|
|
189
|
+
|
|
190
|
+
// Initialize L1 fee analyzer for fisherman mode
|
|
191
|
+
if (config.fishermanMode) {
|
|
192
|
+
this.l1FeeAnalyzer = new L1FeeAnalyzer(
|
|
193
|
+
this.l1TxUtils.client,
|
|
194
|
+
deps.dateProvider,
|
|
195
|
+
createLogger('sequencer:publisher:fee-analyzer'),
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Initialize fee asset price oracle
|
|
200
|
+
this.feeAssetPriceOracle = new FeeAssetPriceOracle(
|
|
201
|
+
this.l1TxUtils.client,
|
|
202
|
+
this.rollupContract,
|
|
203
|
+
createLogger('sequencer:publisher:price-oracle'),
|
|
204
|
+
);
|
|
177
205
|
}
|
|
178
206
|
|
|
179
207
|
public getRollupContract(): RollupContract {
|
|
180
208
|
return this.rollupContract;
|
|
181
209
|
}
|
|
182
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Gets the fee asset price modifier from the oracle.
|
|
213
|
+
* Returns 0n if the oracle query fails.
|
|
214
|
+
*/
|
|
215
|
+
public getFeeAssetPriceModifier(): Promise<bigint> {
|
|
216
|
+
return this.feeAssetPriceOracle.computePriceModifier();
|
|
217
|
+
}
|
|
218
|
+
|
|
183
219
|
public getSenderAddress() {
|
|
184
220
|
return this.l1TxUtils.getSenderAddress();
|
|
185
221
|
}
|
|
186
222
|
|
|
223
|
+
/**
|
|
224
|
+
* Gets the L1 fee analyzer instance (only available in fisherman mode)
|
|
225
|
+
*/
|
|
226
|
+
public getL1FeeAnalyzer(): L1FeeAnalyzer | undefined {
|
|
227
|
+
return this.l1FeeAnalyzer;
|
|
228
|
+
}
|
|
229
|
+
|
|
187
230
|
/**
|
|
188
231
|
* Sets the proposer address to use for simulations in fisherman mode.
|
|
189
232
|
* @param proposerAddress - The actual proposer's address to use for balance lookups in simulations
|
|
@@ -211,6 +254,62 @@ export class SequencerPublisher {
|
|
|
211
254
|
}
|
|
212
255
|
}
|
|
213
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Analyzes L1 fees for the pending requests without sending them.
|
|
259
|
+
* This is used in fisherman mode to validate fee calculations.
|
|
260
|
+
* @param l2SlotNumber - The L2 slot number for this analysis
|
|
261
|
+
* @param onComplete - Optional callback to invoke when analysis completes (after block is mined)
|
|
262
|
+
* @returns The analysis result (incomplete until block mines), or undefined if no requests
|
|
263
|
+
*/
|
|
264
|
+
public async analyzeL1Fees(
|
|
265
|
+
l2SlotNumber: SlotNumber,
|
|
266
|
+
onComplete?: (analysis: L1FeeAnalysisResult) => void,
|
|
267
|
+
): Promise<L1FeeAnalysisResult | undefined> {
|
|
268
|
+
if (!this.l1FeeAnalyzer) {
|
|
269
|
+
this.log.warn('L1 fee analyzer not available (not in fisherman mode)');
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const requestsToAnalyze = [...this.requests];
|
|
274
|
+
if (requestsToAnalyze.length === 0) {
|
|
275
|
+
this.log.debug('No requests to analyze for L1 fees');
|
|
276
|
+
return undefined;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Extract blob config from requests (if any)
|
|
280
|
+
const blobConfigs = requestsToAnalyze.filter(request => request.blobConfig).map(request => request.blobConfig);
|
|
281
|
+
const blobConfig = blobConfigs[0];
|
|
282
|
+
|
|
283
|
+
// Get gas configs
|
|
284
|
+
const gasConfigs = requestsToAnalyze.filter(request => request.gasConfig).map(request => request.gasConfig);
|
|
285
|
+
const gasLimits = gasConfigs.map(g => g?.gasLimit).filter((g): g is bigint => g !== undefined);
|
|
286
|
+
const gasLimit = gasLimits.length > 0 ? gasLimits.reduce((sum, g) => sum + g, 0n) : 0n;
|
|
287
|
+
|
|
288
|
+
// Get the transaction requests
|
|
289
|
+
const l1Requests = requestsToAnalyze.map(r => r.request);
|
|
290
|
+
|
|
291
|
+
// Start the analysis
|
|
292
|
+
const analysisId = await this.l1FeeAnalyzer.startAnalysis(
|
|
293
|
+
l2SlotNumber,
|
|
294
|
+
gasLimit > 0n ? gasLimit : MAX_L1_TX_LIMIT,
|
|
295
|
+
l1Requests,
|
|
296
|
+
blobConfig,
|
|
297
|
+
onComplete,
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
this.log.info('Started L1 fee analysis', {
|
|
301
|
+
analysisId,
|
|
302
|
+
l2SlotNumber: l2SlotNumber.toString(),
|
|
303
|
+
requestCount: requestsToAnalyze.length,
|
|
304
|
+
hasBlobConfig: !!blobConfig,
|
|
305
|
+
gasLimit: gasLimit.toString(),
|
|
306
|
+
actions: requestsToAnalyze.map(r => r.action),
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Return the analysis result (will be incomplete until block mines)
|
|
310
|
+
return this.l1FeeAnalyzer.getAnalysis(analysisId);
|
|
311
|
+
}
|
|
312
|
+
|
|
214
313
|
/**
|
|
215
314
|
* Sends all requests that are still valid.
|
|
216
315
|
* @returns one of:
|
|
@@ -218,10 +317,11 @@ export class SequencerPublisher {
|
|
|
218
317
|
* - a receipt and errorMsg if it failed on L1
|
|
219
318
|
* - undefined if no valid requests are found OR the tx failed to send.
|
|
220
319
|
*/
|
|
320
|
+
@trackSpan('SequencerPublisher.sendRequests')
|
|
221
321
|
public async sendRequests() {
|
|
222
322
|
const requestsToProcess = [...this.requests];
|
|
223
323
|
this.requests = [];
|
|
224
|
-
if (this.interrupted) {
|
|
324
|
+
if (this.interrupted || requestsToProcess.length === 0) {
|
|
225
325
|
return undefined;
|
|
226
326
|
}
|
|
227
327
|
const currentL2Slot = this.getCurrentL2Slot();
|
|
@@ -264,7 +364,16 @@ export class SequencerPublisher {
|
|
|
264
364
|
|
|
265
365
|
// Merge gasConfigs. Yields the sum of gasLimits, and the earliest txTimeoutAt, or undefined if no gasConfig sets them.
|
|
266
366
|
const gasLimits = gasConfigs.map(g => g?.gasLimit).filter((g): g is bigint => g !== undefined);
|
|
267
|
-
|
|
367
|
+
let gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
|
|
368
|
+
// Cap at L1 block gas limit so the node accepts the tx ("gas limit too high" otherwise).
|
|
369
|
+
const maxGas = MAX_L1_TX_LIMIT;
|
|
370
|
+
if (gasLimit !== undefined && gasLimit > maxGas) {
|
|
371
|
+
this.log.debug('Capping bundled tx gas limit to L1 max', {
|
|
372
|
+
requested: gasLimit,
|
|
373
|
+
capped: maxGas,
|
|
374
|
+
});
|
|
375
|
+
gasLimit = maxGas;
|
|
376
|
+
}
|
|
268
377
|
const txTimeoutAts = gasConfigs.map(g => g?.txTimeoutAt).filter((g): g is Date => g !== undefined);
|
|
269
378
|
const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map(g => g.getTime()))) : undefined; // earliest
|
|
270
379
|
const txConfig: RequestWithExpiry['gasConfig'] = { gasLimit, txTimeoutAt };
|
|
@@ -335,14 +444,14 @@ export class SequencerPublisher {
|
|
|
335
444
|
public canProposeAtNextEthBlock(
|
|
336
445
|
tipArchive: Fr,
|
|
337
446
|
msgSender: EthAddress,
|
|
338
|
-
opts: {
|
|
447
|
+
opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
339
448
|
) {
|
|
340
449
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
341
450
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
342
451
|
|
|
343
452
|
return this.rollupContract
|
|
344
453
|
.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
|
|
345
|
-
forcePendingCheckpointNumber: opts.
|
|
454
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
346
455
|
})
|
|
347
456
|
.catch(err => {
|
|
348
457
|
if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
|
|
@@ -361,7 +470,11 @@ export class SequencerPublisher {
|
|
|
361
470
|
* It will throw if the block header is invalid.
|
|
362
471
|
* @param header - The block header to validate
|
|
363
472
|
*/
|
|
364
|
-
|
|
473
|
+
@trackSpan('SequencerPublisher.validateBlockHeader')
|
|
474
|
+
public async validateBlockHeader(
|
|
475
|
+
header: CheckpointHeader,
|
|
476
|
+
opts?: { forcePendingCheckpointNumber: CheckpointNumber | undefined },
|
|
477
|
+
): Promise<void> {
|
|
365
478
|
const flags = { ignoreDA: true, ignoreSignatures: true };
|
|
366
479
|
|
|
367
480
|
const args = [
|
|
@@ -370,12 +483,14 @@ export class SequencerPublisher {
|
|
|
370
483
|
[], // no signers
|
|
371
484
|
Signature.empty().toViemSignature(),
|
|
372
485
|
`0x${'0'.repeat(64)}`, // 32 empty bytes
|
|
373
|
-
header.
|
|
486
|
+
header.blobsHash.toString(),
|
|
374
487
|
flags,
|
|
375
488
|
] as const;
|
|
376
489
|
|
|
377
490
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
378
|
-
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
491
|
+
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
492
|
+
opts?.forcePendingCheckpointNumber,
|
|
493
|
+
);
|
|
379
494
|
let balance = 0n;
|
|
380
495
|
if (this.config.fishermanMode) {
|
|
381
496
|
// In fisherman mode, we can't know where the proposer is publishing from
|
|
@@ -402,77 +517,95 @@ export class SequencerPublisher {
|
|
|
402
517
|
}
|
|
403
518
|
|
|
404
519
|
/**
|
|
405
|
-
* Simulate making a call to invalidate a
|
|
406
|
-
* @param
|
|
520
|
+
* Simulate making a call to invalidate a checkpoint with invalid attestations. Returns undefined if no need to invalidate.
|
|
521
|
+
* @param validationResult - The validation result indicating which checkpoint to invalidate (as returned by the archiver)
|
|
407
522
|
*/
|
|
408
|
-
public async
|
|
409
|
-
validationResult:
|
|
410
|
-
): Promise<
|
|
523
|
+
public async simulateInvalidateCheckpoint(
|
|
524
|
+
validationResult: ValidateCheckpointResult,
|
|
525
|
+
): Promise<InvalidateCheckpointRequest | undefined> {
|
|
411
526
|
if (validationResult.valid) {
|
|
412
527
|
return undefined;
|
|
413
528
|
}
|
|
414
529
|
|
|
415
|
-
const { reason,
|
|
416
|
-
const
|
|
417
|
-
const logData = { ...
|
|
530
|
+
const { reason, checkpoint } = validationResult;
|
|
531
|
+
const checkpointNumber = checkpoint.checkpointNumber;
|
|
532
|
+
const logData = { ...checkpoint, reason };
|
|
418
533
|
|
|
419
|
-
const
|
|
420
|
-
if (
|
|
534
|
+
const currentCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
535
|
+
if (currentCheckpointNumber < checkpointNumber) {
|
|
421
536
|
this.log.verbose(
|
|
422
|
-
`Skipping
|
|
423
|
-
{
|
|
537
|
+
`Skipping checkpoint ${checkpointNumber} invalidation since it has already been removed from the pending chain`,
|
|
538
|
+
{ currentCheckpointNumber, ...logData },
|
|
424
539
|
);
|
|
425
540
|
return undefined;
|
|
426
541
|
}
|
|
427
542
|
|
|
428
|
-
const request = this.
|
|
429
|
-
this.log.debug(`Simulating invalidate
|
|
543
|
+
const request = this.buildInvalidateCheckpointRequest(validationResult);
|
|
544
|
+
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
|
|
430
545
|
|
|
431
546
|
try {
|
|
432
|
-
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
433
|
-
|
|
547
|
+
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
548
|
+
request,
|
|
549
|
+
undefined,
|
|
550
|
+
undefined,
|
|
551
|
+
mergeAbis([request.abi ?? [], ErrorsAbi]),
|
|
552
|
+
);
|
|
553
|
+
this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} succeeded`, {
|
|
554
|
+
...logData,
|
|
555
|
+
request,
|
|
556
|
+
gasUsed,
|
|
557
|
+
});
|
|
434
558
|
|
|
435
|
-
return {
|
|
559
|
+
return {
|
|
560
|
+
request,
|
|
561
|
+
gasUsed,
|
|
562
|
+
checkpointNumber,
|
|
563
|
+
forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
|
|
564
|
+
reason,
|
|
565
|
+
};
|
|
436
566
|
} catch (err) {
|
|
437
567
|
const viemError = formatViemError(err);
|
|
438
568
|
|
|
439
|
-
// If the error is due to the
|
|
440
|
-
// we can safely ignore it and return undefined so we go ahead with
|
|
441
|
-
if (viemError.message?.includes('
|
|
569
|
+
// If the error is due to the checkpoint not being in the pending chain, and it was indeed removed by someone else,
|
|
570
|
+
// we can safely ignore it and return undefined so we go ahead with checkpoint building.
|
|
571
|
+
if (viemError.message?.includes('Rollup__CheckpointNotInPendingChain')) {
|
|
442
572
|
this.log.verbose(
|
|
443
|
-
`Simulation for invalidate
|
|
573
|
+
`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
|
|
444
574
|
{ ...logData, request, error: viemError.message },
|
|
445
575
|
);
|
|
446
|
-
const
|
|
447
|
-
if (
|
|
448
|
-
this.log.verbose(`
|
|
576
|
+
const latestPendingCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
577
|
+
if (latestPendingCheckpointNumber < checkpointNumber) {
|
|
578
|
+
this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, { ...logData });
|
|
449
579
|
return undefined;
|
|
450
580
|
} else {
|
|
451
581
|
this.log.error(
|
|
452
|
-
`Simulation for invalidate ${
|
|
582
|
+
`Simulation for invalidate checkpoint ${checkpointNumber} failed and it is still in pending chain`,
|
|
453
583
|
viemError,
|
|
454
584
|
logData,
|
|
455
585
|
);
|
|
456
|
-
throw new Error(
|
|
457
|
-
|
|
458
|
-
|
|
586
|
+
throw new Error(
|
|
587
|
+
`Failed to simulate invalidate checkpoint ${checkpointNumber} while it is still in pending chain`,
|
|
588
|
+
{
|
|
589
|
+
cause: viemError,
|
|
590
|
+
},
|
|
591
|
+
);
|
|
459
592
|
}
|
|
460
593
|
}
|
|
461
594
|
|
|
462
|
-
// Otherwise, throw. We cannot build the next
|
|
463
|
-
this.log.error(`Simulation for invalidate
|
|
464
|
-
throw new Error(`Failed to simulate invalidate
|
|
595
|
+
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
596
|
+
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
|
|
597
|
+
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
|
|
465
598
|
}
|
|
466
599
|
}
|
|
467
600
|
|
|
468
|
-
private
|
|
601
|
+
private buildInvalidateCheckpointRequest(validationResult: ValidateCheckpointResult) {
|
|
469
602
|
if (validationResult.valid) {
|
|
470
|
-
throw new Error('Cannot invalidate a valid
|
|
603
|
+
throw new Error('Cannot invalidate a valid checkpoint');
|
|
471
604
|
}
|
|
472
605
|
|
|
473
|
-
const {
|
|
474
|
-
const logData = { ...
|
|
475
|
-
this.log.debug(`
|
|
606
|
+
const { checkpoint, committee, reason } = validationResult;
|
|
607
|
+
const logData = { ...checkpoint, reason };
|
|
608
|
+
this.log.debug(`Building invalidate checkpoint ${checkpoint.checkpointNumber} request`, logData);
|
|
476
609
|
|
|
477
610
|
const attestationsAndSigners = new CommitteeAttestationsAndSigners(
|
|
478
611
|
validationResult.attestations,
|
|
@@ -480,14 +613,14 @@ export class SequencerPublisher {
|
|
|
480
613
|
|
|
481
614
|
if (reason === 'invalid-attestation') {
|
|
482
615
|
return this.rollupContract.buildInvalidateBadAttestationRequest(
|
|
483
|
-
|
|
616
|
+
checkpoint.checkpointNumber,
|
|
484
617
|
attestationsAndSigners,
|
|
485
618
|
committee,
|
|
486
619
|
validationResult.invalidIndex,
|
|
487
620
|
);
|
|
488
621
|
} else if (reason === 'insufficient-attestations') {
|
|
489
622
|
return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
|
|
490
|
-
|
|
623
|
+
checkpoint.checkpointNumber,
|
|
491
624
|
attestationsAndSigners,
|
|
492
625
|
committee,
|
|
493
626
|
);
|
|
@@ -497,47 +630,41 @@ export class SequencerPublisher {
|
|
|
497
630
|
}
|
|
498
631
|
}
|
|
499
632
|
|
|
500
|
-
/**
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
*
|
|
505
|
-
* @param block - The block to propose
|
|
506
|
-
* @param attestationData - The block's attestation data
|
|
507
|
-
*
|
|
508
|
-
*/
|
|
509
|
-
public async validateBlockForSubmission(
|
|
510
|
-
block: L2Block,
|
|
633
|
+
/** Simulates `propose` to make sure that the checkpoint is valid for submission */
|
|
634
|
+
@trackSpan('SequencerPublisher.validateCheckpointForSubmission')
|
|
635
|
+
public async validateCheckpointForSubmission(
|
|
636
|
+
checkpoint: Checkpoint,
|
|
511
637
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
512
638
|
attestationsAndSignersSignature: Signature,
|
|
513
|
-
options: {
|
|
639
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
514
640
|
): Promise<bigint> {
|
|
515
641
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
516
642
|
|
|
643
|
+
// TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
|
|
517
644
|
// If we have no attestations, we still need to provide the empty attestations
|
|
518
645
|
// so that the committee is recalculated correctly
|
|
519
|
-
const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
520
|
-
if (ignoreSignatures) {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
const blobFields =
|
|
646
|
+
// const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
647
|
+
// if (ignoreSignatures) {
|
|
648
|
+
// const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
|
|
649
|
+
// if (!committee) {
|
|
650
|
+
// this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
651
|
+
// throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
652
|
+
// }
|
|
653
|
+
// attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
654
|
+
// CommitteeAttestation.fromAddress(committeeMember),
|
|
655
|
+
// );
|
|
656
|
+
// }
|
|
657
|
+
|
|
658
|
+
const blobFields = checkpoint.toBlobFields();
|
|
532
659
|
const blobs = getBlobsPerL1Block(blobFields);
|
|
533
660
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
534
661
|
|
|
535
662
|
const args = [
|
|
536
663
|
{
|
|
537
|
-
header:
|
|
538
|
-
archive: toHex(
|
|
664
|
+
header: checkpoint.header.toViem(),
|
|
665
|
+
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
539
666
|
oracleInput: {
|
|
540
|
-
feeAssetPriceModifier:
|
|
667
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
541
668
|
},
|
|
542
669
|
},
|
|
543
670
|
attestationsAndSigners.getPackedAttestations(),
|
|
@@ -573,10 +700,19 @@ export class SequencerPublisher {
|
|
|
573
700
|
const round = await base.computeRound(slotNumber);
|
|
574
701
|
const roundInfo = await base.getRoundInfo(this.rollupContract.address, round);
|
|
575
702
|
|
|
703
|
+
if (roundInfo.quorumReached) {
|
|
704
|
+
return false;
|
|
705
|
+
}
|
|
706
|
+
|
|
576
707
|
if (roundInfo.lastSignalSlot >= slotNumber) {
|
|
577
708
|
return false;
|
|
578
709
|
}
|
|
579
710
|
|
|
711
|
+
if (await this.isPayloadEmpty(payload)) {
|
|
712
|
+
this.log.warn(`Skipping vote cast for payload with empty code`);
|
|
713
|
+
return false;
|
|
714
|
+
}
|
|
715
|
+
|
|
580
716
|
const cachedLastVote = this.lastActions[signalType];
|
|
581
717
|
this.lastActions[signalType] = slotNumber;
|
|
582
718
|
const action = signalType;
|
|
@@ -596,7 +732,7 @@ export class SequencerPublisher {
|
|
|
596
732
|
});
|
|
597
733
|
|
|
598
734
|
try {
|
|
599
|
-
await this.l1TxUtils.simulate(request, { time: timestamp }, [], ErrorsAbi);
|
|
735
|
+
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
600
736
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
601
737
|
} catch (err) {
|
|
602
738
|
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, err);
|
|
@@ -619,14 +755,14 @@ export class SequencerPublisher {
|
|
|
619
755
|
const logData = { ...result, slotNumber, round, payload: payload.toString() };
|
|
620
756
|
if (!success) {
|
|
621
757
|
this.log.error(
|
|
622
|
-
`Signaling in
|
|
758
|
+
`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} failed`,
|
|
623
759
|
logData,
|
|
624
760
|
);
|
|
625
761
|
this.lastActions[signalType] = cachedLastVote;
|
|
626
762
|
return false;
|
|
627
763
|
} else {
|
|
628
764
|
this.log.info(
|
|
629
|
-
`Signaling in
|
|
765
|
+
`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} succeeded`,
|
|
630
766
|
logData,
|
|
631
767
|
);
|
|
632
768
|
return true;
|
|
@@ -636,6 +772,17 @@ export class SequencerPublisher {
|
|
|
636
772
|
return true;
|
|
637
773
|
}
|
|
638
774
|
|
|
775
|
+
private async isPayloadEmpty(payload: EthAddress): Promise<boolean> {
|
|
776
|
+
const key = payload.toString();
|
|
777
|
+
const cached = this.isPayloadEmptyCache.get(key);
|
|
778
|
+
if (cached) {
|
|
779
|
+
return cached;
|
|
780
|
+
}
|
|
781
|
+
const isEmpty = !(await this.l1TxUtils.getCode(payload));
|
|
782
|
+
this.isPayloadEmptyCache.set(key, isEmpty);
|
|
783
|
+
return isEmpty;
|
|
784
|
+
}
|
|
785
|
+
|
|
639
786
|
/**
|
|
640
787
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
641
788
|
* @param slotNumber - The slot number to cast a signal for.
|
|
@@ -783,30 +930,25 @@ export class SequencerPublisher {
|
|
|
783
930
|
return true;
|
|
784
931
|
}
|
|
785
932
|
|
|
786
|
-
/**
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
* @param block - L2 block to propose.
|
|
790
|
-
* @returns True if the tx has been enqueued, throws otherwise. See #9315
|
|
791
|
-
*/
|
|
792
|
-
public async enqueueProposeL2Block(
|
|
793
|
-
block: L2Block,
|
|
933
|
+
/** Simulates and enqueues a proposal for a checkpoint on L1 */
|
|
934
|
+
public async enqueueProposeCheckpoint(
|
|
935
|
+
checkpoint: Checkpoint,
|
|
794
936
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
795
937
|
attestationsAndSignersSignature: Signature,
|
|
796
|
-
opts: { txTimeoutAt?: Date;
|
|
797
|
-
): Promise<
|
|
798
|
-
const checkpointHeader =
|
|
938
|
+
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
939
|
+
): Promise<void> {
|
|
940
|
+
const checkpointHeader = checkpoint.header;
|
|
799
941
|
|
|
800
|
-
const blobFields =
|
|
942
|
+
const blobFields = checkpoint.toBlobFields();
|
|
801
943
|
const blobs = getBlobsPerL1Block(blobFields);
|
|
802
944
|
|
|
803
|
-
const proposeTxArgs = {
|
|
945
|
+
const proposeTxArgs: L1ProcessArgs = {
|
|
804
946
|
header: checkpointHeader,
|
|
805
|
-
archive:
|
|
806
|
-
body: block.body.toBuffer(),
|
|
947
|
+
archive: checkpoint.archive.root.toBuffer(),
|
|
807
948
|
blobs,
|
|
808
949
|
attestationsAndSigners,
|
|
809
950
|
attestationsAndSignersSignature,
|
|
951
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
810
952
|
};
|
|
811
953
|
|
|
812
954
|
let ts: bigint;
|
|
@@ -817,22 +959,29 @@ export class SequencerPublisher {
|
|
|
817
959
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
818
960
|
// make time consistency checks break.
|
|
819
961
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
820
|
-
ts = await this.
|
|
962
|
+
ts = await this.validateCheckpointForSubmission(
|
|
963
|
+
checkpoint,
|
|
964
|
+
attestationsAndSigners,
|
|
965
|
+
attestationsAndSignersSignature,
|
|
966
|
+
opts,
|
|
967
|
+
);
|
|
821
968
|
} catch (err: any) {
|
|
822
|
-
this.log.error(`
|
|
823
|
-
...
|
|
824
|
-
slotNumber:
|
|
825
|
-
|
|
969
|
+
this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
970
|
+
...checkpoint.getStats(),
|
|
971
|
+
slotNumber: checkpoint.header.slotNumber,
|
|
972
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
826
973
|
});
|
|
827
974
|
throw err;
|
|
828
975
|
}
|
|
829
976
|
|
|
830
|
-
this.log.verbose(`Enqueuing
|
|
831
|
-
await this.addProposeTx(
|
|
832
|
-
return true;
|
|
977
|
+
this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
|
|
978
|
+
await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
|
|
833
979
|
}
|
|
834
980
|
|
|
835
|
-
public
|
|
981
|
+
public enqueueInvalidateCheckpoint(
|
|
982
|
+
request: InvalidateCheckpointRequest | undefined,
|
|
983
|
+
opts: { txTimeoutAt?: Date } = {},
|
|
984
|
+
) {
|
|
836
985
|
if (!request) {
|
|
837
986
|
return;
|
|
838
987
|
}
|
|
@@ -840,9 +989,9 @@ export class SequencerPublisher {
|
|
|
840
989
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
841
990
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(request.gasUsed) * 64) / 63)));
|
|
842
991
|
|
|
843
|
-
const { gasUsed,
|
|
844
|
-
const logData = { gasUsed,
|
|
845
|
-
this.log.verbose(`Enqueuing invalidate
|
|
992
|
+
const { gasUsed, checkpointNumber } = request;
|
|
993
|
+
const logData = { gasUsed, checkpointNumber, gasLimit, opts };
|
|
994
|
+
this.log.verbose(`Enqueuing invalidate checkpoint request`, logData);
|
|
846
995
|
this.addRequest({
|
|
847
996
|
action: `invalidate-by-${request.reason}`,
|
|
848
997
|
request: request.request,
|
|
@@ -855,9 +1004,9 @@ export class SequencerPublisher {
|
|
|
855
1004
|
result.receipt.status === 'success' &&
|
|
856
1005
|
tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointInvalidated');
|
|
857
1006
|
if (!success) {
|
|
858
|
-
this.log.warn(`Invalidate
|
|
1007
|
+
this.log.warn(`Invalidate checkpoint ${request.checkpointNumber} failed`, { ...result, ...logData });
|
|
859
1008
|
} else {
|
|
860
|
-
this.log.info(`Invalidate
|
|
1009
|
+
this.log.info(`Invalidate checkpoint ${request.checkpointNumber} succeeded`, { ...result, ...logData });
|
|
861
1010
|
}
|
|
862
1011
|
return !!success;
|
|
863
1012
|
},
|
|
@@ -883,12 +1032,14 @@ export class SequencerPublisher {
|
|
|
883
1032
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
884
1033
|
|
|
885
1034
|
let gasUsed: bigint;
|
|
1035
|
+
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
886
1036
|
try {
|
|
887
|
-
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [],
|
|
1037
|
+
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi)); // TODO(palla/slash): Check the timestamp logic
|
|
888
1038
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
889
1039
|
} catch (err) {
|
|
890
|
-
const viemError = formatViemError(err);
|
|
1040
|
+
const viemError = formatViemError(err, simulateAbi);
|
|
891
1041
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1042
|
+
|
|
892
1043
|
return false;
|
|
893
1044
|
}
|
|
894
1045
|
|
|
@@ -896,10 +1047,14 @@ export class SequencerPublisher {
|
|
|
896
1047
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(gasUsed) * 64) / 63)));
|
|
897
1048
|
logData.gasLimit = gasLimit;
|
|
898
1049
|
|
|
1050
|
+
// Store the ABI used for simulation on the request so Multicall3.forward can decode errors
|
|
1051
|
+
// when the tx is sent and a revert is diagnosed via simulation.
|
|
1052
|
+
const requestWithAbi = { ...request, abi: simulateAbi };
|
|
1053
|
+
|
|
899
1054
|
this.log.debug(`Enqueuing ${action}`, logData);
|
|
900
1055
|
this.addRequest({
|
|
901
1056
|
action,
|
|
902
|
-
request,
|
|
1057
|
+
request: requestWithAbi,
|
|
903
1058
|
gasConfig: { gasLimit },
|
|
904
1059
|
lastValidL2Slot: slotNumber,
|
|
905
1060
|
checkSuccess: (_req, result) => {
|
|
@@ -936,7 +1091,7 @@ export class SequencerPublisher {
|
|
|
936
1091
|
private async prepareProposeTx(
|
|
937
1092
|
encodedData: L1ProcessArgs,
|
|
938
1093
|
timestamp: bigint,
|
|
939
|
-
options: {
|
|
1094
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
940
1095
|
) {
|
|
941
1096
|
const kzg = Blob.getViemKzgInstance();
|
|
942
1097
|
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
@@ -981,8 +1136,7 @@ export class SequencerPublisher {
|
|
|
981
1136
|
header: encodedData.header.toViem(),
|
|
982
1137
|
archive: toHex(encodedData.archive),
|
|
983
1138
|
oracleInput: {
|
|
984
|
-
|
|
985
|
-
feeAssetPriceModifier: 0n,
|
|
1139
|
+
feeAssetPriceModifier: encodedData.feeAssetPriceModifier,
|
|
986
1140
|
},
|
|
987
1141
|
},
|
|
988
1142
|
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
@@ -1008,7 +1162,7 @@ export class SequencerPublisher {
|
|
|
1008
1162
|
readonly header: ViemHeader;
|
|
1009
1163
|
readonly archive: `0x${string}`;
|
|
1010
1164
|
readonly oracleInput: {
|
|
1011
|
-
readonly feeAssetPriceModifier:
|
|
1165
|
+
readonly feeAssetPriceModifier: bigint;
|
|
1012
1166
|
};
|
|
1013
1167
|
},
|
|
1014
1168
|
ViemCommitteeAttestations,
|
|
@@ -1017,7 +1171,7 @@ export class SequencerPublisher {
|
|
|
1017
1171
|
`0x${string}`,
|
|
1018
1172
|
],
|
|
1019
1173
|
timestamp: bigint,
|
|
1020
|
-
options: {
|
|
1174
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1021
1175
|
) {
|
|
1022
1176
|
const rollupData = encodeFunctionData({
|
|
1023
1177
|
abi: RollupAbi,
|
|
@@ -1025,10 +1179,10 @@ export class SequencerPublisher {
|
|
|
1025
1179
|
args,
|
|
1026
1180
|
});
|
|
1027
1181
|
|
|
1028
|
-
// override the pending
|
|
1029
|
-
const
|
|
1030
|
-
options.
|
|
1031
|
-
? await this.rollupContract.makePendingCheckpointNumberOverride(options.
|
|
1182
|
+
// override the pending checkpoint number if requested
|
|
1183
|
+
const forcePendingCheckpointNumberStateDiff = (
|
|
1184
|
+
options.forcePendingCheckpointNumber !== undefined
|
|
1185
|
+
? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber)
|
|
1032
1186
|
: []
|
|
1033
1187
|
).flatMap(override => override.stateDiff ?? []);
|
|
1034
1188
|
|
|
@@ -1038,7 +1192,7 @@ export class SequencerPublisher {
|
|
|
1038
1192
|
// @note we override checkBlob to false since blobs are not part simulate()
|
|
1039
1193
|
stateDiff: [
|
|
1040
1194
|
{ slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
|
|
1041
|
-
...
|
|
1195
|
+
...forcePendingCheckpointNumberStateDiff,
|
|
1042
1196
|
],
|
|
1043
1197
|
},
|
|
1044
1198
|
];
|
|
@@ -1055,20 +1209,20 @@ export class SequencerPublisher {
|
|
|
1055
1209
|
{
|
|
1056
1210
|
to: this.rollupContract.address,
|
|
1057
1211
|
data: rollupData,
|
|
1058
|
-
gas:
|
|
1212
|
+
gas: MAX_L1_TX_LIMIT,
|
|
1059
1213
|
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1060
1214
|
},
|
|
1061
1215
|
{
|
|
1062
1216
|
// @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
|
|
1063
1217
|
time: timestamp + 1n,
|
|
1064
1218
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1065
|
-
gasLimit:
|
|
1219
|
+
gasLimit: MAX_L1_TX_LIMIT * 2n,
|
|
1066
1220
|
},
|
|
1067
1221
|
stateOverrides,
|
|
1068
1222
|
RollupAbi,
|
|
1069
1223
|
{
|
|
1070
1224
|
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
1071
|
-
fallbackGasEstimate:
|
|
1225
|
+
fallbackGasEstimate: MAX_L1_TX_LIMIT,
|
|
1072
1226
|
},
|
|
1073
1227
|
)
|
|
1074
1228
|
.catch(err => {
|
|
@@ -1078,7 +1232,7 @@ export class SequencerPublisher {
|
|
|
1078
1232
|
this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
|
|
1079
1233
|
// Return a minimal simulation result with the fallback gas estimate
|
|
1080
1234
|
return {
|
|
1081
|
-
gasUsed:
|
|
1235
|
+
gasUsed: MAX_L1_TX_LIMIT,
|
|
1082
1236
|
logs: [],
|
|
1083
1237
|
};
|
|
1084
1238
|
}
|
|
@@ -1090,11 +1244,12 @@ export class SequencerPublisher {
|
|
|
1090
1244
|
}
|
|
1091
1245
|
|
|
1092
1246
|
private async addProposeTx(
|
|
1093
|
-
|
|
1247
|
+
checkpoint: Checkpoint,
|
|
1094
1248
|
encodedData: L1ProcessArgs,
|
|
1095
|
-
opts: { txTimeoutAt?: Date;
|
|
1249
|
+
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
1096
1250
|
timestamp: bigint,
|
|
1097
1251
|
): Promise<void> {
|
|
1252
|
+
const slot = checkpoint.header.slotNumber;
|
|
1098
1253
|
const timer = new Timer();
|
|
1099
1254
|
const kzg = Blob.getViemKzgInstance();
|
|
1100
1255
|
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
@@ -1109,11 +1264,13 @@ export class SequencerPublisher {
|
|
|
1109
1264
|
SequencerPublisher.MULTICALL_OVERHEAD_GAS_GUESS, // We issue the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
1110
1265
|
);
|
|
1111
1266
|
|
|
1112
|
-
// Send the blobs to the blob
|
|
1113
|
-
// tx fails but it does get mined. We make sure that the blobs are sent to the blob
|
|
1114
|
-
void
|
|
1115
|
-
this.
|
|
1116
|
-
|
|
1267
|
+
// Send the blobs to the blob client preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
|
|
1268
|
+
// tx fails but it does get mined. We make sure that the blobs are sent to the blob client regardless of the tx outcome.
|
|
1269
|
+
void Promise.resolve().then(() =>
|
|
1270
|
+
this.blobClient.sendBlobsToFilestore(encodedData.blobs).catch(_err => {
|
|
1271
|
+
this.log.error('Failed to send blobs to blob client');
|
|
1272
|
+
}),
|
|
1273
|
+
);
|
|
1117
1274
|
|
|
1118
1275
|
return this.addRequest({
|
|
1119
1276
|
action: 'propose',
|
|
@@ -1121,7 +1278,7 @@ export class SequencerPublisher {
|
|
|
1121
1278
|
to: this.rollupContract.address,
|
|
1122
1279
|
data: rollupData,
|
|
1123
1280
|
},
|
|
1124
|
-
lastValidL2Slot:
|
|
1281
|
+
lastValidL2Slot: checkpoint.header.slotNumber,
|
|
1125
1282
|
gasConfig: { ...opts, gasLimit },
|
|
1126
1283
|
blobConfig: {
|
|
1127
1284
|
blobs: encodedData.blobs.map(b => b.data),
|
|
@@ -1136,11 +1293,12 @@ export class SequencerPublisher {
|
|
|
1136
1293
|
receipt &&
|
|
1137
1294
|
receipt.status === 'success' &&
|
|
1138
1295
|
tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointProposed');
|
|
1296
|
+
|
|
1139
1297
|
if (success) {
|
|
1140
1298
|
const endBlock = receipt.blockNumber;
|
|
1141
1299
|
const inclusionBlocks = Number(endBlock - startBlock);
|
|
1142
1300
|
const { calldataGas, calldataSize, sender } = stats!;
|
|
1143
|
-
const publishStats:
|
|
1301
|
+
const publishStats: L1PublishCheckpointStats = {
|
|
1144
1302
|
gasPrice: receipt.effectiveGasPrice,
|
|
1145
1303
|
gasUsed: receipt.gasUsed,
|
|
1146
1304
|
blobGasUsed: receipt.blobGasUsed ?? 0n,
|
|
@@ -1149,23 +1307,26 @@ export class SequencerPublisher {
|
|
|
1149
1307
|
calldataGas,
|
|
1150
1308
|
calldataSize,
|
|
1151
1309
|
sender,
|
|
1152
|
-
...
|
|
1310
|
+
...checkpoint.getStats(),
|
|
1153
1311
|
eventName: 'rollup-published-to-l1',
|
|
1154
1312
|
blobCount: encodedData.blobs.length,
|
|
1155
1313
|
inclusionBlocks,
|
|
1156
1314
|
};
|
|
1157
|
-
this.log.info(`Published
|
|
1315
|
+
this.log.info(`Published checkpoint ${checkpoint.number} at slot ${slot} to rollup contract`, {
|
|
1316
|
+
...stats,
|
|
1317
|
+
...checkpoint.getStats(),
|
|
1318
|
+
...pick(receipt, 'transactionHash', 'blockHash'),
|
|
1319
|
+
});
|
|
1158
1320
|
this.metrics.recordProcessBlockTx(timer.ms(), publishStats);
|
|
1159
1321
|
|
|
1160
1322
|
return true;
|
|
1161
1323
|
} else {
|
|
1162
1324
|
this.metrics.recordFailedTx('process');
|
|
1163
|
-
this.log.error(
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
});
|
|
1325
|
+
this.log.error(
|
|
1326
|
+
`Publishing checkpoint at slot ${slot} failed with ${errorMsg ?? 'no error message'}`,
|
|
1327
|
+
undefined,
|
|
1328
|
+
{ ...checkpoint.getStats(), ...receipt },
|
|
1329
|
+
);
|
|
1169
1330
|
return false;
|
|
1170
1331
|
}
|
|
1171
1332
|
},
|