@aztec/sequencer-client 0.0.1-commit.e6bd8901 → 0.0.1-commit.ec5f612
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/client/sequencer-client.d.ts +12 -7
- 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 +17 -12
- package/dest/global_variable_builder/global_builder.d.ts +2 -4
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- 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 +12 -4
- package/dest/publisher/sequencer-publisher.d.ts +22 -8
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +297 -47
- package/dest/sequencer/checkpoint_proposal_job.d.ts +32 -9
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +114 -59
- package/dest/sequencer/metrics.d.ts +17 -5
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +111 -30
- package/dest/sequencer/sequencer.d.ts +17 -7
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +30 -27
- 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 +3 -5
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts +10 -5
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +24 -10
- package/dest/test/utils.d.ts +3 -3
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +5 -4
- package/package.json +28 -28
- package/src/client/sequencer-client.ts +25 -7
- package/src/config.ts +26 -19
- package/src/global_variable_builder/global_builder.ts +1 -1
- 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 +7 -3
- package/src/publisher/sequencer-publisher.ts +274 -53
- package/src/sequencer/checkpoint_proposal_job.ts +159 -76
- package/src/sequencer/metrics.ts +124 -32
- package/src/sequencer/sequencer.ts +40 -32
- package/src/sequencer/timetable.ts +7 -6
- package/src/test/index.ts +2 -4
- package/src/test/mock_checkpoint_builder.ts +34 -9
- package/src/test/utils.ts +5 -2
|
@@ -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,11 +19,12 @@ 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
30
|
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
@@ -31,6 +33,7 @@ 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';
|
|
@@ -42,9 +45,19 @@ import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
|
42
45
|
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
43
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 = [
|
|
@@ -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,7 +149,7 @@ 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;
|
|
@@ -144,11 +160,12 @@ export class SequencerPublisher {
|
|
|
144
160
|
protected requests: RequestWithExpiry[] = [];
|
|
145
161
|
|
|
146
162
|
constructor(
|
|
147
|
-
private config:
|
|
163
|
+
private config: Pick<SequencerPublisherConfig, 'fishermanMode' | 'l1TxFailedStore'> &
|
|
164
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration'> & { l1ChainId: number },
|
|
148
165
|
deps: {
|
|
149
166
|
telemetry?: TelemetryClient;
|
|
150
167
|
blobClient: BlobClientInterface;
|
|
151
|
-
l1TxUtils:
|
|
168
|
+
l1TxUtils: L1TxUtils;
|
|
152
169
|
rollupContract: RollupContract;
|
|
153
170
|
slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
154
171
|
governanceProposerContract: GovernanceProposerContract;
|
|
@@ -192,12 +209,52 @@ export class SequencerPublisher {
|
|
|
192
209
|
createLogger('sequencer:publisher:fee-analyzer'),
|
|
193
210
|
);
|
|
194
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
|
+
});
|
|
195
244
|
}
|
|
196
245
|
|
|
197
246
|
public getRollupContract(): RollupContract {
|
|
198
247
|
return this.rollupContract;
|
|
199
248
|
}
|
|
200
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
|
+
|
|
201
258
|
public getSenderAddress() {
|
|
202
259
|
return this.l1TxUtils.getSenderAddress();
|
|
203
260
|
}
|
|
@@ -273,7 +330,7 @@ export class SequencerPublisher {
|
|
|
273
330
|
// Start the analysis
|
|
274
331
|
const analysisId = await this.l1FeeAnalyzer.startAnalysis(
|
|
275
332
|
l2SlotNumber,
|
|
276
|
-
gasLimit > 0n ? gasLimit :
|
|
333
|
+
gasLimit > 0n ? gasLimit : MAX_L1_TX_LIMIT,
|
|
277
334
|
l1Requests,
|
|
278
335
|
blobConfig,
|
|
279
336
|
onComplete,
|
|
@@ -346,7 +403,16 @@ export class SequencerPublisher {
|
|
|
346
403
|
|
|
347
404
|
// Merge gasConfigs. Yields the sum of gasLimits, and the earliest txTimeoutAt, or undefined if no gasConfig sets them.
|
|
348
405
|
const gasLimits = gasConfigs.map(g => g?.gasLimit).filter((g): g is bigint => g !== undefined);
|
|
349
|
-
|
|
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
|
+
}
|
|
350
416
|
const txTimeoutAts = gasConfigs.map(g => g?.txTimeoutAt).filter((g): g is Date => g !== undefined);
|
|
351
417
|
const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map(g => g.getTime()))) : undefined; // earliest
|
|
352
418
|
const txConfig: RequestWithExpiry['gasConfig'] = { gasLimit, txTimeoutAt };
|
|
@@ -356,6 +422,21 @@ export class SequencerPublisher {
|
|
|
356
422
|
validRequests.sort((a, b) => compareActions(a.action, b.action));
|
|
357
423
|
|
|
358
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
|
+
|
|
359
440
|
this.log.debug('Forwarding transactions', {
|
|
360
441
|
validRequests: validRequests.map(request => request.action),
|
|
361
442
|
txConfig,
|
|
@@ -368,7 +449,12 @@ export class SequencerPublisher {
|
|
|
368
449
|
this.rollupContract.address,
|
|
369
450
|
this.log,
|
|
370
451
|
);
|
|
371
|
-
const
|
|
452
|
+
const txContext = { multicallData, blobData: blobDataHex, l1BlockNumber };
|
|
453
|
+
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(
|
|
454
|
+
validRequests,
|
|
455
|
+
result,
|
|
456
|
+
txContext,
|
|
457
|
+
);
|
|
372
458
|
return { result, expiredActions, sentActions: validActions, successfulActions, failedActions };
|
|
373
459
|
} catch (err) {
|
|
374
460
|
const viemError = formatViemError(err);
|
|
@@ -388,11 +474,25 @@ export class SequencerPublisher {
|
|
|
388
474
|
|
|
389
475
|
private callbackBundledTransactions(
|
|
390
476
|
requests: RequestWithExpiry[],
|
|
391
|
-
result
|
|
477
|
+
result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError | undefined,
|
|
478
|
+
txContext: { multicallData: Hex; blobData?: Hex[]; l1BlockNumber: bigint },
|
|
392
479
|
) {
|
|
393
480
|
const actionsListStr = requests.map(r => r.action).join(', ');
|
|
394
481
|
if (result instanceof FormattedViemError) {
|
|
395
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
|
+
});
|
|
396
496
|
return { failedActions: requests.map(r => r.action) };
|
|
397
497
|
} else {
|
|
398
498
|
this.log.verbose(`Published bundled transactions (${actionsListStr})`, { result, requests });
|
|
@@ -405,6 +505,30 @@ export class SequencerPublisher {
|
|
|
405
505
|
failedActions.push(request.action);
|
|
406
506
|
}
|
|
407
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
|
+
}
|
|
408
532
|
return { successfulActions, failedActions };
|
|
409
533
|
}
|
|
410
534
|
}
|
|
@@ -516,8 +640,15 @@ export class SequencerPublisher {
|
|
|
516
640
|
const request = this.buildInvalidateCheckpointRequest(validationResult);
|
|
517
641
|
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
|
|
518
642
|
|
|
643
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
644
|
+
|
|
519
645
|
try {
|
|
520
|
-
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
646
|
+
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
647
|
+
request,
|
|
648
|
+
undefined,
|
|
649
|
+
undefined,
|
|
650
|
+
mergeAbis([request.abi ?? [], ErrorsAbi]),
|
|
651
|
+
);
|
|
521
652
|
this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} succeeded`, {
|
|
522
653
|
...logData,
|
|
523
654
|
request,
|
|
@@ -536,7 +667,7 @@ export class SequencerPublisher {
|
|
|
536
667
|
|
|
537
668
|
// If the error is due to the checkpoint not being in the pending chain, and it was indeed removed by someone else,
|
|
538
669
|
// we can safely ignore it and return undefined so we go ahead with checkpoint building.
|
|
539
|
-
if (viemError.message?.includes('
|
|
670
|
+
if (viemError.message?.includes('Rollup__CheckpointNotInPendingChain')) {
|
|
540
671
|
this.log.verbose(
|
|
541
672
|
`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
|
|
542
673
|
{ ...logData, request, error: viemError.message },
|
|
@@ -562,6 +693,18 @@ export class SequencerPublisher {
|
|
|
562
693
|
|
|
563
694
|
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
564
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
|
+
});
|
|
565
708
|
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
|
|
566
709
|
}
|
|
567
710
|
}
|
|
@@ -607,24 +750,8 @@ export class SequencerPublisher {
|
|
|
607
750
|
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
608
751
|
): Promise<bigint> {
|
|
609
752
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
610
|
-
|
|
611
|
-
// TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
|
|
612
|
-
// If we have no attestations, we still need to provide the empty attestations
|
|
613
|
-
// so that the committee is recalculated correctly
|
|
614
|
-
// const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
615
|
-
// if (ignoreSignatures) {
|
|
616
|
-
// const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
|
|
617
|
-
// if (!committee) {
|
|
618
|
-
// this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
619
|
-
// throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
620
|
-
// }
|
|
621
|
-
// attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
622
|
-
// CommitteeAttestation.fromAddress(committeeMember),
|
|
623
|
-
// );
|
|
624
|
-
// }
|
|
625
|
-
|
|
626
753
|
const blobFields = checkpoint.toBlobFields();
|
|
627
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
754
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
628
755
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
629
756
|
|
|
630
757
|
const args = [
|
|
@@ -632,7 +759,7 @@ export class SequencerPublisher {
|
|
|
632
759
|
header: checkpoint.header.toViem(),
|
|
633
760
|
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
634
761
|
oracleInput: {
|
|
635
|
-
feeAssetPriceModifier:
|
|
762
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
636
763
|
},
|
|
637
764
|
},
|
|
638
765
|
attestationsAndSigners.getPackedAttestations(),
|
|
@@ -681,6 +808,32 @@ export class SequencerPublisher {
|
|
|
681
808
|
return false;
|
|
682
809
|
}
|
|
683
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
|
+
|
|
684
837
|
const cachedLastVote = this.lastActions[signalType];
|
|
685
838
|
this.lastActions[signalType] = slotNumber;
|
|
686
839
|
const action = signalType;
|
|
@@ -699,11 +852,26 @@ export class SequencerPublisher {
|
|
|
699
852
|
lastValidL2Slot: slotNumber,
|
|
700
853
|
});
|
|
701
854
|
|
|
855
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
856
|
+
|
|
702
857
|
try {
|
|
703
|
-
await this.l1TxUtils.simulate(request, { time: timestamp }, [], ErrorsAbi);
|
|
858
|
+
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
704
859
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
705
860
|
} catch (err) {
|
|
706
|
-
|
|
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
|
+
});
|
|
707
875
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
708
876
|
}
|
|
709
877
|
|
|
@@ -908,14 +1076,15 @@ export class SequencerPublisher {
|
|
|
908
1076
|
const checkpointHeader = checkpoint.header;
|
|
909
1077
|
|
|
910
1078
|
const blobFields = checkpoint.toBlobFields();
|
|
911
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
1079
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
912
1080
|
|
|
913
|
-
const proposeTxArgs = {
|
|
1081
|
+
const proposeTxArgs: L1ProcessArgs = {
|
|
914
1082
|
header: checkpointHeader,
|
|
915
1083
|
archive: checkpoint.archive.root.toBuffer(),
|
|
916
1084
|
blobs,
|
|
917
1085
|
attestationsAndSigners,
|
|
918
1086
|
attestationsAndSignersSignature,
|
|
1087
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
919
1088
|
};
|
|
920
1089
|
|
|
921
1090
|
let ts: bigint;
|
|
@@ -998,13 +1167,30 @@ export class SequencerPublisher {
|
|
|
998
1167
|
|
|
999
1168
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
1000
1169
|
|
|
1170
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1171
|
+
|
|
1001
1172
|
let gasUsed: bigint;
|
|
1173
|
+
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
1002
1174
|
try {
|
|
1003
|
-
({ 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
|
|
1004
1176
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
1005
1177
|
} catch (err) {
|
|
1006
|
-
const viemError = formatViemError(err);
|
|
1178
|
+
const viemError = formatViemError(err, simulateAbi);
|
|
1007
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
|
+
|
|
1008
1194
|
return false;
|
|
1009
1195
|
}
|
|
1010
1196
|
|
|
@@ -1012,10 +1198,14 @@ export class SequencerPublisher {
|
|
|
1012
1198
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(gasUsed) * 64) / 63)));
|
|
1013
1199
|
logData.gasLimit = gasLimit;
|
|
1014
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
|
+
|
|
1015
1205
|
this.log.debug(`Enqueuing ${action}`, logData);
|
|
1016
1206
|
this.addRequest({
|
|
1017
1207
|
action,
|
|
1018
|
-
request,
|
|
1208
|
+
request: requestWithAbi,
|
|
1019
1209
|
gasConfig: { gasLimit },
|
|
1020
1210
|
lastValidL2Slot: slotNumber,
|
|
1021
1211
|
checkSuccess: (_req, result) => {
|
|
@@ -1084,9 +1274,27 @@ export class SequencerPublisher {
|
|
|
1084
1274
|
kzg,
|
|
1085
1275
|
},
|
|
1086
1276
|
)
|
|
1087
|
-
.catch(err => {
|
|
1088
|
-
const
|
|
1089
|
-
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
|
+
});
|
|
1090
1298
|
throw new Error('Failed to validate blobs');
|
|
1091
1299
|
});
|
|
1092
1300
|
}
|
|
@@ -1097,8 +1305,7 @@ export class SequencerPublisher {
|
|
|
1097
1305
|
header: encodedData.header.toViem(),
|
|
1098
1306
|
archive: toHex(encodedData.archive),
|
|
1099
1307
|
oracleInput: {
|
|
1100
|
-
|
|
1101
|
-
feeAssetPriceModifier: 0n,
|
|
1308
|
+
feeAssetPriceModifier: encodedData.feeAssetPriceModifier,
|
|
1102
1309
|
},
|
|
1103
1310
|
},
|
|
1104
1311
|
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
@@ -1124,7 +1331,7 @@ export class SequencerPublisher {
|
|
|
1124
1331
|
readonly header: ViemHeader;
|
|
1125
1332
|
readonly archive: `0x${string}`;
|
|
1126
1333
|
readonly oracleInput: {
|
|
1127
|
-
readonly feeAssetPriceModifier:
|
|
1334
|
+
readonly feeAssetPriceModifier: bigint;
|
|
1128
1335
|
};
|
|
1129
1336
|
},
|
|
1130
1337
|
ViemCommitteeAttestations,
|
|
@@ -1166,25 +1373,27 @@ export class SequencerPublisher {
|
|
|
1166
1373
|
});
|
|
1167
1374
|
}
|
|
1168
1375
|
|
|
1376
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1377
|
+
|
|
1169
1378
|
const simulationResult = await this.l1TxUtils
|
|
1170
1379
|
.simulate(
|
|
1171
1380
|
{
|
|
1172
1381
|
to: this.rollupContract.address,
|
|
1173
1382
|
data: rollupData,
|
|
1174
|
-
gas:
|
|
1383
|
+
gas: MAX_L1_TX_LIMIT,
|
|
1175
1384
|
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1176
1385
|
},
|
|
1177
1386
|
{
|
|
1178
1387
|
// @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
|
|
1179
1388
|
time: timestamp + 1n,
|
|
1180
1389
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1181
|
-
gasLimit:
|
|
1390
|
+
gasLimit: MAX_L1_TX_LIMIT * 2n,
|
|
1182
1391
|
},
|
|
1183
1392
|
stateOverrides,
|
|
1184
1393
|
RollupAbi,
|
|
1185
1394
|
{
|
|
1186
1395
|
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
1187
|
-
fallbackGasEstimate:
|
|
1396
|
+
fallbackGasEstimate: MAX_L1_TX_LIMIT,
|
|
1188
1397
|
},
|
|
1189
1398
|
)
|
|
1190
1399
|
.catch(err => {
|
|
@@ -1194,11 +1403,23 @@ export class SequencerPublisher {
|
|
|
1194
1403
|
this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
|
|
1195
1404
|
// Return a minimal simulation result with the fallback gas estimate
|
|
1196
1405
|
return {
|
|
1197
|
-
gasUsed:
|
|
1406
|
+
gasUsed: MAX_L1_TX_LIMIT,
|
|
1198
1407
|
logs: [],
|
|
1199
1408
|
};
|
|
1200
1409
|
}
|
|
1201
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
|
+
});
|
|
1202
1423
|
throw err;
|
|
1203
1424
|
});
|
|
1204
1425
|
|