@aztec/sequencer-client 0.0.1-commit.3100065 → 0.0.1-commit.321f6a9
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/publisher/l1_tx_failed_store/failed_tx_store.d.ts +48 -8
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -1
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +68 -1
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +2 -2
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +1 -1
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +4 -2
- package/dest/publisher/l1_tx_failed_store/index.d.ts +2 -2
- package/dest/publisher/l1_tx_failed_store/index.d.ts.map +1 -1
- package/dest/publisher/l1_tx_failed_store/index.js +1 -0
- package/dest/publisher/sequencer-publisher.d.ts +18 -3
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +244 -40
- package/dest/sequencer/errors.d.ts +8 -1
- package/dest/sequencer/errors.d.ts.map +1 -1
- package/dest/sequencer/errors.js +9 -0
- package/package.json +29 -28
- package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +114 -7
- package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +4 -3
- package/src/publisher/l1_tx_failed_store/index.ts +1 -1
- package/src/publisher/sequencer-publisher.ts +268 -50
- package/src/sequencer/README.md +243 -0
- package/src/sequencer/errors.ts +15 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { jsonParseWithSchema, jsonStringify } from '@aztec/foundation/json-rpc';
|
|
1
2
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
2
3
|
import type { FileStore } from '@aztec/stdlib/file-store';
|
|
3
4
|
|
|
4
|
-
import type
|
|
5
|
+
import { type FailedL1Tx, FailedL1TxSchema, type FailedL1TxUri, type L1TxFailedStore } from './failed_tx_store.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* L1TxFailedStore implementation using the FileStore abstraction.
|
|
@@ -20,7 +21,7 @@ export class FileStoreL1TxFailedStore implements L1TxFailedStore {
|
|
|
20
21
|
public async saveFailedTx(tx: FailedL1Tx): Promise<FailedL1TxUri> {
|
|
21
22
|
const prefix = tx.receipt ? 'tx' : 'data';
|
|
22
23
|
const path = `${tx.failureType}/${prefix}-${tx.id}.json`;
|
|
23
|
-
const json =
|
|
24
|
+
const json = jsonStringify(tx, true);
|
|
24
25
|
|
|
25
26
|
const uri = await this.fileStore.save(path, Buffer.from(json), {
|
|
26
27
|
metadata: {
|
|
@@ -41,6 +42,6 @@ export class FileStoreL1TxFailedStore implements L1TxFailedStore {
|
|
|
41
42
|
|
|
42
43
|
public async getFailedTx(uri: FailedL1TxUri): Promise<FailedL1Tx> {
|
|
43
44
|
const data = await this.fileStore.read(uri);
|
|
44
|
-
return
|
|
45
|
+
return jsonParseWithSchema(data.toString(), FailedL1TxSchema);
|
|
45
46
|
}
|
|
46
47
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { type FailedL1Tx, type FailedL1TxUri, type L1TxFailedStore } from './failed_tx_store.js';
|
|
1
|
+
export { type FailedL1Tx, FailedL1TxSchema, type FailedL1TxUri, type L1TxFailedStore } from './failed_tx_store.js';
|
|
2
2
|
export { createL1TxFailedStore } from './factory.js';
|
|
3
3
|
export { FileStoreL1TxFailedStore } from './file_store_failed_tx_store.js';
|
|
@@ -13,11 +13,12 @@ import {
|
|
|
13
13
|
type SlashingProposerContract,
|
|
14
14
|
buildSimulationOverridesStateOverride,
|
|
15
15
|
} from '@aztec/ethereum/contracts';
|
|
16
|
-
import { type L1FeeAnalysisResult, L1FeeAnalyzer } from '@aztec/ethereum/l1-fee-analysis';
|
|
16
|
+
import { type L1FeeAnalysisResult, L1FeeAnalyzer, captureWindowBlockFees } from '@aztec/ethereum/l1-fee-analysis';
|
|
17
17
|
import {
|
|
18
18
|
type L1BlobInputs,
|
|
19
19
|
type L1TxConfig,
|
|
20
20
|
type L1TxRequest,
|
|
21
|
+
L1TxTimeoutError,
|
|
21
22
|
type L1TxUtils,
|
|
22
23
|
MAX_L1_TX_LIMIT,
|
|
23
24
|
type TransactionStats,
|
|
@@ -287,25 +288,97 @@ export class SequencerPublisher {
|
|
|
287
288
|
}
|
|
288
289
|
|
|
289
290
|
/**
|
|
290
|
-
*
|
|
291
|
-
*
|
|
291
|
+
* Logs the gas-pricing data of a failed L1 transaction at warn — so underpricing is diagnosable
|
|
292
|
+
* from logs even with no failed-tx store configured — and backs the record up to the store when
|
|
293
|
+
* one is. When captureFeeSummary is true, also records the fee data of the already-mined L1
|
|
294
|
+
* blocks in the target slot's inclusion window.
|
|
292
295
|
*/
|
|
293
|
-
private backupFailedTx(
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
296
|
+
private backupFailedTx(
|
|
297
|
+
failedTx: Omit<FailedL1Tx, 'timestamp'>,
|
|
298
|
+
opts?: { captureFeeSummary?: boolean; targetSlot?: SlotNumber; sharedFeeSummary?: FailedL1Tx['gasInfo'] },
|
|
299
|
+
): void {
|
|
298
300
|
const tx: FailedL1Tx = {
|
|
299
301
|
...failedTx,
|
|
300
302
|
timestamp: Date.now(),
|
|
301
303
|
};
|
|
302
304
|
|
|
303
305
|
// Fire and forget - don't block on backup
|
|
304
|
-
void
|
|
305
|
-
|
|
306
|
-
|
|
306
|
+
void (async () => {
|
|
307
|
+
try {
|
|
308
|
+
// Prefer a pre-captured summary (shared across a batch of failures in the same slot) so we
|
|
309
|
+
// don't re-read the fee window per record. A capture error must not lose the record itself.
|
|
310
|
+
const feeSummary =
|
|
311
|
+
opts?.sharedFeeSummary ??
|
|
312
|
+
(opts?.captureFeeSummary
|
|
313
|
+
? await this.captureFeeEnvironment(opts.targetSlot).catch(() => undefined)
|
|
314
|
+
: undefined);
|
|
315
|
+
if (feeSummary) {
|
|
316
|
+
tx.gasInfo = { ...tx.gasInfo, ...feeSummary };
|
|
317
|
+
}
|
|
318
|
+
if (tx.gasInfo) {
|
|
319
|
+
this.log.warn(`Gas pricing data for failed L1 tx (${tx.failureType})`, {
|
|
320
|
+
failureType: tx.failureType,
|
|
321
|
+
actions: tx.context.actions,
|
|
322
|
+
slot: tx.context.slot,
|
|
323
|
+
...tx.gasInfo,
|
|
324
|
+
...tx.timing,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
const store = await this.failedTxStore;
|
|
328
|
+
if (store) {
|
|
329
|
+
await store.saveFailedTx(tx);
|
|
330
|
+
}
|
|
331
|
+
} catch (err) {
|
|
307
332
|
this.log.warn(`Failed to backup failed L1 tx to store`, err);
|
|
308
|
-
}
|
|
333
|
+
}
|
|
334
|
+
})();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Captures per-block fee data for the L1 blocks in the target slot's inclusion window (the blocks the
|
|
339
|
+
* tx could have landed in) for underpricing diagnostics. Reads only already-mined blocks, so it never
|
|
340
|
+
* waits on the chain. Safe to call off the critical path: the underlying capture never throws, and this
|
|
341
|
+
* returns undefined when there is no target slot or the window is not yet mined (e.g. an early send
|
|
342
|
+
* failure), in which case the record simply carries no window data.
|
|
343
|
+
*/
|
|
344
|
+
private async captureFeeEnvironment(targetL2Slot: SlotNumber | undefined): Promise<FailedL1Tx['gasInfo']> {
|
|
345
|
+
if (targetL2Slot === undefined) {
|
|
346
|
+
return undefined;
|
|
347
|
+
}
|
|
348
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
349
|
+
// The inclusion window is [start of slot N, start of slot N+1): all L1 blocks that can include a tx
|
|
350
|
+
// for this L2 slot. getTimestampForSlot returns seconds, matching block.timestamp.
|
|
351
|
+
const windowStartS = getTimestampForSlot(targetL2Slot, l1Constants);
|
|
352
|
+
const windowEndS = getTimestampForSlot(SlotNumber(Number(targetL2Slot) + 1), l1Constants);
|
|
353
|
+
const windowBlocks = await captureWindowBlockFees(this.l1TxUtils.client, windowStartS, windowEndS);
|
|
354
|
+
if (windowBlocks.length === 0) {
|
|
355
|
+
return undefined;
|
|
356
|
+
}
|
|
357
|
+
return { windowBlocks };
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** Computes timing info relative to the L2 slot deadline. */
|
|
361
|
+
private computeTimingInfo(targetL2Slot: SlotNumber | undefined): FailedL1Tx['timing'] {
|
|
362
|
+
if (targetL2Slot === undefined) {
|
|
363
|
+
return undefined;
|
|
364
|
+
}
|
|
365
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
366
|
+
const slotDeadlineS = getTimestampForSlot(SlotNumber(Number(targetL2Slot) + 1), l1Constants);
|
|
367
|
+
const slotDeadlineMs = Number(slotDeadlineS) * 1000;
|
|
368
|
+
return {
|
|
369
|
+
targetL2Slot: Number(targetL2Slot),
|
|
370
|
+
slotDeadlineTimestampS: slotDeadlineS,
|
|
371
|
+
msUntilSlotDeadline: slotDeadlineMs - this.dateProvider.now(),
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Builds an id for a synthetic failure record (send-error/timeout) that has no on-chain tx hash.
|
|
377
|
+
* Includes the failure time so each attempt — including retries of the same slot — is stored as its
|
|
378
|
+
* own record rather than overwriting the previous one.
|
|
379
|
+
*/
|
|
380
|
+
private failureRecordId(actions: string[], targetSlot: SlotNumber | undefined): Hex {
|
|
381
|
+
return keccak256(toHex(`${actions.join(',')}:${targetSlot ?? ''}:${Date.now()}`));
|
|
309
382
|
}
|
|
310
383
|
|
|
311
384
|
public getRollupContract(): RollupContract {
|
|
@@ -469,7 +542,7 @@ export class SequencerPublisher {
|
|
|
469
542
|
|
|
470
543
|
if (bundleResult.kind === 'aborted') {
|
|
471
544
|
this.logDroppedInSim(bundleResult.droppedRequests);
|
|
472
|
-
void this.backupDroppedInSim(bundleResult.droppedRequests).catch(err =>
|
|
545
|
+
void this.backupDroppedInSim(bundleResult.droppedRequests, currentL2Slot).catch(err =>
|
|
473
546
|
this.log.error(`Failed to backup requests dropped in simulation`, err),
|
|
474
547
|
);
|
|
475
548
|
return undefined;
|
|
@@ -495,7 +568,7 @@ export class SequencerPublisher {
|
|
|
495
568
|
requests: requests.map(request => request.action),
|
|
496
569
|
txConfig,
|
|
497
570
|
});
|
|
498
|
-
const result = await this.forwardWithPublisherRotation(requests, txConfig, blobConfig);
|
|
571
|
+
const result = await this.forwardWithPublisherRotation(requests, txConfig, blobConfig, currentL2Slot);
|
|
499
572
|
if (result === undefined) {
|
|
500
573
|
return undefined;
|
|
501
574
|
}
|
|
@@ -511,6 +584,44 @@ export class SequencerPublisher {
|
|
|
511
584
|
} catch (err) {
|
|
512
585
|
const viemError = formatViemError(err);
|
|
513
586
|
this.log.error(`Failed to publish bundled transactions`, viemError);
|
|
587
|
+
if (err instanceof TimeoutError) {
|
|
588
|
+
const timeoutState = err instanceof L1TxTimeoutError ? err.txState : undefined;
|
|
589
|
+
void (async () => {
|
|
590
|
+
// The RPC is likely degraded right after a timeout, so back up without the block number
|
|
591
|
+
// rather than leaking an unhandled rejection.
|
|
592
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber().catch(() => 0n);
|
|
593
|
+
this.backupFailedTx(
|
|
594
|
+
{
|
|
595
|
+
id: this.failureRecordId(
|
|
596
|
+
validRequests.map(r => r.action),
|
|
597
|
+
currentL2Slot,
|
|
598
|
+
),
|
|
599
|
+
failureType: 'timeout',
|
|
600
|
+
request: { to: MULTI_CALL_3_ADDRESS as Hex, data: '0x' as Hex },
|
|
601
|
+
l1BlockNumber,
|
|
602
|
+
error: { message: viemError.message, name: 'TimeoutError' },
|
|
603
|
+
context: {
|
|
604
|
+
actions: validRequests.map(r => r.action),
|
|
605
|
+
requests: validRequests
|
|
606
|
+
.filter(r => r.request.to !== null)
|
|
607
|
+
.map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
|
|
608
|
+
sender: this.getSenderAddress().toString(),
|
|
609
|
+
slot: Number(currentL2Slot),
|
|
610
|
+
},
|
|
611
|
+
timing: this.computeTimingInfo(currentL2Slot),
|
|
612
|
+
gasInfo: timeoutState
|
|
613
|
+
? {
|
|
614
|
+
sentGasPriceLadder: timeoutState.gasPriceHistory,
|
|
615
|
+
attempts: timeoutState.attempts,
|
|
616
|
+
gasLimit: timeoutState.gasLimit,
|
|
617
|
+
nonce: timeoutState.nonce,
|
|
618
|
+
}
|
|
619
|
+
: undefined,
|
|
620
|
+
},
|
|
621
|
+
{ captureFeeSummary: true, targetSlot: currentL2Slot },
|
|
622
|
+
);
|
|
623
|
+
})();
|
|
624
|
+
}
|
|
514
625
|
return undefined;
|
|
515
626
|
} finally {
|
|
516
627
|
try {
|
|
@@ -538,23 +649,37 @@ export class SequencerPublisher {
|
|
|
538
649
|
}
|
|
539
650
|
|
|
540
651
|
/** Backs up entries dropped by bundle simulation, one record per dropped action. */
|
|
541
|
-
private async backupDroppedInSim(dropped: DroppedRequest[]): Promise<void> {
|
|
652
|
+
private async backupDroppedInSim(dropped: DroppedRequest[], targetSlot?: SlotNumber): Promise<void> {
|
|
542
653
|
if (dropped.length === 0) {
|
|
543
654
|
return;
|
|
544
655
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
this.
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
656
|
+
// Invoked as `void backupDroppedInSim(...)` on the publish path, so it must not throw.
|
|
657
|
+
try {
|
|
658
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
659
|
+
// Every dropped entry failed in the same slot against the same L1 fee conditions, so capture
|
|
660
|
+
// the fee environment once and share it rather than re-reading the window per entry.
|
|
661
|
+
const sharedFeeSummary = await this.captureFeeEnvironment(targetSlot).catch(() => undefined);
|
|
662
|
+
const timing = this.computeTimingInfo(targetSlot);
|
|
663
|
+
for (const { request: req } of dropped) {
|
|
664
|
+
this.backupFailedTx(
|
|
665
|
+
{
|
|
666
|
+
id: keccak256(req.request.data!),
|
|
667
|
+
failureType: 'simulation',
|
|
668
|
+
request: { to: req.request.to! as Hex, data: req.request.data! },
|
|
669
|
+
l1BlockNumber,
|
|
670
|
+
error: { message: 'Bundle entry dropped: action reverted in sim' },
|
|
671
|
+
context: {
|
|
672
|
+
actions: [req.action],
|
|
673
|
+
sender: this.getSenderAddress().toString(),
|
|
674
|
+
slot: targetSlot !== undefined ? Number(targetSlot) : undefined,
|
|
675
|
+
},
|
|
676
|
+
timing,
|
|
677
|
+
},
|
|
678
|
+
{ sharedFeeSummary },
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
} catch (err) {
|
|
682
|
+
this.log.warn(`Failed to back up dropped-in-sim entries`, err);
|
|
558
683
|
}
|
|
559
684
|
}
|
|
560
685
|
|
|
@@ -567,6 +692,7 @@ export class SequencerPublisher {
|
|
|
567
692
|
validRequests: RequestWithExpiry[],
|
|
568
693
|
txConfig: RequestWithExpiry['gasConfig'],
|
|
569
694
|
blobConfig: L1BlobInputs | undefined,
|
|
695
|
+
targetSlot?: SlotNumber,
|
|
570
696
|
) {
|
|
571
697
|
if (!txConfig?.gasLimit) {
|
|
572
698
|
throw new Error('gasLimit is required for bundled transactions');
|
|
@@ -603,11 +729,13 @@ export class SequencerPublisher {
|
|
|
603
729
|
this.log.error('Forwarder transaction reverted on-chain; not rotating publisher', err, {
|
|
604
730
|
transactionHash: err.receipt.transactionHash,
|
|
605
731
|
});
|
|
732
|
+
this.backupRevertFailure(validRequests, err, currentPublisher, targetSlot);
|
|
606
733
|
return undefined;
|
|
607
734
|
}
|
|
608
735
|
const viemError = formatViemError(err);
|
|
609
736
|
if (!this.getNextPublisher) {
|
|
610
737
|
this.log.error('Failed to publish bundled transactions', viemError);
|
|
738
|
+
this.backupSendFailure(validRequests, viemError, currentPublisher, targetSlot);
|
|
611
739
|
return undefined;
|
|
612
740
|
}
|
|
613
741
|
this.log.warn(
|
|
@@ -621,6 +749,7 @@ export class SequencerPublisher {
|
|
|
621
749
|
viemError,
|
|
622
750
|
{ triedAddresses: triedAddresses.map(a => a.toString()) },
|
|
623
751
|
);
|
|
752
|
+
this.backupSendFailure(validRequests, viemError, currentPublisher, targetSlot);
|
|
624
753
|
return undefined;
|
|
625
754
|
}
|
|
626
755
|
currentPublisher = nextPublisher;
|
|
@@ -628,6 +757,87 @@ export class SequencerPublisher {
|
|
|
628
757
|
}
|
|
629
758
|
}
|
|
630
759
|
|
|
760
|
+
/** Backs up an on-chain revert failure to the failed tx store. */
|
|
761
|
+
private backupRevertFailure(
|
|
762
|
+
requests: RequestWithExpiry[],
|
|
763
|
+
err: MulticallForwarderRevertedError,
|
|
764
|
+
publisher: L1TxUtils,
|
|
765
|
+
targetSlot?: SlotNumber,
|
|
766
|
+
): void {
|
|
767
|
+
this.backupFailedTx(
|
|
768
|
+
{
|
|
769
|
+
id: err.receipt.transactionHash,
|
|
770
|
+
failureType: 'revert',
|
|
771
|
+
request: { to: MULTI_CALL_3_ADDRESS as Hex, data: '0x' as Hex },
|
|
772
|
+
l1BlockNumber: err.receipt.blockNumber,
|
|
773
|
+
receipt: {
|
|
774
|
+
transactionHash: err.receipt.transactionHash,
|
|
775
|
+
blockNumber: err.receipt.blockNumber,
|
|
776
|
+
gasUsed: err.receipt.gasUsed,
|
|
777
|
+
status: 'reverted',
|
|
778
|
+
},
|
|
779
|
+
error: { message: err.message, name: err.name },
|
|
780
|
+
context: {
|
|
781
|
+
actions: requests.map(r => r.action),
|
|
782
|
+
requests: requests
|
|
783
|
+
.filter(r => r.request.to !== null)
|
|
784
|
+
.map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
|
|
785
|
+
sender: publisher.getSenderAddress().toString(),
|
|
786
|
+
slot: targetSlot !== undefined ? Number(targetSlot) : undefined,
|
|
787
|
+
},
|
|
788
|
+
gasInfo: err.txState
|
|
789
|
+
? {
|
|
790
|
+
sentGasPrice: err.txState.gasPrice,
|
|
791
|
+
gasLimit: err.txState.gasLimit,
|
|
792
|
+
nonce: err.txState.nonce,
|
|
793
|
+
}
|
|
794
|
+
: undefined,
|
|
795
|
+
timing: this.computeTimingInfo(targetSlot),
|
|
796
|
+
},
|
|
797
|
+
{ captureFeeSummary: true, targetSlot },
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/** Backs up a send failure (tx never reached chain) to the failed tx store. */
|
|
802
|
+
private backupSendFailure(
|
|
803
|
+
requests: RequestWithExpiry[],
|
|
804
|
+
error: FormattedViemError | Error,
|
|
805
|
+
publisher: L1TxUtils,
|
|
806
|
+
targetSlot?: SlotNumber,
|
|
807
|
+
): void {
|
|
808
|
+
// If we can't get the block number, still back up without it.
|
|
809
|
+
void this.l1TxUtils
|
|
810
|
+
.getBlockNumber()
|
|
811
|
+
.catch(() => 0n)
|
|
812
|
+
.then(l1BlockNumber => {
|
|
813
|
+
this.backupFailedTx(
|
|
814
|
+
{
|
|
815
|
+
id: this.failureRecordId(
|
|
816
|
+
requests.map(r => r.action),
|
|
817
|
+
targetSlot,
|
|
818
|
+
),
|
|
819
|
+
failureType: 'send-error',
|
|
820
|
+
request: { to: MULTI_CALL_3_ADDRESS as Hex, data: '0x' as Hex },
|
|
821
|
+
l1BlockNumber,
|
|
822
|
+
error: {
|
|
823
|
+
message: error.message,
|
|
824
|
+
name: 'name' in error ? error.name : undefined,
|
|
825
|
+
},
|
|
826
|
+
context: {
|
|
827
|
+
actions: requests.map(r => r.action),
|
|
828
|
+
requests: requests
|
|
829
|
+
.filter(r => r.request.to !== null)
|
|
830
|
+
.map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
|
|
831
|
+
sender: publisher.getSenderAddress().toString(),
|
|
832
|
+
slot: targetSlot !== undefined ? Number(targetSlot) : undefined,
|
|
833
|
+
},
|
|
834
|
+
timing: this.computeTimingInfo(targetSlot),
|
|
835
|
+
},
|
|
836
|
+
{ captureFeeSummary: true, targetSlot },
|
|
837
|
+
);
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
|
|
631
841
|
/*
|
|
632
842
|
* Schedules sending all enqueued requests at (or after) the start of the given L2 slot.
|
|
633
843
|
*/
|
|
@@ -889,18 +1099,22 @@ export class SequencerPublisher {
|
|
|
889
1099
|
|
|
890
1100
|
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
891
1101
|
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
|
|
892
|
-
this.backupFailedTx(
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
1102
|
+
this.backupFailedTx(
|
|
1103
|
+
{
|
|
1104
|
+
id: keccak256(request.data!),
|
|
1105
|
+
failureType: 'simulation',
|
|
1106
|
+
request: { to: request.to!, data: request.data!, value: request.value },
|
|
1107
|
+
l1BlockNumber,
|
|
1108
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1109
|
+
context: {
|
|
1110
|
+
actions: [`invalidate-${reason}`],
|
|
1111
|
+
checkpointNumber,
|
|
1112
|
+
sender: this.getSenderAddress().toString(),
|
|
1113
|
+
},
|
|
1114
|
+
timing: this.computeTimingInfo(this.getCurrentL2Slot()),
|
|
902
1115
|
},
|
|
903
|
-
|
|
1116
|
+
{ captureFeeSummary: true, targetSlot: this.getCurrentL2Slot() },
|
|
1117
|
+
);
|
|
904
1118
|
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
|
|
905
1119
|
}
|
|
906
1120
|
}
|
|
@@ -1346,18 +1560,22 @@ export class SequencerPublisher {
|
|
|
1346
1560
|
args: [blobInput],
|
|
1347
1561
|
});
|
|
1348
1562
|
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1349
|
-
this.backupFailedTx(
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1563
|
+
this.backupFailedTx(
|
|
1564
|
+
{
|
|
1565
|
+
id: keccak256(validateBlobsData),
|
|
1566
|
+
failureType: 'simulation',
|
|
1567
|
+
request: { to: this.rollupContract.address as Hex, data: validateBlobsData },
|
|
1568
|
+
blobData: encodedData.blobs.map(b => toHex(b.data)) as Hex[],
|
|
1569
|
+
l1BlockNumber,
|
|
1570
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1571
|
+
context: {
|
|
1572
|
+
actions: ['validate-blobs'],
|
|
1573
|
+
sender: this.getSenderAddress().toString(),
|
|
1574
|
+
},
|
|
1575
|
+
timing: this.computeTimingInfo(this.getCurrentL2Slot()),
|
|
1359
1576
|
},
|
|
1360
|
-
|
|
1577
|
+
{ captureFeeSummary: true, targetSlot: this.getCurrentL2Slot() },
|
|
1578
|
+
);
|
|
1361
1579
|
throw new Error('Failed to validate blobs');
|
|
1362
1580
|
});
|
|
1363
1581
|
}
|