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