@aztec/sequencer-client 0.0.1-commit.0c875d939 → 0.0.1-commit.10bd49492
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 +23 -7
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +99 -16
- package/dest/config.d.ts +24 -5
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +36 -20
- 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 +27 -2
- package/dest/publisher/sequencer-publisher.d.ts +19 -7
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +285 -25
- package/dest/sequencer/checkpoint_proposal_job.d.ts +2 -4
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +99 -47
- package/dest/sequencer/metrics.d.ts +14 -5
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +61 -15
- package/dest/sequencer/sequencer.d.ts +24 -13
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +35 -39
- package/dest/sequencer/timetable.d.ts +4 -3
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +6 -7
- package/dest/sequencer/types.d.ts +2 -2
- package/dest/sequencer/types.d.ts.map +1 -1
- 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 +4 -6
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +39 -30
- package/package.json +28 -28
- package/src/client/sequencer-client.ts +135 -18
- package/src/config.ts +45 -27
- 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 +38 -6
- package/src/publisher/sequencer-publisher.ts +273 -38
- package/src/sequencer/checkpoint_proposal_job.ts +133 -56
- package/src/sequencer/metrics.ts +68 -18
- package/src/sequencer/sequencer.ts +44 -45
- package/src/sequencer/timetable.ts +7 -7
- package/src/sequencer/types.ts +1 -1
- package/src/test/index.ts +2 -4
- package/src/test/mock_checkpoint_builder.ts +48 -45
|
@@ -19,20 +19,22 @@ import {
|
|
|
19
19
|
type L1BlobInputs,
|
|
20
20
|
type L1TxConfig,
|
|
21
21
|
type L1TxRequest,
|
|
22
|
+
type L1TxUtils,
|
|
22
23
|
MAX_L1_TX_LIMIT,
|
|
23
24
|
type TransactionStats,
|
|
24
25
|
WEI_CONST,
|
|
25
26
|
} from '@aztec/ethereum/l1-tx-utils';
|
|
26
|
-
import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs';
|
|
27
27
|
import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
28
28
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
29
29
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
30
30
|
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
31
31
|
import { pick } from '@aztec/foundation/collection';
|
|
32
32
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
33
|
+
import { TimeoutError } from '@aztec/foundation/error';
|
|
33
34
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
34
35
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
35
36
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
37
|
+
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
36
38
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
37
39
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
38
40
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
@@ -44,9 +46,19 @@ import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
|
44
46
|
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
45
47
|
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
46
48
|
|
|
47
|
-
import {
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
import {
|
|
50
|
+
type Hex,
|
|
51
|
+
type StateOverride,
|
|
52
|
+
type TransactionReceipt,
|
|
53
|
+
type TypedDataDefinition,
|
|
54
|
+
encodeFunctionData,
|
|
55
|
+
keccak256,
|
|
56
|
+
multicall3Abi,
|
|
57
|
+
toHex,
|
|
58
|
+
} from 'viem';
|
|
59
|
+
|
|
60
|
+
import type { SequencerPublisherConfig } from './config.js';
|
|
61
|
+
import { type FailedL1Tx, type L1TxFailedStore, createL1TxFailedStore } from './l1_tx_failed_store/index.js';
|
|
50
62
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
51
63
|
|
|
52
64
|
/** Arguments to the process method of the rollup contract */
|
|
@@ -108,6 +120,7 @@ export class SequencerPublisher {
|
|
|
108
120
|
private interrupted = false;
|
|
109
121
|
private metrics: SequencerPublisherMetrics;
|
|
110
122
|
public epochCache: EpochCache;
|
|
123
|
+
private failedTxStore?: Promise<L1TxFailedStore | undefined>;
|
|
111
124
|
|
|
112
125
|
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
113
126
|
protected slashingLog = createLogger('sequencer:publisher:slashing');
|
|
@@ -115,6 +128,7 @@ export class SequencerPublisher {
|
|
|
115
128
|
protected lastActions: Partial<Record<Action, SlotNumber>> = {};
|
|
116
129
|
|
|
117
130
|
private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
|
|
131
|
+
private payloadProposedCache: Set<string> = new Set<string>();
|
|
118
132
|
|
|
119
133
|
protected log: Logger;
|
|
120
134
|
protected ethereumSlotDuration: bigint;
|
|
@@ -124,6 +138,9 @@ export class SequencerPublisher {
|
|
|
124
138
|
/** Address to use for simulations in fisherman mode (actual proposer's address) */
|
|
125
139
|
private proposerAddressForSimulation?: EthAddress;
|
|
126
140
|
|
|
141
|
+
/** Optional callback to obtain a replacement publisher when the current one fails to send. */
|
|
142
|
+
private getNextPublisher?: (excludeAddresses: EthAddress[]) => Promise<L1TxUtils | undefined>;
|
|
143
|
+
|
|
127
144
|
/** L1 fee analyzer for fisherman mode */
|
|
128
145
|
private l1FeeAnalyzer?: L1FeeAnalyzer;
|
|
129
146
|
|
|
@@ -136,7 +153,7 @@ export class SequencerPublisher {
|
|
|
136
153
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
137
154
|
public static VOTE_GAS_GUESS: bigint = 800_000n;
|
|
138
155
|
|
|
139
|
-
public l1TxUtils:
|
|
156
|
+
public l1TxUtils: L1TxUtils;
|
|
140
157
|
public rollupContract: RollupContract;
|
|
141
158
|
public govProposerContract: GovernanceProposerContract;
|
|
142
159
|
public slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
@@ -147,11 +164,12 @@ export class SequencerPublisher {
|
|
|
147
164
|
protected requests: RequestWithExpiry[] = [];
|
|
148
165
|
|
|
149
166
|
constructor(
|
|
150
|
-
private config:
|
|
167
|
+
private config: Pick<SequencerPublisherConfig, 'fishermanMode' | 'l1TxFailedStore'> &
|
|
168
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration'> & { l1ChainId: number },
|
|
151
169
|
deps: {
|
|
152
170
|
telemetry?: TelemetryClient;
|
|
153
171
|
blobClient: BlobClientInterface;
|
|
154
|
-
l1TxUtils:
|
|
172
|
+
l1TxUtils: L1TxUtils;
|
|
155
173
|
rollupContract: RollupContract;
|
|
156
174
|
slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
157
175
|
governanceProposerContract: GovernanceProposerContract;
|
|
@@ -161,6 +179,7 @@ export class SequencerPublisher {
|
|
|
161
179
|
metrics: SequencerPublisherMetrics;
|
|
162
180
|
lastActions: Partial<Record<Action, SlotNumber>>;
|
|
163
181
|
log?: Logger;
|
|
182
|
+
getNextPublisher?: (excludeAddresses: EthAddress[]) => Promise<L1TxUtils | undefined>;
|
|
164
183
|
},
|
|
165
184
|
) {
|
|
166
185
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
@@ -174,6 +193,7 @@ export class SequencerPublisher {
|
|
|
174
193
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
175
194
|
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
176
195
|
this.l1TxUtils = deps.l1TxUtils;
|
|
196
|
+
this.getNextPublisher = deps.getNextPublisher;
|
|
177
197
|
|
|
178
198
|
this.rollupContract = deps.rollupContract;
|
|
179
199
|
|
|
@@ -202,6 +222,31 @@ export class SequencerPublisher {
|
|
|
202
222
|
this.rollupContract,
|
|
203
223
|
createLogger('sequencer:publisher:price-oracle'),
|
|
204
224
|
);
|
|
225
|
+
|
|
226
|
+
// Initialize failed L1 tx store (optional, for test networks)
|
|
227
|
+
this.failedTxStore = createL1TxFailedStore(config.l1TxFailedStore, this.log);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Backs up a failed L1 transaction to the configured store for debugging.
|
|
232
|
+
* Does nothing if no store is configured.
|
|
233
|
+
*/
|
|
234
|
+
private backupFailedTx(failedTx: Omit<FailedL1Tx, 'timestamp'>): void {
|
|
235
|
+
if (!this.failedTxStore) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const tx: FailedL1Tx = {
|
|
240
|
+
...failedTx,
|
|
241
|
+
timestamp: Date.now(),
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
// Fire and forget - don't block on backup
|
|
245
|
+
void this.failedTxStore
|
|
246
|
+
.then(store => store?.saveFailedTx(tx))
|
|
247
|
+
.catch(err => {
|
|
248
|
+
this.log.warn(`Failed to backup failed L1 tx to store`, err);
|
|
249
|
+
});
|
|
205
250
|
}
|
|
206
251
|
|
|
207
252
|
public getRollupContract(): RollupContract {
|
|
@@ -383,19 +428,36 @@ export class SequencerPublisher {
|
|
|
383
428
|
validRequests.sort((a, b) => compareActions(a.action, b.action));
|
|
384
429
|
|
|
385
430
|
try {
|
|
431
|
+
// Capture context for failed tx backup before sending
|
|
432
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
433
|
+
const multicallData = encodeFunctionData({
|
|
434
|
+
abi: multicall3Abi,
|
|
435
|
+
functionName: 'aggregate3',
|
|
436
|
+
args: [
|
|
437
|
+
validRequests.map(r => ({
|
|
438
|
+
target: r.request.to!,
|
|
439
|
+
callData: r.request.data!,
|
|
440
|
+
allowFailure: true,
|
|
441
|
+
})),
|
|
442
|
+
],
|
|
443
|
+
});
|
|
444
|
+
const blobDataHex = blobConfig?.blobs?.map(b => toHex(b)) as Hex[] | undefined;
|
|
445
|
+
|
|
446
|
+
const txContext = { multicallData, blobData: blobDataHex, l1BlockNumber };
|
|
447
|
+
|
|
386
448
|
this.log.debug('Forwarding transactions', {
|
|
387
449
|
validRequests: validRequests.map(request => request.action),
|
|
388
450
|
txConfig,
|
|
389
451
|
});
|
|
390
|
-
const result = await
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
452
|
+
const result = await this.forwardWithPublisherRotation(validRequests, txConfig, blobConfig);
|
|
453
|
+
if (result === undefined) {
|
|
454
|
+
return undefined;
|
|
455
|
+
}
|
|
456
|
+
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(
|
|
457
|
+
validRequests,
|
|
458
|
+
result,
|
|
459
|
+
txContext,
|
|
397
460
|
);
|
|
398
|
-
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(validRequests, result);
|
|
399
461
|
return { result, expiredActions, sentActions: validActions, successfulActions, failedActions };
|
|
400
462
|
} catch (err) {
|
|
401
463
|
const viemError = formatViemError(err);
|
|
@@ -413,13 +475,76 @@ export class SequencerPublisher {
|
|
|
413
475
|
}
|
|
414
476
|
}
|
|
415
477
|
|
|
478
|
+
/**
|
|
479
|
+
* Forwards transactions via Multicall3, rotating to the next available publisher if a send
|
|
480
|
+
* failure occurs (i.e. the tx never reached the chain).
|
|
481
|
+
* On-chain reverts and simulation errors are returned as-is without rotation.
|
|
482
|
+
*/
|
|
483
|
+
private async forwardWithPublisherRotation(
|
|
484
|
+
validRequests: RequestWithExpiry[],
|
|
485
|
+
txConfig: RequestWithExpiry['gasConfig'],
|
|
486
|
+
blobConfig: L1BlobInputs | undefined,
|
|
487
|
+
) {
|
|
488
|
+
const triedAddresses: EthAddress[] = [];
|
|
489
|
+
let currentPublisher = this.l1TxUtils;
|
|
490
|
+
|
|
491
|
+
while (true) {
|
|
492
|
+
triedAddresses.push(currentPublisher.getSenderAddress());
|
|
493
|
+
try {
|
|
494
|
+
const result = await Multicall3.forward(
|
|
495
|
+
validRequests.map(r => r.request),
|
|
496
|
+
currentPublisher,
|
|
497
|
+
txConfig,
|
|
498
|
+
blobConfig,
|
|
499
|
+
this.rollupContract.address,
|
|
500
|
+
this.log,
|
|
501
|
+
);
|
|
502
|
+
this.l1TxUtils = currentPublisher;
|
|
503
|
+
return result;
|
|
504
|
+
} catch (err) {
|
|
505
|
+
if (err instanceof TimeoutError) {
|
|
506
|
+
throw err;
|
|
507
|
+
}
|
|
508
|
+
const viemError = formatViemError(err);
|
|
509
|
+
if (!this.getNextPublisher) {
|
|
510
|
+
this.log.error('Failed to publish bundled transactions', viemError);
|
|
511
|
+
return undefined;
|
|
512
|
+
}
|
|
513
|
+
this.log.warn(
|
|
514
|
+
`Publisher ${currentPublisher.getSenderAddress()} failed to send, rotating to next publisher`,
|
|
515
|
+
viemError,
|
|
516
|
+
);
|
|
517
|
+
const nextPublisher = await this.getNextPublisher([...triedAddresses]);
|
|
518
|
+
if (!nextPublisher) {
|
|
519
|
+
this.log.error('All available publishers exhausted, failed to publish bundled transactions');
|
|
520
|
+
return undefined;
|
|
521
|
+
}
|
|
522
|
+
currentPublisher = nextPublisher;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
416
527
|
private callbackBundledTransactions(
|
|
417
528
|
requests: RequestWithExpiry[],
|
|
418
|
-
result
|
|
529
|
+
result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError | undefined,
|
|
530
|
+
txContext: { multicallData: Hex; blobData?: Hex[]; l1BlockNumber: bigint },
|
|
419
531
|
) {
|
|
420
532
|
const actionsListStr = requests.map(r => r.action).join(', ');
|
|
421
533
|
if (result instanceof FormattedViemError) {
|
|
422
534
|
this.log.error(`Failed to publish bundled transactions (${actionsListStr})`, result);
|
|
535
|
+
this.backupFailedTx({
|
|
536
|
+
id: keccak256(txContext.multicallData),
|
|
537
|
+
failureType: 'send-error',
|
|
538
|
+
request: { to: MULTI_CALL_3_ADDRESS, data: txContext.multicallData },
|
|
539
|
+
blobData: txContext.blobData,
|
|
540
|
+
l1BlockNumber: txContext.l1BlockNumber.toString(),
|
|
541
|
+
error: { message: result.message, name: result.name },
|
|
542
|
+
context: {
|
|
543
|
+
actions: requests.map(r => r.action),
|
|
544
|
+
requests: requests.map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
|
|
545
|
+
sender: this.getSenderAddress().toString(),
|
|
546
|
+
},
|
|
547
|
+
});
|
|
423
548
|
return { failedActions: requests.map(r => r.action) };
|
|
424
549
|
} else {
|
|
425
550
|
this.log.verbose(`Published bundled transactions (${actionsListStr})`, { result, requests });
|
|
@@ -432,6 +557,30 @@ export class SequencerPublisher {
|
|
|
432
557
|
failedActions.push(request.action);
|
|
433
558
|
}
|
|
434
559
|
}
|
|
560
|
+
// Single backup for the whole reverted tx
|
|
561
|
+
if (failedActions.length > 0 && result?.receipt?.status === 'reverted') {
|
|
562
|
+
this.backupFailedTx({
|
|
563
|
+
id: result.receipt.transactionHash,
|
|
564
|
+
failureType: 'revert',
|
|
565
|
+
request: { to: MULTI_CALL_3_ADDRESS, data: txContext.multicallData },
|
|
566
|
+
blobData: txContext.blobData,
|
|
567
|
+
l1BlockNumber: result.receipt.blockNumber.toString(),
|
|
568
|
+
receipt: {
|
|
569
|
+
transactionHash: result.receipt.transactionHash,
|
|
570
|
+
blockNumber: result.receipt.blockNumber.toString(),
|
|
571
|
+
gasUsed: (result.receipt.gasUsed ?? 0n).toString(),
|
|
572
|
+
status: 'reverted',
|
|
573
|
+
},
|
|
574
|
+
error: { message: result.errorMsg ?? 'Transaction reverted' },
|
|
575
|
+
context: {
|
|
576
|
+
actions: failedActions,
|
|
577
|
+
requests: requests
|
|
578
|
+
.filter(r => failedActions.includes(r.action))
|
|
579
|
+
.map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
|
|
580
|
+
sender: this.getSenderAddress().toString(),
|
|
581
|
+
},
|
|
582
|
+
});
|
|
583
|
+
}
|
|
435
584
|
return { successfulActions, failedActions };
|
|
436
585
|
}
|
|
437
586
|
}
|
|
@@ -543,6 +692,8 @@ export class SequencerPublisher {
|
|
|
543
692
|
const request = this.buildInvalidateCheckpointRequest(validationResult);
|
|
544
693
|
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
|
|
545
694
|
|
|
695
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
696
|
+
|
|
546
697
|
try {
|
|
547
698
|
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
548
699
|
request,
|
|
@@ -594,6 +745,18 @@ export class SequencerPublisher {
|
|
|
594
745
|
|
|
595
746
|
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
596
747
|
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
|
|
748
|
+
this.backupFailedTx({
|
|
749
|
+
id: keccak256(request.data!),
|
|
750
|
+
failureType: 'simulation',
|
|
751
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
752
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
753
|
+
error: { message: viemError.message, name: viemError.name },
|
|
754
|
+
context: {
|
|
755
|
+
actions: [`invalidate-${reason}`],
|
|
756
|
+
checkpointNumber,
|
|
757
|
+
sender: this.getSenderAddress().toString(),
|
|
758
|
+
},
|
|
759
|
+
});
|
|
597
760
|
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
|
|
598
761
|
}
|
|
599
762
|
}
|
|
@@ -639,24 +802,8 @@ export class SequencerPublisher {
|
|
|
639
802
|
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
640
803
|
): Promise<bigint> {
|
|
641
804
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
642
|
-
|
|
643
|
-
// TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
|
|
644
|
-
// If we have no attestations, we still need to provide the empty attestations
|
|
645
|
-
// so that the committee is recalculated correctly
|
|
646
|
-
// const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
647
|
-
// if (ignoreSignatures) {
|
|
648
|
-
// const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
|
|
649
|
-
// if (!committee) {
|
|
650
|
-
// this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
651
|
-
// throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
652
|
-
// }
|
|
653
|
-
// attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
654
|
-
// CommitteeAttestation.fromAddress(committeeMember),
|
|
655
|
-
// );
|
|
656
|
-
// }
|
|
657
|
-
|
|
658
805
|
const blobFields = checkpoint.toBlobFields();
|
|
659
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
806
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
660
807
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
661
808
|
|
|
662
809
|
const args = [
|
|
@@ -713,6 +860,32 @@ export class SequencerPublisher {
|
|
|
713
860
|
return false;
|
|
714
861
|
}
|
|
715
862
|
|
|
863
|
+
// Check if payload was already submitted to governance
|
|
864
|
+
const cacheKey = payload.toString();
|
|
865
|
+
if (!this.payloadProposedCache.has(cacheKey)) {
|
|
866
|
+
try {
|
|
867
|
+
const l1StartBlock = await this.rollupContract.getL1StartBlock();
|
|
868
|
+
const proposed = await retry(
|
|
869
|
+
() => base.hasPayloadBeenProposed(payload.toString(), l1StartBlock),
|
|
870
|
+
'Check if payload was proposed',
|
|
871
|
+
makeBackoff([0, 1, 2]),
|
|
872
|
+
this.log,
|
|
873
|
+
true,
|
|
874
|
+
);
|
|
875
|
+
if (proposed) {
|
|
876
|
+
this.payloadProposedCache.add(cacheKey);
|
|
877
|
+
}
|
|
878
|
+
} catch (err) {
|
|
879
|
+
this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
|
|
880
|
+
return false;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
if (this.payloadProposedCache.has(cacheKey)) {
|
|
885
|
+
this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
|
|
886
|
+
return false;
|
|
887
|
+
}
|
|
888
|
+
|
|
716
889
|
const cachedLastVote = this.lastActions[signalType];
|
|
717
890
|
this.lastActions[signalType] = slotNumber;
|
|
718
891
|
const action = signalType;
|
|
@@ -731,11 +904,26 @@ export class SequencerPublisher {
|
|
|
731
904
|
lastValidL2Slot: slotNumber,
|
|
732
905
|
});
|
|
733
906
|
|
|
907
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
908
|
+
|
|
734
909
|
try {
|
|
735
910
|
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
736
911
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
737
912
|
} catch (err) {
|
|
738
|
-
|
|
913
|
+
const viemError = formatViemError(err);
|
|
914
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError);
|
|
915
|
+
this.backupFailedTx({
|
|
916
|
+
id: keccak256(request.data!),
|
|
917
|
+
failureType: 'simulation',
|
|
918
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
919
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
920
|
+
error: { message: viemError.message, name: viemError.name },
|
|
921
|
+
context: {
|
|
922
|
+
actions: [action],
|
|
923
|
+
slot: slotNumber,
|
|
924
|
+
sender: this.getSenderAddress().toString(),
|
|
925
|
+
},
|
|
926
|
+
});
|
|
739
927
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
740
928
|
}
|
|
741
929
|
|
|
@@ -940,7 +1128,7 @@ export class SequencerPublisher {
|
|
|
940
1128
|
const checkpointHeader = checkpoint.header;
|
|
941
1129
|
|
|
942
1130
|
const blobFields = checkpoint.toBlobFields();
|
|
943
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
1131
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
944
1132
|
|
|
945
1133
|
const proposeTxArgs: L1ProcessArgs = {
|
|
946
1134
|
header: checkpointHeader,
|
|
@@ -1031,6 +1219,8 @@ export class SequencerPublisher {
|
|
|
1031
1219
|
|
|
1032
1220
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
1033
1221
|
|
|
1222
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1223
|
+
|
|
1034
1224
|
let gasUsed: bigint;
|
|
1035
1225
|
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
1036
1226
|
try {
|
|
@@ -1040,6 +1230,19 @@ export class SequencerPublisher {
|
|
|
1040
1230
|
const viemError = formatViemError(err, simulateAbi);
|
|
1041
1231
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1042
1232
|
|
|
1233
|
+
this.backupFailedTx({
|
|
1234
|
+
id: keccak256(request.data!),
|
|
1235
|
+
failureType: 'simulation',
|
|
1236
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
1237
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1238
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1239
|
+
context: {
|
|
1240
|
+
actions: [action],
|
|
1241
|
+
slot: slotNumber,
|
|
1242
|
+
sender: this.getSenderAddress().toString(),
|
|
1243
|
+
},
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1043
1246
|
return false;
|
|
1044
1247
|
}
|
|
1045
1248
|
|
|
@@ -1123,9 +1326,27 @@ export class SequencerPublisher {
|
|
|
1123
1326
|
kzg,
|
|
1124
1327
|
},
|
|
1125
1328
|
)
|
|
1126
|
-
.catch(err => {
|
|
1127
|
-
const
|
|
1128
|
-
this.log.error(`Failed to validate blobs`, message, { metaMessages });
|
|
1329
|
+
.catch(async err => {
|
|
1330
|
+
const viemError = formatViemError(err);
|
|
1331
|
+
this.log.error(`Failed to validate blobs`, viemError.message, { metaMessages: viemError.metaMessages });
|
|
1332
|
+
const validateBlobsData = encodeFunctionData({
|
|
1333
|
+
abi: RollupAbi,
|
|
1334
|
+
functionName: 'validateBlobs',
|
|
1335
|
+
args: [blobInput],
|
|
1336
|
+
});
|
|
1337
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1338
|
+
this.backupFailedTx({
|
|
1339
|
+
id: keccak256(validateBlobsData),
|
|
1340
|
+
failureType: 'simulation',
|
|
1341
|
+
request: { to: this.rollupContract.address as Hex, data: validateBlobsData },
|
|
1342
|
+
blobData: encodedData.blobs.map(b => toHex(b.data)) as Hex[],
|
|
1343
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1344
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1345
|
+
context: {
|
|
1346
|
+
actions: ['validate-blobs'],
|
|
1347
|
+
sender: this.getSenderAddress().toString(),
|
|
1348
|
+
},
|
|
1349
|
+
});
|
|
1129
1350
|
throw new Error('Failed to validate blobs');
|
|
1130
1351
|
});
|
|
1131
1352
|
}
|
|
@@ -1204,6 +1425,8 @@ export class SequencerPublisher {
|
|
|
1204
1425
|
});
|
|
1205
1426
|
}
|
|
1206
1427
|
|
|
1428
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1429
|
+
|
|
1207
1430
|
const simulationResult = await this.l1TxUtils
|
|
1208
1431
|
.simulate(
|
|
1209
1432
|
{
|
|
@@ -1237,6 +1460,18 @@ export class SequencerPublisher {
|
|
|
1237
1460
|
};
|
|
1238
1461
|
}
|
|
1239
1462
|
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1463
|
+
this.backupFailedTx({
|
|
1464
|
+
id: keccak256(rollupData),
|
|
1465
|
+
failureType: 'simulation',
|
|
1466
|
+
request: { to: this.rollupContract.address, data: rollupData },
|
|
1467
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1468
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1469
|
+
context: {
|
|
1470
|
+
actions: ['propose'],
|
|
1471
|
+
slot: Number(args[0].header.slotNumber),
|
|
1472
|
+
sender: this.getSenderAddress().toString(),
|
|
1473
|
+
},
|
|
1474
|
+
});
|
|
1240
1475
|
throw err;
|
|
1241
1476
|
});
|
|
1242
1477
|
|