@aztec/sequencer-client 0.0.1-commit.c7c42ec → 0.0.1-commit.c949de6bc
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 +14 -10
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +15 -4
- package/dest/config.d.ts +3 -4
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +22 -12
- package/dest/global_variable_builder/global_builder.d.ts +5 -7
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +13 -13
- 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 +35 -17
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +106 -42
- package/dest/publisher/index.d.ts +2 -1
- package/dest/publisher/index.d.ts.map +1 -1
- package/dest/publisher/l1_tx_failed_store/factory.d.ts +11 -0
- package/dest/publisher/l1_tx_failed_store/factory.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/factory.js +22 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +59 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +1 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +15 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +34 -0
- package/dest/publisher/l1_tx_failed_store/index.d.ts +4 -0
- package/dest/publisher/l1_tx_failed_store/index.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/index.js +2 -0
- package/dest/publisher/sequencer-publisher-factory.d.ts +11 -3
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +13 -2
- package/dest/publisher/sequencer-publisher-metrics.d.ts +1 -1
- 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 +38 -23
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +739 -96
- package/dest/sequencer/checkpoint_proposal_job.d.ts +40 -12
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +641 -64
- package/dest/sequencer/checkpoint_voter.d.ts +3 -2
- package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_voter.js +34 -10
- package/dest/sequencer/index.d.ts +1 -3
- package/dest/sequencer/index.d.ts.map +1 -1
- package/dest/sequencer/index.js +0 -2
- package/dest/sequencer/metrics.d.ts +19 -7
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +131 -141
- package/dest/sequencer/sequencer.d.ts +38 -18
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +513 -66
- package/dest/sequencer/timetable.d.ts +1 -4
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +2 -5
- package/dest/test/index.d.ts +4 -7
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts +25 -11
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +52 -9
- package/dest/test/utils.d.ts +13 -9
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +27 -17
- package/package.json +30 -28
- package/src/client/sequencer-client.ts +28 -11
- package/src/config.ts +31 -19
- package/src/global_variable_builder/global_builder.ts +14 -14
- package/src/index.ts +1 -9
- package/src/publisher/config.ts +121 -43
- package/src/publisher/index.ts +3 -0
- package/src/publisher/l1_tx_failed_store/factory.ts +32 -0
- package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +55 -0
- package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +46 -0
- package/src/publisher/l1_tx_failed_store/index.ts +3 -0
- package/src/publisher/sequencer-publisher-factory.ts +23 -6
- package/src/publisher/sequencer-publisher-metrics.ts +17 -69
- package/src/publisher/sequencer-publisher.ts +358 -126
- package/src/sequencer/checkpoint_proposal_job.ts +313 -93
- package/src/sequencer/checkpoint_voter.ts +32 -7
- package/src/sequencer/index.ts +0 -2
- package/src/sequencer/metrics.ts +132 -148
- package/src/sequencer/sequencer.ts +159 -68
- package/src/sequencer/timetable.ts +7 -6
- package/src/test/index.ts +3 -6
- package/src/test/mock_checkpoint_builder.ts +102 -29
- package/src/test/utils.ts +58 -28
- package/dest/sequencer/block_builder.d.ts +0 -26
- package/dest/sequencer/block_builder.d.ts.map +0 -1
- package/dest/sequencer/block_builder.js +0 -129
- package/dest/sequencer/checkpoint_builder.d.ts +0 -63
- package/dest/sequencer/checkpoint_builder.d.ts.map +0 -1
- package/dest/sequencer/checkpoint_builder.js +0 -131
- 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 -18
- 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 -217
- package/src/sequencer/checkpoint_builder.ts +0 -217
- package/src/tx_validator/nullifier_cache.ts +0 -30
- package/src/tx_validator/tx_validator_factory.ts +0 -133
|
@@ -4,6 +4,7 @@ import type { EpochCache } from '@aztec/epoch-cache';
|
|
|
4
4
|
import type { L1ContractsConfig } from '@aztec/ethereum/config';
|
|
5
5
|
import {
|
|
6
6
|
type EmpireSlashingProposerContract,
|
|
7
|
+
FeeAssetPriceOracle,
|
|
7
8
|
type GovernanceProposerContract,
|
|
8
9
|
type IEmpireBase,
|
|
9
10
|
MULTI_CALL_3_ADDRESS,
|
|
@@ -18,33 +19,45 @@ import {
|
|
|
18
19
|
type L1BlobInputs,
|
|
19
20
|
type L1TxConfig,
|
|
20
21
|
type L1TxRequest,
|
|
22
|
+
type L1TxUtils,
|
|
23
|
+
MAX_L1_TX_LIMIT,
|
|
21
24
|
type TransactionStats,
|
|
22
25
|
WEI_CONST,
|
|
23
26
|
} from '@aztec/ethereum/l1-tx-utils';
|
|
24
|
-
import
|
|
25
|
-
import { FormattedViemError, formatViemError, tryExtractEvent } from '@aztec/ethereum/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 {
|
|
30
|
+
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
29
31
|
import { pick } from '@aztec/foundation/collection';
|
|
30
32
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
31
33
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
32
34
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
33
35
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
36
|
+
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
34
37
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
35
38
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
36
39
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
37
40
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
38
|
-
import { CommitteeAttestationsAndSigners, type
|
|
41
|
+
import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
39
42
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
40
43
|
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
41
44
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
42
45
|
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
43
|
-
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
46
|
+
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
44
47
|
|
|
45
|
-
import {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
import {
|
|
49
|
+
type Hex,
|
|
50
|
+
type StateOverride,
|
|
51
|
+
type TransactionReceipt,
|
|
52
|
+
type TypedDataDefinition,
|
|
53
|
+
encodeFunctionData,
|
|
54
|
+
keccak256,
|
|
55
|
+
multicall3Abi,
|
|
56
|
+
toHex,
|
|
57
|
+
} from 'viem';
|
|
58
|
+
|
|
59
|
+
import type { SequencerPublisherConfig } from './config.js';
|
|
60
|
+
import { type FailedL1Tx, type L1TxFailedStore, createL1TxFailedStore } from './l1_tx_failed_store/index.js';
|
|
48
61
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
49
62
|
|
|
50
63
|
/** Arguments to the process method of the rollup contract */
|
|
@@ -59,6 +72,8 @@ type L1ProcessArgs = {
|
|
|
59
72
|
attestationsAndSigners: CommitteeAttestationsAndSigners;
|
|
60
73
|
/** Attestations and signers signature */
|
|
61
74
|
attestationsAndSignersSignature: Signature;
|
|
75
|
+
/** The fee asset price modifier in basis points (from oracle) */
|
|
76
|
+
feeAssetPriceModifier: bigint;
|
|
62
77
|
};
|
|
63
78
|
|
|
64
79
|
export const Actions = [
|
|
@@ -80,12 +95,12 @@ type GovernanceSignalAction = Extract<Action, 'governance-signal' | 'empire-slas
|
|
|
80
95
|
// Sorting for actions such that invalidations go before proposals, and proposals go before votes
|
|
81
96
|
export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
|
|
82
97
|
|
|
83
|
-
export type
|
|
98
|
+
export type InvalidateCheckpointRequest = {
|
|
84
99
|
request: L1TxRequest;
|
|
85
100
|
reason: 'invalid-attestation' | 'insufficient-attestations';
|
|
86
101
|
gasUsed: bigint;
|
|
87
|
-
|
|
88
|
-
|
|
102
|
+
checkpointNumber: CheckpointNumber;
|
|
103
|
+
forcePendingCheckpointNumber: CheckpointNumber;
|
|
89
104
|
};
|
|
90
105
|
|
|
91
106
|
interface RequestWithExpiry {
|
|
@@ -104,6 +119,7 @@ export class SequencerPublisher {
|
|
|
104
119
|
private interrupted = false;
|
|
105
120
|
private metrics: SequencerPublisherMetrics;
|
|
106
121
|
public epochCache: EpochCache;
|
|
122
|
+
private failedTxStore?: Promise<L1TxFailedStore | undefined>;
|
|
107
123
|
|
|
108
124
|
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
109
125
|
protected slashingLog = createLogger('sequencer:publisher:slashing');
|
|
@@ -111,6 +127,7 @@ export class SequencerPublisher {
|
|
|
111
127
|
protected lastActions: Partial<Record<Action, SlotNumber>> = {};
|
|
112
128
|
|
|
113
129
|
private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
|
|
130
|
+
private payloadProposedCache: Set<string> = new Set<string>();
|
|
114
131
|
|
|
115
132
|
protected log: Logger;
|
|
116
133
|
protected ethereumSlotDuration: bigint;
|
|
@@ -122,10 +139,9 @@ export class SequencerPublisher {
|
|
|
122
139
|
|
|
123
140
|
/** L1 fee analyzer for fisherman mode */
|
|
124
141
|
private l1FeeAnalyzer?: L1FeeAnalyzer;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
public static PROPOSE_GAS_GUESS: bigint = 12_000_000n;
|
|
142
|
+
|
|
143
|
+
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */
|
|
144
|
+
private feeAssetPriceOracle: FeeAssetPriceOracle;
|
|
129
145
|
|
|
130
146
|
// A CALL to a cold address is 2700 gas
|
|
131
147
|
public static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
@@ -133,20 +149,23 @@ export class SequencerPublisher {
|
|
|
133
149
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
134
150
|
public static VOTE_GAS_GUESS: bigint = 800_000n;
|
|
135
151
|
|
|
136
|
-
public l1TxUtils:
|
|
152
|
+
public l1TxUtils: L1TxUtils;
|
|
137
153
|
public rollupContract: RollupContract;
|
|
138
154
|
public govProposerContract: GovernanceProposerContract;
|
|
139
155
|
public slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
140
156
|
public slashFactoryContract: SlashFactoryContract;
|
|
141
157
|
|
|
158
|
+
public readonly tracer: Tracer;
|
|
159
|
+
|
|
142
160
|
protected requests: RequestWithExpiry[] = [];
|
|
143
161
|
|
|
144
162
|
constructor(
|
|
145
|
-
private config:
|
|
163
|
+
private config: Pick<SequencerPublisherConfig, 'fishermanMode' | 'l1TxFailedStore'> &
|
|
164
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration'> & { l1ChainId: number },
|
|
146
165
|
deps: {
|
|
147
166
|
telemetry?: TelemetryClient;
|
|
148
167
|
blobClient: BlobClientInterface;
|
|
149
|
-
l1TxUtils:
|
|
168
|
+
l1TxUtils: L1TxUtils;
|
|
150
169
|
rollupContract: RollupContract;
|
|
151
170
|
slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
152
171
|
governanceProposerContract: GovernanceProposerContract;
|
|
@@ -167,6 +186,7 @@ export class SequencerPublisher {
|
|
|
167
186
|
|
|
168
187
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
169
188
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
189
|
+
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
170
190
|
this.l1TxUtils = deps.l1TxUtils;
|
|
171
191
|
|
|
172
192
|
this.rollupContract = deps.rollupContract;
|
|
@@ -189,12 +209,52 @@ export class SequencerPublisher {
|
|
|
189
209
|
createLogger('sequencer:publisher:fee-analyzer'),
|
|
190
210
|
);
|
|
191
211
|
}
|
|
212
|
+
|
|
213
|
+
// Initialize fee asset price oracle
|
|
214
|
+
this.feeAssetPriceOracle = new FeeAssetPriceOracle(
|
|
215
|
+
this.l1TxUtils.client,
|
|
216
|
+
this.rollupContract,
|
|
217
|
+
createLogger('sequencer:publisher:price-oracle'),
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// Initialize failed L1 tx store (optional, for test networks)
|
|
221
|
+
this.failedTxStore = createL1TxFailedStore(config.l1TxFailedStore, this.log);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Backs up a failed L1 transaction to the configured store for debugging.
|
|
226
|
+
* Does nothing if no store is configured.
|
|
227
|
+
*/
|
|
228
|
+
private backupFailedTx(failedTx: Omit<FailedL1Tx, 'timestamp'>): void {
|
|
229
|
+
if (!this.failedTxStore) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const tx: FailedL1Tx = {
|
|
234
|
+
...failedTx,
|
|
235
|
+
timestamp: Date.now(),
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Fire and forget - don't block on backup
|
|
239
|
+
void this.failedTxStore
|
|
240
|
+
.then(store => store?.saveFailedTx(tx))
|
|
241
|
+
.catch(err => {
|
|
242
|
+
this.log.warn(`Failed to backup failed L1 tx to store`, err);
|
|
243
|
+
});
|
|
192
244
|
}
|
|
193
245
|
|
|
194
246
|
public getRollupContract(): RollupContract {
|
|
195
247
|
return this.rollupContract;
|
|
196
248
|
}
|
|
197
249
|
|
|
250
|
+
/**
|
|
251
|
+
* Gets the fee asset price modifier from the oracle.
|
|
252
|
+
* Returns 0n if the oracle query fails.
|
|
253
|
+
*/
|
|
254
|
+
public getFeeAssetPriceModifier(): Promise<bigint> {
|
|
255
|
+
return this.feeAssetPriceOracle.computePriceModifier();
|
|
256
|
+
}
|
|
257
|
+
|
|
198
258
|
public getSenderAddress() {
|
|
199
259
|
return this.l1TxUtils.getSenderAddress();
|
|
200
260
|
}
|
|
@@ -270,7 +330,7 @@ export class SequencerPublisher {
|
|
|
270
330
|
// Start the analysis
|
|
271
331
|
const analysisId = await this.l1FeeAnalyzer.startAnalysis(
|
|
272
332
|
l2SlotNumber,
|
|
273
|
-
gasLimit > 0n ? gasLimit :
|
|
333
|
+
gasLimit > 0n ? gasLimit : MAX_L1_TX_LIMIT,
|
|
274
334
|
l1Requests,
|
|
275
335
|
blobConfig,
|
|
276
336
|
onComplete,
|
|
@@ -296,6 +356,7 @@ export class SequencerPublisher {
|
|
|
296
356
|
* - a receipt and errorMsg if it failed on L1
|
|
297
357
|
* - undefined if no valid requests are found OR the tx failed to send.
|
|
298
358
|
*/
|
|
359
|
+
@trackSpan('SequencerPublisher.sendRequests')
|
|
299
360
|
public async sendRequests() {
|
|
300
361
|
const requestsToProcess = [...this.requests];
|
|
301
362
|
this.requests = [];
|
|
@@ -342,7 +403,16 @@ export class SequencerPublisher {
|
|
|
342
403
|
|
|
343
404
|
// Merge gasConfigs. Yields the sum of gasLimits, and the earliest txTimeoutAt, or undefined if no gasConfig sets them.
|
|
344
405
|
const gasLimits = gasConfigs.map(g => g?.gasLimit).filter((g): g is bigint => g !== undefined);
|
|
345
|
-
|
|
406
|
+
let gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
|
|
407
|
+
// Cap at L1 block gas limit so the node accepts the tx ("gas limit too high" otherwise).
|
|
408
|
+
const maxGas = MAX_L1_TX_LIMIT;
|
|
409
|
+
if (gasLimit !== undefined && gasLimit > maxGas) {
|
|
410
|
+
this.log.debug('Capping bundled tx gas limit to L1 max', {
|
|
411
|
+
requested: gasLimit,
|
|
412
|
+
capped: maxGas,
|
|
413
|
+
});
|
|
414
|
+
gasLimit = maxGas;
|
|
415
|
+
}
|
|
346
416
|
const txTimeoutAts = gasConfigs.map(g => g?.txTimeoutAt).filter((g): g is Date => g !== undefined);
|
|
347
417
|
const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map(g => g.getTime()))) : undefined; // earliest
|
|
348
418
|
const txConfig: RequestWithExpiry['gasConfig'] = { gasLimit, txTimeoutAt };
|
|
@@ -352,6 +422,21 @@ export class SequencerPublisher {
|
|
|
352
422
|
validRequests.sort((a, b) => compareActions(a.action, b.action));
|
|
353
423
|
|
|
354
424
|
try {
|
|
425
|
+
// Capture context for failed tx backup before sending
|
|
426
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
427
|
+
const multicallData = encodeFunctionData({
|
|
428
|
+
abi: multicall3Abi,
|
|
429
|
+
functionName: 'aggregate3',
|
|
430
|
+
args: [
|
|
431
|
+
validRequests.map(r => ({
|
|
432
|
+
target: r.request.to!,
|
|
433
|
+
callData: r.request.data!,
|
|
434
|
+
allowFailure: true,
|
|
435
|
+
})),
|
|
436
|
+
],
|
|
437
|
+
});
|
|
438
|
+
const blobDataHex = blobConfig?.blobs?.map(b => toHex(b)) as Hex[] | undefined;
|
|
439
|
+
|
|
355
440
|
this.log.debug('Forwarding transactions', {
|
|
356
441
|
validRequests: validRequests.map(request => request.action),
|
|
357
442
|
txConfig,
|
|
@@ -364,7 +449,12 @@ export class SequencerPublisher {
|
|
|
364
449
|
this.rollupContract.address,
|
|
365
450
|
this.log,
|
|
366
451
|
);
|
|
367
|
-
const
|
|
452
|
+
const txContext = { multicallData, blobData: blobDataHex, l1BlockNumber };
|
|
453
|
+
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(
|
|
454
|
+
validRequests,
|
|
455
|
+
result,
|
|
456
|
+
txContext,
|
|
457
|
+
);
|
|
368
458
|
return { result, expiredActions, sentActions: validActions, successfulActions, failedActions };
|
|
369
459
|
} catch (err) {
|
|
370
460
|
const viemError = formatViemError(err);
|
|
@@ -384,11 +474,25 @@ export class SequencerPublisher {
|
|
|
384
474
|
|
|
385
475
|
private callbackBundledTransactions(
|
|
386
476
|
requests: RequestWithExpiry[],
|
|
387
|
-
result
|
|
477
|
+
result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError | undefined,
|
|
478
|
+
txContext: { multicallData: Hex; blobData?: Hex[]; l1BlockNumber: bigint },
|
|
388
479
|
) {
|
|
389
480
|
const actionsListStr = requests.map(r => r.action).join(', ');
|
|
390
481
|
if (result instanceof FormattedViemError) {
|
|
391
482
|
this.log.error(`Failed to publish bundled transactions (${actionsListStr})`, result);
|
|
483
|
+
this.backupFailedTx({
|
|
484
|
+
id: keccak256(txContext.multicallData),
|
|
485
|
+
failureType: 'send-error',
|
|
486
|
+
request: { to: MULTI_CALL_3_ADDRESS, data: txContext.multicallData },
|
|
487
|
+
blobData: txContext.blobData,
|
|
488
|
+
l1BlockNumber: txContext.l1BlockNumber.toString(),
|
|
489
|
+
error: { message: result.message, name: result.name },
|
|
490
|
+
context: {
|
|
491
|
+
actions: requests.map(r => r.action),
|
|
492
|
+
requests: requests.map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
|
|
493
|
+
sender: this.getSenderAddress().toString(),
|
|
494
|
+
},
|
|
495
|
+
});
|
|
392
496
|
return { failedActions: requests.map(r => r.action) };
|
|
393
497
|
} else {
|
|
394
498
|
this.log.verbose(`Published bundled transactions (${actionsListStr})`, { result, requests });
|
|
@@ -401,6 +505,30 @@ export class SequencerPublisher {
|
|
|
401
505
|
failedActions.push(request.action);
|
|
402
506
|
}
|
|
403
507
|
}
|
|
508
|
+
// Single backup for the whole reverted tx
|
|
509
|
+
if (failedActions.length > 0 && result?.receipt?.status === 'reverted') {
|
|
510
|
+
this.backupFailedTx({
|
|
511
|
+
id: result.receipt.transactionHash,
|
|
512
|
+
failureType: 'revert',
|
|
513
|
+
request: { to: MULTI_CALL_3_ADDRESS, data: txContext.multicallData },
|
|
514
|
+
blobData: txContext.blobData,
|
|
515
|
+
l1BlockNumber: result.receipt.blockNumber.toString(),
|
|
516
|
+
receipt: {
|
|
517
|
+
transactionHash: result.receipt.transactionHash,
|
|
518
|
+
blockNumber: result.receipt.blockNumber.toString(),
|
|
519
|
+
gasUsed: (result.receipt.gasUsed ?? 0n).toString(),
|
|
520
|
+
status: 'reverted',
|
|
521
|
+
},
|
|
522
|
+
error: { message: result.errorMsg ?? 'Transaction reverted' },
|
|
523
|
+
context: {
|
|
524
|
+
actions: failedActions,
|
|
525
|
+
requests: requests
|
|
526
|
+
.filter(r => failedActions.includes(r.action))
|
|
527
|
+
.map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
|
|
528
|
+
sender: this.getSenderAddress().toString(),
|
|
529
|
+
},
|
|
530
|
+
});
|
|
531
|
+
}
|
|
404
532
|
return { successfulActions, failedActions };
|
|
405
533
|
}
|
|
406
534
|
}
|
|
@@ -413,17 +541,14 @@ export class SequencerPublisher {
|
|
|
413
541
|
public canProposeAtNextEthBlock(
|
|
414
542
|
tipArchive: Fr,
|
|
415
543
|
msgSender: EthAddress,
|
|
416
|
-
opts: {
|
|
544
|
+
opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
417
545
|
) {
|
|
418
546
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
419
547
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
420
548
|
|
|
421
549
|
return this.rollupContract
|
|
422
550
|
.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
|
|
423
|
-
forcePendingCheckpointNumber:
|
|
424
|
-
opts.forcePendingBlockNumber !== undefined
|
|
425
|
-
? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber)
|
|
426
|
-
: undefined,
|
|
551
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
427
552
|
})
|
|
428
553
|
.catch(err => {
|
|
429
554
|
if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
|
|
@@ -442,10 +567,11 @@ export class SequencerPublisher {
|
|
|
442
567
|
* It will throw if the block header is invalid.
|
|
443
568
|
* @param header - The block header to validate
|
|
444
569
|
*/
|
|
570
|
+
@trackSpan('SequencerPublisher.validateBlockHeader')
|
|
445
571
|
public async validateBlockHeader(
|
|
446
572
|
header: CheckpointHeader,
|
|
447
|
-
opts?: {
|
|
448
|
-
) {
|
|
573
|
+
opts?: { forcePendingCheckpointNumber: CheckpointNumber | undefined },
|
|
574
|
+
): Promise<void> {
|
|
449
575
|
const flags = { ignoreDA: true, ignoreSignatures: true };
|
|
450
576
|
|
|
451
577
|
const args = [
|
|
@@ -454,17 +580,13 @@ export class SequencerPublisher {
|
|
|
454
580
|
[], // no signers
|
|
455
581
|
Signature.empty().toViemSignature(),
|
|
456
582
|
`0x${'0'.repeat(64)}`, // 32 empty bytes
|
|
457
|
-
header.
|
|
583
|
+
header.blobsHash.toString(),
|
|
458
584
|
flags,
|
|
459
585
|
] as const;
|
|
460
586
|
|
|
461
587
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
462
|
-
const optsForcePendingCheckpointNumber =
|
|
463
|
-
opts?.forcePendingBlockNumber !== undefined
|
|
464
|
-
? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber)
|
|
465
|
-
: undefined;
|
|
466
588
|
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
467
|
-
|
|
589
|
+
opts?.forcePendingCheckpointNumber,
|
|
468
590
|
);
|
|
469
591
|
let balance = 0n;
|
|
470
592
|
if (this.config.fishermanMode) {
|
|
@@ -492,77 +614,109 @@ export class SequencerPublisher {
|
|
|
492
614
|
}
|
|
493
615
|
|
|
494
616
|
/**
|
|
495
|
-
* Simulate making a call to invalidate a
|
|
496
|
-
* @param
|
|
617
|
+
* Simulate making a call to invalidate a checkpoint with invalid attestations. Returns undefined if no need to invalidate.
|
|
618
|
+
* @param validationResult - The validation result indicating which checkpoint to invalidate (as returned by the archiver)
|
|
497
619
|
*/
|
|
498
|
-
public async
|
|
499
|
-
validationResult:
|
|
500
|
-
): Promise<
|
|
620
|
+
public async simulateInvalidateCheckpoint(
|
|
621
|
+
validationResult: ValidateCheckpointResult,
|
|
622
|
+
): Promise<InvalidateCheckpointRequest | undefined> {
|
|
501
623
|
if (validationResult.valid) {
|
|
502
624
|
return undefined;
|
|
503
625
|
}
|
|
504
626
|
|
|
505
|
-
const { reason,
|
|
506
|
-
const
|
|
507
|
-
const logData = { ...
|
|
627
|
+
const { reason, checkpoint } = validationResult;
|
|
628
|
+
const checkpointNumber = checkpoint.checkpointNumber;
|
|
629
|
+
const logData = { ...checkpoint, reason };
|
|
508
630
|
|
|
509
|
-
const
|
|
510
|
-
if (
|
|
631
|
+
const currentCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
632
|
+
if (currentCheckpointNumber < checkpointNumber) {
|
|
511
633
|
this.log.verbose(
|
|
512
|
-
`Skipping
|
|
513
|
-
{
|
|
634
|
+
`Skipping checkpoint ${checkpointNumber} invalidation since it has already been removed from the pending chain`,
|
|
635
|
+
{ currentCheckpointNumber, ...logData },
|
|
514
636
|
);
|
|
515
637
|
return undefined;
|
|
516
638
|
}
|
|
517
639
|
|
|
518
|
-
const request = this.
|
|
519
|
-
this.log.debug(`Simulating invalidate
|
|
640
|
+
const request = this.buildInvalidateCheckpointRequest(validationResult);
|
|
641
|
+
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
|
|
642
|
+
|
|
643
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
520
644
|
|
|
521
645
|
try {
|
|
522
|
-
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
523
|
-
|
|
646
|
+
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
647
|
+
request,
|
|
648
|
+
undefined,
|
|
649
|
+
undefined,
|
|
650
|
+
mergeAbis([request.abi ?? [], ErrorsAbi]),
|
|
651
|
+
);
|
|
652
|
+
this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} succeeded`, {
|
|
653
|
+
...logData,
|
|
654
|
+
request,
|
|
655
|
+
gasUsed,
|
|
656
|
+
});
|
|
524
657
|
|
|
525
|
-
return {
|
|
658
|
+
return {
|
|
659
|
+
request,
|
|
660
|
+
gasUsed,
|
|
661
|
+
checkpointNumber,
|
|
662
|
+
forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
|
|
663
|
+
reason,
|
|
664
|
+
};
|
|
526
665
|
} catch (err) {
|
|
527
666
|
const viemError = formatViemError(err);
|
|
528
667
|
|
|
529
|
-
// If the error is due to the
|
|
530
|
-
// we can safely ignore it and return undefined so we go ahead with
|
|
531
|
-
if (viemError.message?.includes('
|
|
668
|
+
// If the error is due to the checkpoint not being in the pending chain, and it was indeed removed by someone else,
|
|
669
|
+
// we can safely ignore it and return undefined so we go ahead with checkpoint building.
|
|
670
|
+
if (viemError.message?.includes('Rollup__CheckpointNotInPendingChain')) {
|
|
532
671
|
this.log.verbose(
|
|
533
|
-
`Simulation for invalidate
|
|
672
|
+
`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
|
|
534
673
|
{ ...logData, request, error: viemError.message },
|
|
535
674
|
);
|
|
536
|
-
const
|
|
537
|
-
if (
|
|
538
|
-
this.log.verbose(`
|
|
675
|
+
const latestPendingCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
676
|
+
if (latestPendingCheckpointNumber < checkpointNumber) {
|
|
677
|
+
this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, { ...logData });
|
|
539
678
|
return undefined;
|
|
540
679
|
} else {
|
|
541
680
|
this.log.error(
|
|
542
|
-
`Simulation for invalidate ${
|
|
681
|
+
`Simulation for invalidate checkpoint ${checkpointNumber} failed and it is still in pending chain`,
|
|
543
682
|
viemError,
|
|
544
683
|
logData,
|
|
545
684
|
);
|
|
546
|
-
throw new Error(
|
|
547
|
-
|
|
548
|
-
|
|
685
|
+
throw new Error(
|
|
686
|
+
`Failed to simulate invalidate checkpoint ${checkpointNumber} while it is still in pending chain`,
|
|
687
|
+
{
|
|
688
|
+
cause: viemError,
|
|
689
|
+
},
|
|
690
|
+
);
|
|
549
691
|
}
|
|
550
692
|
}
|
|
551
693
|
|
|
552
|
-
// Otherwise, throw. We cannot build the next
|
|
553
|
-
this.log.error(`Simulation for invalidate
|
|
554
|
-
|
|
694
|
+
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
695
|
+
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
|
|
696
|
+
this.backupFailedTx({
|
|
697
|
+
id: keccak256(request.data!),
|
|
698
|
+
failureType: 'simulation',
|
|
699
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
700
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
701
|
+
error: { message: viemError.message, name: viemError.name },
|
|
702
|
+
context: {
|
|
703
|
+
actions: [`invalidate-${reason}`],
|
|
704
|
+
checkpointNumber,
|
|
705
|
+
sender: this.getSenderAddress().toString(),
|
|
706
|
+
},
|
|
707
|
+
});
|
|
708
|
+
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
|
|
555
709
|
}
|
|
556
710
|
}
|
|
557
711
|
|
|
558
|
-
private
|
|
712
|
+
private buildInvalidateCheckpointRequest(validationResult: ValidateCheckpointResult) {
|
|
559
713
|
if (validationResult.valid) {
|
|
560
|
-
throw new Error('Cannot invalidate a valid
|
|
714
|
+
throw new Error('Cannot invalidate a valid checkpoint');
|
|
561
715
|
}
|
|
562
716
|
|
|
563
|
-
const {
|
|
564
|
-
const logData = { ...
|
|
565
|
-
this.log.debug(`
|
|
717
|
+
const { checkpoint, committee, reason } = validationResult;
|
|
718
|
+
const logData = { ...checkpoint, reason };
|
|
719
|
+
this.log.debug(`Building invalidate checkpoint ${checkpoint.checkpointNumber} request`, logData);
|
|
566
720
|
|
|
567
721
|
const attestationsAndSigners = new CommitteeAttestationsAndSigners(
|
|
568
722
|
validationResult.attestations,
|
|
@@ -570,14 +724,14 @@ export class SequencerPublisher {
|
|
|
570
724
|
|
|
571
725
|
if (reason === 'invalid-attestation') {
|
|
572
726
|
return this.rollupContract.buildInvalidateBadAttestationRequest(
|
|
573
|
-
|
|
727
|
+
checkpoint.checkpointNumber,
|
|
574
728
|
attestationsAndSigners,
|
|
575
729
|
committee,
|
|
576
730
|
validationResult.invalidIndex,
|
|
577
731
|
);
|
|
578
732
|
} else if (reason === 'insufficient-attestations') {
|
|
579
733
|
return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
|
|
580
|
-
|
|
734
|
+
checkpoint.checkpointNumber,
|
|
581
735
|
attestationsAndSigners,
|
|
582
736
|
committee,
|
|
583
737
|
);
|
|
@@ -588,31 +742,16 @@ export class SequencerPublisher {
|
|
|
588
742
|
}
|
|
589
743
|
|
|
590
744
|
/** Simulates `propose` to make sure that the checkpoint is valid for submission */
|
|
745
|
+
@trackSpan('SequencerPublisher.validateCheckpointForSubmission')
|
|
591
746
|
public async validateCheckpointForSubmission(
|
|
592
747
|
checkpoint: Checkpoint,
|
|
593
748
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
594
749
|
attestationsAndSignersSignature: Signature,
|
|
595
|
-
options: {
|
|
750
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
596
751
|
): Promise<bigint> {
|
|
597
752
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
598
|
-
|
|
599
|
-
// TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
|
|
600
|
-
// If we have no attestations, we still need to provide the empty attestations
|
|
601
|
-
// so that the committee is recalculated correctly
|
|
602
|
-
// const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
603
|
-
// if (ignoreSignatures) {
|
|
604
|
-
// const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
|
|
605
|
-
// if (!committee) {
|
|
606
|
-
// this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
607
|
-
// throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
608
|
-
// }
|
|
609
|
-
// attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
610
|
-
// CommitteeAttestation.fromAddress(committeeMember),
|
|
611
|
-
// );
|
|
612
|
-
// }
|
|
613
|
-
|
|
614
753
|
const blobFields = checkpoint.toBlobFields();
|
|
615
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
754
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
616
755
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
617
756
|
|
|
618
757
|
const args = [
|
|
@@ -620,7 +759,7 @@ export class SequencerPublisher {
|
|
|
620
759
|
header: checkpoint.header.toViem(),
|
|
621
760
|
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
622
761
|
oracleInput: {
|
|
623
|
-
feeAssetPriceModifier:
|
|
762
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
624
763
|
},
|
|
625
764
|
},
|
|
626
765
|
attestationsAndSigners.getPackedAttestations(),
|
|
@@ -669,6 +808,32 @@ export class SequencerPublisher {
|
|
|
669
808
|
return false;
|
|
670
809
|
}
|
|
671
810
|
|
|
811
|
+
// Check if payload was already submitted to governance
|
|
812
|
+
const cacheKey = payload.toString();
|
|
813
|
+
if (!this.payloadProposedCache.has(cacheKey)) {
|
|
814
|
+
try {
|
|
815
|
+
const l1StartBlock = await this.rollupContract.getL1StartBlock();
|
|
816
|
+
const proposed = await retry(
|
|
817
|
+
() => base.hasPayloadBeenProposed(payload.toString(), l1StartBlock),
|
|
818
|
+
'Check if payload was proposed',
|
|
819
|
+
makeBackoff([0, 1, 2]),
|
|
820
|
+
this.log,
|
|
821
|
+
true,
|
|
822
|
+
);
|
|
823
|
+
if (proposed) {
|
|
824
|
+
this.payloadProposedCache.add(cacheKey);
|
|
825
|
+
}
|
|
826
|
+
} catch (err) {
|
|
827
|
+
this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
|
|
828
|
+
return false;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
if (this.payloadProposedCache.has(cacheKey)) {
|
|
833
|
+
this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
|
|
834
|
+
return false;
|
|
835
|
+
}
|
|
836
|
+
|
|
672
837
|
const cachedLastVote = this.lastActions[signalType];
|
|
673
838
|
this.lastActions[signalType] = slotNumber;
|
|
674
839
|
const action = signalType;
|
|
@@ -687,11 +852,26 @@ export class SequencerPublisher {
|
|
|
687
852
|
lastValidL2Slot: slotNumber,
|
|
688
853
|
});
|
|
689
854
|
|
|
855
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
856
|
+
|
|
690
857
|
try {
|
|
691
|
-
await this.l1TxUtils.simulate(request, { time: timestamp }, [], ErrorsAbi);
|
|
858
|
+
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
692
859
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
693
860
|
} catch (err) {
|
|
694
|
-
|
|
861
|
+
const viemError = formatViemError(err);
|
|
862
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError);
|
|
863
|
+
this.backupFailedTx({
|
|
864
|
+
id: keccak256(request.data!),
|
|
865
|
+
failureType: 'simulation',
|
|
866
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
867
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
868
|
+
error: { message: viemError.message, name: viemError.name },
|
|
869
|
+
context: {
|
|
870
|
+
actions: [action],
|
|
871
|
+
slot: slotNumber,
|
|
872
|
+
sender: this.getSenderAddress().toString(),
|
|
873
|
+
},
|
|
874
|
+
});
|
|
695
875
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
696
876
|
}
|
|
697
877
|
|
|
@@ -891,19 +1071,20 @@ export class SequencerPublisher {
|
|
|
891
1071
|
checkpoint: Checkpoint,
|
|
892
1072
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
893
1073
|
attestationsAndSignersSignature: Signature,
|
|
894
|
-
opts: { txTimeoutAt?: Date;
|
|
1074
|
+
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
895
1075
|
): Promise<void> {
|
|
896
1076
|
const checkpointHeader = checkpoint.header;
|
|
897
1077
|
|
|
898
1078
|
const blobFields = checkpoint.toBlobFields();
|
|
899
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
1079
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
900
1080
|
|
|
901
|
-
const proposeTxArgs = {
|
|
1081
|
+
const proposeTxArgs: L1ProcessArgs = {
|
|
902
1082
|
header: checkpointHeader,
|
|
903
1083
|
archive: checkpoint.archive.root.toBuffer(),
|
|
904
1084
|
blobs,
|
|
905
1085
|
attestationsAndSigners,
|
|
906
1086
|
attestationsAndSignersSignature,
|
|
1087
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
907
1088
|
};
|
|
908
1089
|
|
|
909
1090
|
let ts: bigint;
|
|
@@ -924,7 +1105,7 @@ export class SequencerPublisher {
|
|
|
924
1105
|
this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
925
1106
|
...checkpoint.getStats(),
|
|
926
1107
|
slotNumber: checkpoint.header.slotNumber,
|
|
927
|
-
|
|
1108
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
928
1109
|
});
|
|
929
1110
|
throw err;
|
|
930
1111
|
}
|
|
@@ -933,7 +1114,10 @@ export class SequencerPublisher {
|
|
|
933
1114
|
await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
|
|
934
1115
|
}
|
|
935
1116
|
|
|
936
|
-
public
|
|
1117
|
+
public enqueueInvalidateCheckpoint(
|
|
1118
|
+
request: InvalidateCheckpointRequest | undefined,
|
|
1119
|
+
opts: { txTimeoutAt?: Date } = {},
|
|
1120
|
+
) {
|
|
937
1121
|
if (!request) {
|
|
938
1122
|
return;
|
|
939
1123
|
}
|
|
@@ -941,9 +1125,9 @@ export class SequencerPublisher {
|
|
|
941
1125
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
942
1126
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(request.gasUsed) * 64) / 63)));
|
|
943
1127
|
|
|
944
|
-
const { gasUsed,
|
|
945
|
-
const logData = { gasUsed,
|
|
946
|
-
this.log.verbose(`Enqueuing invalidate
|
|
1128
|
+
const { gasUsed, checkpointNumber } = request;
|
|
1129
|
+
const logData = { gasUsed, checkpointNumber, gasLimit, opts };
|
|
1130
|
+
this.log.verbose(`Enqueuing invalidate checkpoint request`, logData);
|
|
947
1131
|
this.addRequest({
|
|
948
1132
|
action: `invalidate-by-${request.reason}`,
|
|
949
1133
|
request: request.request,
|
|
@@ -956,9 +1140,9 @@ export class SequencerPublisher {
|
|
|
956
1140
|
result.receipt.status === 'success' &&
|
|
957
1141
|
tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointInvalidated');
|
|
958
1142
|
if (!success) {
|
|
959
|
-
this.log.warn(`Invalidate
|
|
1143
|
+
this.log.warn(`Invalidate checkpoint ${request.checkpointNumber} failed`, { ...result, ...logData });
|
|
960
1144
|
} else {
|
|
961
|
-
this.log.info(`Invalidate
|
|
1145
|
+
this.log.info(`Invalidate checkpoint ${request.checkpointNumber} succeeded`, { ...result, ...logData });
|
|
962
1146
|
}
|
|
963
1147
|
return !!success;
|
|
964
1148
|
},
|
|
@@ -983,13 +1167,30 @@ export class SequencerPublisher {
|
|
|
983
1167
|
|
|
984
1168
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
985
1169
|
|
|
1170
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1171
|
+
|
|
986
1172
|
let gasUsed: bigint;
|
|
1173
|
+
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
987
1174
|
try {
|
|
988
|
-
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [],
|
|
1175
|
+
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi)); // TODO(palla/slash): Check the timestamp logic
|
|
989
1176
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
990
1177
|
} catch (err) {
|
|
991
|
-
const viemError = formatViemError(err);
|
|
1178
|
+
const viemError = formatViemError(err, simulateAbi);
|
|
992
1179
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1180
|
+
|
|
1181
|
+
this.backupFailedTx({
|
|
1182
|
+
id: keccak256(request.data!),
|
|
1183
|
+
failureType: 'simulation',
|
|
1184
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
1185
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1186
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1187
|
+
context: {
|
|
1188
|
+
actions: [action],
|
|
1189
|
+
slot: slotNumber,
|
|
1190
|
+
sender: this.getSenderAddress().toString(),
|
|
1191
|
+
},
|
|
1192
|
+
});
|
|
1193
|
+
|
|
993
1194
|
return false;
|
|
994
1195
|
}
|
|
995
1196
|
|
|
@@ -997,10 +1198,14 @@ export class SequencerPublisher {
|
|
|
997
1198
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(gasUsed) * 64) / 63)));
|
|
998
1199
|
logData.gasLimit = gasLimit;
|
|
999
1200
|
|
|
1201
|
+
// Store the ABI used for simulation on the request so Multicall3.forward can decode errors
|
|
1202
|
+
// when the tx is sent and a revert is diagnosed via simulation.
|
|
1203
|
+
const requestWithAbi = { ...request, abi: simulateAbi };
|
|
1204
|
+
|
|
1000
1205
|
this.log.debug(`Enqueuing ${action}`, logData);
|
|
1001
1206
|
this.addRequest({
|
|
1002
1207
|
action,
|
|
1003
|
-
request,
|
|
1208
|
+
request: requestWithAbi,
|
|
1004
1209
|
gasConfig: { gasLimit },
|
|
1005
1210
|
lastValidL2Slot: slotNumber,
|
|
1006
1211
|
checkSuccess: (_req, result) => {
|
|
@@ -1037,7 +1242,7 @@ export class SequencerPublisher {
|
|
|
1037
1242
|
private async prepareProposeTx(
|
|
1038
1243
|
encodedData: L1ProcessArgs,
|
|
1039
1244
|
timestamp: bigint,
|
|
1040
|
-
options: {
|
|
1245
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1041
1246
|
) {
|
|
1042
1247
|
const kzg = Blob.getViemKzgInstance();
|
|
1043
1248
|
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
@@ -1069,9 +1274,27 @@ export class SequencerPublisher {
|
|
|
1069
1274
|
kzg,
|
|
1070
1275
|
},
|
|
1071
1276
|
)
|
|
1072
|
-
.catch(err => {
|
|
1073
|
-
const
|
|
1074
|
-
this.log.error(`Failed to validate blobs`, message, { metaMessages });
|
|
1277
|
+
.catch(async err => {
|
|
1278
|
+
const viemError = formatViemError(err);
|
|
1279
|
+
this.log.error(`Failed to validate blobs`, viemError.message, { metaMessages: viemError.metaMessages });
|
|
1280
|
+
const validateBlobsData = encodeFunctionData({
|
|
1281
|
+
abi: RollupAbi,
|
|
1282
|
+
functionName: 'validateBlobs',
|
|
1283
|
+
args: [blobInput],
|
|
1284
|
+
});
|
|
1285
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1286
|
+
this.backupFailedTx({
|
|
1287
|
+
id: keccak256(validateBlobsData),
|
|
1288
|
+
failureType: 'simulation',
|
|
1289
|
+
request: { to: this.rollupContract.address as Hex, data: validateBlobsData },
|
|
1290
|
+
blobData: encodedData.blobs.map(b => toHex(b.data)) as Hex[],
|
|
1291
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1292
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1293
|
+
context: {
|
|
1294
|
+
actions: ['validate-blobs'],
|
|
1295
|
+
sender: this.getSenderAddress().toString(),
|
|
1296
|
+
},
|
|
1297
|
+
});
|
|
1075
1298
|
throw new Error('Failed to validate blobs');
|
|
1076
1299
|
});
|
|
1077
1300
|
}
|
|
@@ -1082,8 +1305,7 @@ export class SequencerPublisher {
|
|
|
1082
1305
|
header: encodedData.header.toViem(),
|
|
1083
1306
|
archive: toHex(encodedData.archive),
|
|
1084
1307
|
oracleInput: {
|
|
1085
|
-
|
|
1086
|
-
feeAssetPriceModifier: 0n,
|
|
1308
|
+
feeAssetPriceModifier: encodedData.feeAssetPriceModifier,
|
|
1087
1309
|
},
|
|
1088
1310
|
},
|
|
1089
1311
|
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
@@ -1109,7 +1331,7 @@ export class SequencerPublisher {
|
|
|
1109
1331
|
readonly header: ViemHeader;
|
|
1110
1332
|
readonly archive: `0x${string}`;
|
|
1111
1333
|
readonly oracleInput: {
|
|
1112
|
-
readonly feeAssetPriceModifier:
|
|
1334
|
+
readonly feeAssetPriceModifier: bigint;
|
|
1113
1335
|
};
|
|
1114
1336
|
},
|
|
1115
1337
|
ViemCommitteeAttestations,
|
|
@@ -1118,7 +1340,7 @@ export class SequencerPublisher {
|
|
|
1118
1340
|
`0x${string}`,
|
|
1119
1341
|
],
|
|
1120
1342
|
timestamp: bigint,
|
|
1121
|
-
options: {
|
|
1343
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1122
1344
|
) {
|
|
1123
1345
|
const rollupData = encodeFunctionData({
|
|
1124
1346
|
abi: RollupAbi,
|
|
@@ -1127,13 +1349,9 @@ export class SequencerPublisher {
|
|
|
1127
1349
|
});
|
|
1128
1350
|
|
|
1129
1351
|
// override the pending checkpoint number if requested
|
|
1130
|
-
const optsForcePendingCheckpointNumber =
|
|
1131
|
-
options.forcePendingBlockNumber !== undefined
|
|
1132
|
-
? CheckpointNumber.fromBlockNumber(options.forcePendingBlockNumber)
|
|
1133
|
-
: undefined;
|
|
1134
1352
|
const forcePendingCheckpointNumberStateDiff = (
|
|
1135
|
-
|
|
1136
|
-
? await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
1353
|
+
options.forcePendingCheckpointNumber !== undefined
|
|
1354
|
+
? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber)
|
|
1137
1355
|
: []
|
|
1138
1356
|
).flatMap(override => override.stateDiff ?? []);
|
|
1139
1357
|
|
|
@@ -1155,25 +1373,27 @@ export class SequencerPublisher {
|
|
|
1155
1373
|
});
|
|
1156
1374
|
}
|
|
1157
1375
|
|
|
1376
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1377
|
+
|
|
1158
1378
|
const simulationResult = await this.l1TxUtils
|
|
1159
1379
|
.simulate(
|
|
1160
1380
|
{
|
|
1161
1381
|
to: this.rollupContract.address,
|
|
1162
1382
|
data: rollupData,
|
|
1163
|
-
gas:
|
|
1383
|
+
gas: MAX_L1_TX_LIMIT,
|
|
1164
1384
|
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1165
1385
|
},
|
|
1166
1386
|
{
|
|
1167
1387
|
// @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
|
|
1168
1388
|
time: timestamp + 1n,
|
|
1169
1389
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1170
|
-
gasLimit:
|
|
1390
|
+
gasLimit: MAX_L1_TX_LIMIT * 2n,
|
|
1171
1391
|
},
|
|
1172
1392
|
stateOverrides,
|
|
1173
1393
|
RollupAbi,
|
|
1174
1394
|
{
|
|
1175
1395
|
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
1176
|
-
fallbackGasEstimate:
|
|
1396
|
+
fallbackGasEstimate: MAX_L1_TX_LIMIT,
|
|
1177
1397
|
},
|
|
1178
1398
|
)
|
|
1179
1399
|
.catch(err => {
|
|
@@ -1183,11 +1403,23 @@ export class SequencerPublisher {
|
|
|
1183
1403
|
this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
|
|
1184
1404
|
// Return a minimal simulation result with the fallback gas estimate
|
|
1185
1405
|
return {
|
|
1186
|
-
gasUsed:
|
|
1406
|
+
gasUsed: MAX_L1_TX_LIMIT,
|
|
1187
1407
|
logs: [],
|
|
1188
1408
|
};
|
|
1189
1409
|
}
|
|
1190
1410
|
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1411
|
+
this.backupFailedTx({
|
|
1412
|
+
id: keccak256(rollupData),
|
|
1413
|
+
failureType: 'simulation',
|
|
1414
|
+
request: { to: this.rollupContract.address, data: rollupData },
|
|
1415
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1416
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1417
|
+
context: {
|
|
1418
|
+
actions: ['propose'],
|
|
1419
|
+
slot: Number(args[0].header.slotNumber),
|
|
1420
|
+
sender: this.getSenderAddress().toString(),
|
|
1421
|
+
},
|
|
1422
|
+
});
|
|
1191
1423
|
throw err;
|
|
1192
1424
|
});
|
|
1193
1425
|
|
|
@@ -1197,7 +1429,7 @@ export class SequencerPublisher {
|
|
|
1197
1429
|
private async addProposeTx(
|
|
1198
1430
|
checkpoint: Checkpoint,
|
|
1199
1431
|
encodedData: L1ProcessArgs,
|
|
1200
|
-
opts: { txTimeoutAt?: Date;
|
|
1432
|
+
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
1201
1433
|
timestamp: bigint,
|
|
1202
1434
|
): Promise<void> {
|
|
1203
1435
|
const slot = checkpoint.header.slotNumber;
|