@aztec/sequencer-client 0.0.1-commit.3469e52 → 0.0.1-commit.35158ae7e
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 +15 -7
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +60 -26
- package/dest/config.d.ts +26 -7
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +47 -30
- package/dest/global_variable_builder/global_builder.d.ts +14 -10
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +24 -23
- package/dest/global_variable_builder/index.d.ts +2 -2
- package/dest/global_variable_builder/index.d.ts.map +1 -1
- package/dest/publisher/config.d.ts +47 -17
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +121 -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-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 +33 -10
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +374 -57
- package/dest/sequencer/checkpoint_proposal_job.d.ts +41 -12
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +295 -165
- package/dest/sequencer/events.d.ts +2 -1
- package/dest/sequencer/events.d.ts.map +1 -1
- package/dest/sequencer/metrics.d.ts +21 -5
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +122 -30
- package/dest/sequencer/sequencer.d.ts +30 -15
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +95 -82
- package/dest/sequencer/timetable.d.ts +4 -6
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +7 -11
- 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 +18 -15
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +63 -40
- package/dest/test/utils.d.ts +8 -8
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +10 -9
- package/package.json +27 -28
- package/src/client/sequencer-client.ts +76 -23
- package/src/config.ts +66 -41
- package/src/global_variable_builder/global_builder.ts +25 -26
- package/src/global_variable_builder/index.ts +1 -1
- package/src/publisher/config.ts +153 -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-metrics.ts +7 -3
- package/src/publisher/sequencer-publisher.ts +376 -70
- package/src/sequencer/checkpoint_proposal_job.ts +409 -201
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/metrics.ts +138 -32
- package/src/sequencer/sequencer.ts +132 -95
- package/src/sequencer/timetable.ts +13 -12
- package/src/sequencer/types.ts +1 -1
- package/src/test/index.ts +2 -4
- package/src/test/mock_checkpoint_builder.ts +93 -65
- package/src/test/utils.ts +22 -13
|
@@ -372,24 +372,29 @@ function _apply_decs_2203_r(targetClass, memberDecs, classDecs, parentClass) {
|
|
|
372
372
|
}
|
|
373
373
|
var _dec, _dec1, _dec2, _initProto;
|
|
374
374
|
import { Blob, getBlobsPerL1Block, getPrefixedEthBlobCommitments } from '@aztec/blob-lib';
|
|
375
|
-
import { MULTI_CALL_3_ADDRESS, Multicall3, RollupContract } from '@aztec/ethereum/contracts';
|
|
375
|
+
import { FeeAssetPriceOracle, MULTI_CALL_3_ADDRESS, Multicall3, RollupContract } from '@aztec/ethereum/contracts';
|
|
376
376
|
import { L1FeeAnalyzer } from '@aztec/ethereum/l1-fee-analysis';
|
|
377
|
-
import { WEI_CONST } from '@aztec/ethereum/l1-tx-utils';
|
|
378
|
-
import { FormattedViemError, formatViemError, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
377
|
+
import { MAX_L1_TX_LIMIT, WEI_CONST } from '@aztec/ethereum/l1-tx-utils';
|
|
378
|
+
import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
379
379
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
380
380
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
381
381
|
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
382
|
+
import { trimmedBytesLength } from '@aztec/foundation/buffer';
|
|
382
383
|
import { pick } from '@aztec/foundation/collection';
|
|
384
|
+
import { TimeoutError } from '@aztec/foundation/error';
|
|
383
385
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
384
386
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
385
387
|
import { createLogger } from '@aztec/foundation/log';
|
|
388
|
+
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
386
389
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
387
390
|
import { Timer } from '@aztec/foundation/timer';
|
|
388
391
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
389
392
|
import { encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
390
393
|
import { CommitteeAttestationsAndSigners } from '@aztec/stdlib/block';
|
|
394
|
+
import { getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
391
395
|
import { getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
392
|
-
import { encodeFunctionData, toHex } from 'viem';
|
|
396
|
+
import { encodeFunctionData, keccak256, multicall3Abi, toHex } from 'viem';
|
|
397
|
+
import { createL1TxFailedStore } from './l1_tx_failed_store/index.js';
|
|
393
398
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
394
399
|
export const Actions = [
|
|
395
400
|
'invalidate-by-invalid-attestation',
|
|
@@ -429,19 +434,21 @@ export class SequencerPublisher {
|
|
|
429
434
|
interrupted;
|
|
430
435
|
metrics;
|
|
431
436
|
epochCache;
|
|
437
|
+
failedTxStore;
|
|
432
438
|
governanceLog;
|
|
433
439
|
slashingLog;
|
|
434
440
|
lastActions;
|
|
435
441
|
isPayloadEmptyCache;
|
|
442
|
+
payloadProposedCache;
|
|
436
443
|
log;
|
|
437
444
|
ethereumSlotDuration;
|
|
445
|
+
aztecSlotDuration;
|
|
446
|
+
dateProvider;
|
|
438
447
|
blobClient;
|
|
439
448
|
/** Address to use for simulations in fisherman mode (actual proposer's address) */ proposerAddressForSimulation;
|
|
449
|
+
/** Optional callback to obtain a replacement publisher when the current one fails to send. */ getNextPublisher;
|
|
440
450
|
/** L1 fee analyzer for fisherman mode */ l1FeeAnalyzer;
|
|
441
|
-
|
|
442
|
-
// Total used for full block from int_l1_pub e2e test: 1m (of which 86k is 1x blob)
|
|
443
|
-
// Total used for emptier block from above test: 429k (of which 84k is 1x blob)
|
|
444
|
-
static PROPOSE_GAS_GUESS = 12_000_000n;
|
|
451
|
+
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */ feeAssetPriceOracle;
|
|
445
452
|
// A CALL to a cold address is 2700 gas
|
|
446
453
|
static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
447
454
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
@@ -460,9 +467,12 @@ export class SequencerPublisher {
|
|
|
460
467
|
this.slashingLog = createLogger('sequencer:publisher:slashing');
|
|
461
468
|
this.lastActions = {};
|
|
462
469
|
this.isPayloadEmptyCache = new Map();
|
|
470
|
+
this.payloadProposedCache = new Set();
|
|
463
471
|
this.requests = [];
|
|
464
472
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
465
473
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
474
|
+
this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
|
|
475
|
+
this.dateProvider = deps.dateProvider;
|
|
466
476
|
this.epochCache = deps.epochCache;
|
|
467
477
|
this.lastActions = deps.lastActions;
|
|
468
478
|
this.blobClient = deps.blobClient;
|
|
@@ -470,6 +480,7 @@ export class SequencerPublisher {
|
|
|
470
480
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
471
481
|
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
472
482
|
this.l1TxUtils = deps.l1TxUtils;
|
|
483
|
+
this.getNextPublisher = deps.getNextPublisher;
|
|
473
484
|
this.rollupContract = deps.rollupContract;
|
|
474
485
|
this.govProposerContract = deps.governanceProposerContract;
|
|
475
486
|
this.slashingProposerContract = deps.slashingProposerContract;
|
|
@@ -483,10 +494,36 @@ export class SequencerPublisher {
|
|
|
483
494
|
if (config.fishermanMode) {
|
|
484
495
|
this.l1FeeAnalyzer = new L1FeeAnalyzer(this.l1TxUtils.client, deps.dateProvider, createLogger('sequencer:publisher:fee-analyzer'));
|
|
485
496
|
}
|
|
497
|
+
// Initialize fee asset price oracle
|
|
498
|
+
this.feeAssetPriceOracle = new FeeAssetPriceOracle(this.l1TxUtils.client, this.rollupContract, createLogger('sequencer:publisher:price-oracle'));
|
|
499
|
+
// Initialize failed L1 tx store (optional, for test networks)
|
|
500
|
+
this.failedTxStore = createL1TxFailedStore(config.l1TxFailedStore, this.log);
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Backs up a failed L1 transaction to the configured store for debugging.
|
|
504
|
+
* Does nothing if no store is configured.
|
|
505
|
+
*/ backupFailedTx(failedTx) {
|
|
506
|
+
if (!this.failedTxStore) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const tx = {
|
|
510
|
+
...failedTx,
|
|
511
|
+
timestamp: Date.now()
|
|
512
|
+
};
|
|
513
|
+
// Fire and forget - don't block on backup
|
|
514
|
+
void this.failedTxStore.then((store)=>store?.saveFailedTx(tx)).catch((err)=>{
|
|
515
|
+
this.log.warn(`Failed to backup failed L1 tx to store`, err);
|
|
516
|
+
});
|
|
486
517
|
}
|
|
487
518
|
getRollupContract() {
|
|
488
519
|
return this.rollupContract;
|
|
489
520
|
}
|
|
521
|
+
/**
|
|
522
|
+
* Gets the fee asset price modifier from the oracle.
|
|
523
|
+
* Returns 0n if the oracle query fails.
|
|
524
|
+
*/ getFeeAssetPriceModifier() {
|
|
525
|
+
return this.feeAssetPriceOracle.computePriceModifier();
|
|
526
|
+
}
|
|
490
527
|
getSenderAddress() {
|
|
491
528
|
return this.l1TxUtils.getSenderAddress();
|
|
492
529
|
}
|
|
@@ -505,7 +542,7 @@ export class SequencerPublisher {
|
|
|
505
542
|
this.requests.push(request);
|
|
506
543
|
}
|
|
507
544
|
getCurrentL2Slot() {
|
|
508
|
-
return this.epochCache.
|
|
545
|
+
return this.epochCache.getSlotNow();
|
|
509
546
|
}
|
|
510
547
|
/**
|
|
511
548
|
* Clears all pending requests without sending them.
|
|
@@ -544,7 +581,7 @@ export class SequencerPublisher {
|
|
|
544
581
|
// Get the transaction requests
|
|
545
582
|
const l1Requests = requestsToAnalyze.map((r)=>r.request);
|
|
546
583
|
// Start the analysis
|
|
547
|
-
const analysisId = await this.l1FeeAnalyzer.startAnalysis(l2SlotNumber, gasLimit > 0n ? gasLimit :
|
|
584
|
+
const analysisId = await this.l1FeeAnalyzer.startAnalysis(l2SlotNumber, gasLimit > 0n ? gasLimit : MAX_L1_TX_LIMIT, l1Requests, blobConfig, onComplete);
|
|
548
585
|
this.log.info('Started L1 fee analysis', {
|
|
549
586
|
analysisId,
|
|
550
587
|
l2SlotNumber: l2SlotNumber.toString(),
|
|
@@ -594,15 +631,24 @@ export class SequencerPublisher {
|
|
|
594
631
|
// @note - we can only have one blob config per bundle
|
|
595
632
|
// find requests with gas and blob configs
|
|
596
633
|
// See https://github.com/AztecProtocol/aztec-packages/issues/11513
|
|
597
|
-
const gasConfigs =
|
|
598
|
-
const blobConfigs =
|
|
634
|
+
const gasConfigs = validRequests.filter((request)=>request.gasConfig).map((request)=>request.gasConfig);
|
|
635
|
+
const blobConfigs = validRequests.filter((request)=>request.blobConfig).map((request)=>request.blobConfig);
|
|
599
636
|
if (blobConfigs.length > 1) {
|
|
600
637
|
throw new Error('Multiple blob configs found');
|
|
601
638
|
}
|
|
602
639
|
const blobConfig = blobConfigs[0];
|
|
603
640
|
// Merge gasConfigs. Yields the sum of gasLimits, and the earliest txTimeoutAt, or undefined if no gasConfig sets them.
|
|
604
641
|
const gasLimits = gasConfigs.map((g)=>g?.gasLimit).filter((g)=>g !== undefined);
|
|
605
|
-
|
|
642
|
+
let gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
|
|
643
|
+
// Cap at L1 block gas limit so the node accepts the tx ("gas limit too high" otherwise).
|
|
644
|
+
const maxGas = MAX_L1_TX_LIMIT;
|
|
645
|
+
if (gasLimit !== undefined && gasLimit > maxGas) {
|
|
646
|
+
this.log.debug('Capping bundled tx gas limit to L1 max', {
|
|
647
|
+
requested: gasLimit,
|
|
648
|
+
capped: maxGas
|
|
649
|
+
});
|
|
650
|
+
gasLimit = maxGas;
|
|
651
|
+
}
|
|
606
652
|
const txTimeoutAts = gasConfigs.map((g)=>g?.txTimeoutAt).filter((g)=>g !== undefined);
|
|
607
653
|
const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map((g)=>g.getTime()))) : undefined; // earliest
|
|
608
654
|
const txConfig = {
|
|
@@ -613,12 +659,34 @@ export class SequencerPublisher {
|
|
|
613
659
|
// This ensures the committee gets precomputed correctly
|
|
614
660
|
validRequests.sort((a, b)=>compareActions(a.action, b.action));
|
|
615
661
|
try {
|
|
662
|
+
// Capture context for failed tx backup before sending
|
|
663
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
664
|
+
const multicallData = encodeFunctionData({
|
|
665
|
+
abi: multicall3Abi,
|
|
666
|
+
functionName: 'aggregate3',
|
|
667
|
+
args: [
|
|
668
|
+
validRequests.map((r)=>({
|
|
669
|
+
target: r.request.to,
|
|
670
|
+
callData: r.request.data,
|
|
671
|
+
allowFailure: true
|
|
672
|
+
}))
|
|
673
|
+
]
|
|
674
|
+
});
|
|
675
|
+
const blobDataHex = blobConfig?.blobs?.map((b)=>toHex(b));
|
|
676
|
+
const txContext = {
|
|
677
|
+
multicallData,
|
|
678
|
+
blobData: blobDataHex,
|
|
679
|
+
l1BlockNumber
|
|
680
|
+
};
|
|
616
681
|
this.log.debug('Forwarding transactions', {
|
|
617
682
|
validRequests: validRequests.map((request)=>request.action),
|
|
618
683
|
txConfig
|
|
619
684
|
});
|
|
620
|
-
const result = await
|
|
621
|
-
|
|
685
|
+
const result = await this.forwardWithPublisherRotation(validRequests, txConfig, blobConfig);
|
|
686
|
+
if (result === undefined) {
|
|
687
|
+
return undefined;
|
|
688
|
+
}
|
|
689
|
+
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(validRequests, result, txContext);
|
|
622
690
|
return {
|
|
623
691
|
result,
|
|
624
692
|
expiredActions,
|
|
@@ -638,17 +706,83 @@ export class SequencerPublisher {
|
|
|
638
706
|
}
|
|
639
707
|
}
|
|
640
708
|
}
|
|
641
|
-
|
|
709
|
+
/**
|
|
710
|
+
* Forwards transactions via Multicall3, rotating to the next available publisher if a send
|
|
711
|
+
* failure occurs (i.e. the tx never reached the chain).
|
|
712
|
+
* On-chain reverts and simulation errors are returned as-is without rotation.
|
|
713
|
+
*/ async forwardWithPublisherRotation(validRequests, txConfig, blobConfig) {
|
|
714
|
+
const triedAddresses = [];
|
|
715
|
+
let currentPublisher = this.l1TxUtils;
|
|
716
|
+
while(true){
|
|
717
|
+
triedAddresses.push(currentPublisher.getSenderAddress());
|
|
718
|
+
try {
|
|
719
|
+
const result = await Multicall3.forward(validRequests.map((r)=>r.request), currentPublisher, txConfig, blobConfig, this.rollupContract.address, this.log);
|
|
720
|
+
this.l1TxUtils = currentPublisher;
|
|
721
|
+
return result;
|
|
722
|
+
} catch (err) {
|
|
723
|
+
if (err instanceof TimeoutError) {
|
|
724
|
+
throw err;
|
|
725
|
+
}
|
|
726
|
+
const viemError = formatViemError(err);
|
|
727
|
+
if (!this.getNextPublisher) {
|
|
728
|
+
this.log.error('Failed to publish bundled transactions', viemError);
|
|
729
|
+
return undefined;
|
|
730
|
+
}
|
|
731
|
+
this.log.warn(`Publisher ${currentPublisher.getSenderAddress()} failed to send, rotating to next publisher`, viemError);
|
|
732
|
+
const nextPublisher = await this.getNextPublisher([
|
|
733
|
+
...triedAddresses
|
|
734
|
+
]);
|
|
735
|
+
if (!nextPublisher) {
|
|
736
|
+
this.log.error('All available publishers exhausted, failed to publish bundled transactions');
|
|
737
|
+
return undefined;
|
|
738
|
+
}
|
|
739
|
+
currentPublisher = nextPublisher;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
callbackBundledTransactions(requests, result, txContext) {
|
|
642
744
|
const actionsListStr = requests.map((r)=>r.action).join(', ');
|
|
643
745
|
if (result instanceof FormattedViemError) {
|
|
644
746
|
this.log.error(`Failed to publish bundled transactions (${actionsListStr})`, result);
|
|
747
|
+
this.backupFailedTx({
|
|
748
|
+
id: keccak256(txContext.multicallData),
|
|
749
|
+
failureType: 'send-error',
|
|
750
|
+
request: {
|
|
751
|
+
to: MULTI_CALL_3_ADDRESS,
|
|
752
|
+
data: txContext.multicallData
|
|
753
|
+
},
|
|
754
|
+
blobData: txContext.blobData,
|
|
755
|
+
l1BlockNumber: txContext.l1BlockNumber.toString(),
|
|
756
|
+
error: {
|
|
757
|
+
message: result.message,
|
|
758
|
+
name: result.name
|
|
759
|
+
},
|
|
760
|
+
context: {
|
|
761
|
+
actions: requests.map((r)=>r.action),
|
|
762
|
+
requests: requests.map((r)=>({
|
|
763
|
+
action: r.action,
|
|
764
|
+
to: r.request.to,
|
|
765
|
+
data: r.request.data
|
|
766
|
+
})),
|
|
767
|
+
sender: this.getSenderAddress().toString()
|
|
768
|
+
}
|
|
769
|
+
});
|
|
645
770
|
return {
|
|
646
771
|
failedActions: requests.map((r)=>r.action)
|
|
647
772
|
};
|
|
648
773
|
} else {
|
|
649
774
|
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
650
775
|
result,
|
|
651
|
-
requests
|
|
776
|
+
requests: requests.map((r)=>({
|
|
777
|
+
...r,
|
|
778
|
+
// Avoid logging large blob data
|
|
779
|
+
blobConfig: r.blobConfig ? {
|
|
780
|
+
...r.blobConfig,
|
|
781
|
+
blobs: r.blobConfig.blobs.map((b)=>({
|
|
782
|
+
size: trimmedBytesLength(b)
|
|
783
|
+
}))
|
|
784
|
+
} : undefined
|
|
785
|
+
}))
|
|
652
786
|
});
|
|
653
787
|
const successfulActions = [];
|
|
654
788
|
const failedActions = [];
|
|
@@ -659,6 +793,37 @@ export class SequencerPublisher {
|
|
|
659
793
|
failedActions.push(request.action);
|
|
660
794
|
}
|
|
661
795
|
}
|
|
796
|
+
// Single backup for the whole reverted tx
|
|
797
|
+
if (failedActions.length > 0 && result?.receipt?.status === 'reverted') {
|
|
798
|
+
this.backupFailedTx({
|
|
799
|
+
id: result.receipt.transactionHash,
|
|
800
|
+
failureType: 'revert',
|
|
801
|
+
request: {
|
|
802
|
+
to: MULTI_CALL_3_ADDRESS,
|
|
803
|
+
data: txContext.multicallData
|
|
804
|
+
},
|
|
805
|
+
blobData: txContext.blobData,
|
|
806
|
+
l1BlockNumber: result.receipt.blockNumber.toString(),
|
|
807
|
+
receipt: {
|
|
808
|
+
transactionHash: result.receipt.transactionHash,
|
|
809
|
+
blockNumber: result.receipt.blockNumber.toString(),
|
|
810
|
+
gasUsed: (result.receipt.gasUsed ?? 0n).toString(),
|
|
811
|
+
status: 'reverted'
|
|
812
|
+
},
|
|
813
|
+
error: {
|
|
814
|
+
message: result.errorMsg ?? 'Transaction reverted'
|
|
815
|
+
},
|
|
816
|
+
context: {
|
|
817
|
+
actions: failedActions,
|
|
818
|
+
requests: requests.filter((r)=>failedActions.includes(r.action)).map((r)=>({
|
|
819
|
+
action: r.action,
|
|
820
|
+
to: r.request.to,
|
|
821
|
+
data: r.request.data
|
|
822
|
+
})),
|
|
823
|
+
sender: this.getSenderAddress().toString()
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
}
|
|
662
827
|
return {
|
|
663
828
|
successfulActions,
|
|
664
829
|
failedActions
|
|
@@ -666,17 +831,20 @@ export class SequencerPublisher {
|
|
|
666
831
|
}
|
|
667
832
|
}
|
|
668
833
|
/**
|
|
669
|
-
* @notice Will call `
|
|
834
|
+
* @notice Will call `canProposeAt` to make sure that it is possible to propose
|
|
670
835
|
* @param tipArchive - The archive to check
|
|
671
836
|
* @returns The slot and block number if it is possible to propose, undefined otherwise
|
|
672
|
-
*/
|
|
837
|
+
*/ canProposeAt(tipArchive, msgSender, opts = {}) {
|
|
673
838
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
674
839
|
const ignoredErrors = [
|
|
675
840
|
'SlotAlreadyInChain',
|
|
676
841
|
'InvalidProposer',
|
|
677
842
|
'InvalidArchive'
|
|
678
843
|
];
|
|
679
|
-
|
|
844
|
+
const pipelined = opts.pipelined ?? this.epochCache.isProposerPipeliningEnabled();
|
|
845
|
+
const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
|
|
846
|
+
const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
|
|
847
|
+
return this.rollupContract.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
|
|
680
848
|
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber
|
|
681
849
|
}).catch((err)=>{
|
|
682
850
|
if (err instanceof FormattedViemError && ignoredErrors.find((e)=>err.message.includes(e))) {
|
|
@@ -708,7 +876,7 @@ export class SequencerPublisher {
|
|
|
708
876
|
header.blobsHash.toString(),
|
|
709
877
|
flags
|
|
710
878
|
];
|
|
711
|
-
const ts =
|
|
879
|
+
const ts = this.getNextL1SlotTimestamp();
|
|
712
880
|
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(opts?.forcePendingCheckpointNumber);
|
|
713
881
|
let balance = 0n;
|
|
714
882
|
if (this.config.fishermanMode) {
|
|
@@ -761,8 +929,12 @@ export class SequencerPublisher {
|
|
|
761
929
|
...logData,
|
|
762
930
|
request
|
|
763
931
|
});
|
|
932
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
764
933
|
try {
|
|
765
|
-
const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined,
|
|
934
|
+
const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, mergeAbis([
|
|
935
|
+
request.abi ?? [],
|
|
936
|
+
ErrorsAbi
|
|
937
|
+
]));
|
|
766
938
|
this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} succeeded`, {
|
|
767
939
|
...logData,
|
|
768
940
|
request,
|
|
@@ -779,7 +951,7 @@ export class SequencerPublisher {
|
|
|
779
951
|
const viemError = formatViemError(err);
|
|
780
952
|
// If the error is due to the checkpoint not being in the pending chain, and it was indeed removed by someone else,
|
|
781
953
|
// we can safely ignore it and return undefined so we go ahead with checkpoint building.
|
|
782
|
-
if (viemError.message?.includes('
|
|
954
|
+
if (viemError.message?.includes('Rollup__CheckpointNotInPendingChain')) {
|
|
783
955
|
this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`, {
|
|
784
956
|
...logData,
|
|
785
957
|
request,
|
|
@@ -800,6 +972,27 @@ export class SequencerPublisher {
|
|
|
800
972
|
}
|
|
801
973
|
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
802
974
|
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
|
|
975
|
+
this.backupFailedTx({
|
|
976
|
+
id: keccak256(request.data),
|
|
977
|
+
failureType: 'simulation',
|
|
978
|
+
request: {
|
|
979
|
+
to: request.to,
|
|
980
|
+
data: request.data,
|
|
981
|
+
value: request.value?.toString()
|
|
982
|
+
},
|
|
983
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
984
|
+
error: {
|
|
985
|
+
message: viemError.message,
|
|
986
|
+
name: viemError.name
|
|
987
|
+
},
|
|
988
|
+
context: {
|
|
989
|
+
actions: [
|
|
990
|
+
`invalidate-${reason}`
|
|
991
|
+
],
|
|
992
|
+
checkpointNumber,
|
|
993
|
+
sender: this.getSenderAddress().toString()
|
|
994
|
+
}
|
|
995
|
+
});
|
|
803
996
|
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, {
|
|
804
997
|
cause: viemError
|
|
805
998
|
});
|
|
@@ -826,30 +1019,21 @@ export class SequencerPublisher {
|
|
|
826
1019
|
}
|
|
827
1020
|
}
|
|
828
1021
|
/** Simulates `propose` to make sure that the checkpoint is valid for submission */ async validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, options) {
|
|
829
|
-
|
|
830
|
-
//
|
|
831
|
-
//
|
|
832
|
-
//
|
|
833
|
-
//
|
|
834
|
-
|
|
835
|
-
// const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
|
|
836
|
-
// if (!committee) {
|
|
837
|
-
// this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
838
|
-
// throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
839
|
-
// }
|
|
840
|
-
// attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
841
|
-
// CommitteeAttestation.fromAddress(committeeMember),
|
|
842
|
-
// );
|
|
843
|
-
// }
|
|
1022
|
+
// When pipelining, the checkpoint targets the next slot so its timestamp is in the future.
|
|
1023
|
+
// Without pipelining, the checkpoint targets the current slot so its timestamp is in the past
|
|
1024
|
+
// by the time we simulate (~24s of build time), causing eth_simulateV1 to reject it.
|
|
1025
|
+
// In that case, use the latest L1 block timestamp + one ethereum slot, which is just ahead
|
|
1026
|
+
// of L1 and still within the same L2 slot.
|
|
1027
|
+
const ts = this.epochCache.isProposerPipeliningEnabled() ? checkpoint.header.timestamp : (await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration;
|
|
844
1028
|
const blobFields = checkpoint.toBlobFields();
|
|
845
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
1029
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
846
1030
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
847
1031
|
const args = [
|
|
848
1032
|
{
|
|
849
1033
|
header: checkpoint.header.toViem(),
|
|
850
1034
|
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
851
1035
|
oracleInput: {
|
|
852
|
-
feeAssetPriceModifier:
|
|
1036
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier
|
|
853
1037
|
}
|
|
854
1038
|
},
|
|
855
1039
|
attestationsAndSigners.getPackedAttestations(),
|
|
@@ -884,6 +1068,28 @@ export class SequencerPublisher {
|
|
|
884
1068
|
this.log.warn(`Skipping vote cast for payload with empty code`);
|
|
885
1069
|
return false;
|
|
886
1070
|
}
|
|
1071
|
+
// Check if payload was already submitted to governance
|
|
1072
|
+
const cacheKey = payload.toString();
|
|
1073
|
+
if (!this.payloadProposedCache.has(cacheKey)) {
|
|
1074
|
+
try {
|
|
1075
|
+
const l1StartBlock = await this.rollupContract.getL1StartBlock();
|
|
1076
|
+
const proposed = await retry(()=>base.hasPayloadBeenProposed(payload.toString(), l1StartBlock), 'Check if payload was proposed', makeBackoff([
|
|
1077
|
+
0,
|
|
1078
|
+
1,
|
|
1079
|
+
2
|
|
1080
|
+
]), this.log, true);
|
|
1081
|
+
if (proposed) {
|
|
1082
|
+
this.payloadProposedCache.add(cacheKey);
|
|
1083
|
+
}
|
|
1084
|
+
} catch (err) {
|
|
1085
|
+
this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
|
|
1086
|
+
return false;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
if (this.payloadProposedCache.has(cacheKey)) {
|
|
1090
|
+
this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
|
|
1091
|
+
return false;
|
|
1092
|
+
}
|
|
887
1093
|
const cachedLastVote = this.lastActions[signalType];
|
|
888
1094
|
this.lastActions[signalType] = slotNumber;
|
|
889
1095
|
const action = signalType;
|
|
@@ -894,15 +1100,41 @@ export class SequencerPublisher {
|
|
|
894
1100
|
signer: this.l1TxUtils.client.account?.address,
|
|
895
1101
|
lastValidL2Slot: slotNumber
|
|
896
1102
|
});
|
|
1103
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
897
1104
|
try {
|
|
898
1105
|
await this.l1TxUtils.simulate(request, {
|
|
899
1106
|
time: timestamp
|
|
900
|
-
}, [],
|
|
1107
|
+
}, [], mergeAbis([
|
|
1108
|
+
request.abi ?? [],
|
|
1109
|
+
ErrorsAbi
|
|
1110
|
+
]));
|
|
901
1111
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, {
|
|
902
1112
|
request
|
|
903
1113
|
});
|
|
904
1114
|
} catch (err) {
|
|
905
|
-
|
|
1115
|
+
const viemError = formatViemError(err);
|
|
1116
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError);
|
|
1117
|
+
this.backupFailedTx({
|
|
1118
|
+
id: keccak256(request.data),
|
|
1119
|
+
failureType: 'simulation',
|
|
1120
|
+
request: {
|
|
1121
|
+
to: request.to,
|
|
1122
|
+
data: request.data,
|
|
1123
|
+
value: request.value?.toString()
|
|
1124
|
+
},
|
|
1125
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1126
|
+
error: {
|
|
1127
|
+
message: viemError.message,
|
|
1128
|
+
name: viemError.name
|
|
1129
|
+
},
|
|
1130
|
+
context: {
|
|
1131
|
+
actions: [
|
|
1132
|
+
action
|
|
1133
|
+
],
|
|
1134
|
+
slot: slotNumber,
|
|
1135
|
+
sender: this.getSenderAddress().toString()
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
906
1138
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
907
1139
|
}
|
|
908
1140
|
// TODO(palla/slash): All votes (governance and slashing) should txTimeoutAt at the end of the slot.
|
|
@@ -1041,13 +1273,14 @@ export class SequencerPublisher {
|
|
|
1041
1273
|
/** Simulates and enqueues a proposal for a checkpoint on L1 */ async enqueueProposeCheckpoint(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, opts = {}) {
|
|
1042
1274
|
const checkpointHeader = checkpoint.header;
|
|
1043
1275
|
const blobFields = checkpoint.toBlobFields();
|
|
1044
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
1276
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
1045
1277
|
const proposeTxArgs = {
|
|
1046
1278
|
header: checkpointHeader,
|
|
1047
1279
|
archive: checkpoint.archive.root.toBuffer(),
|
|
1048
1280
|
blobs,
|
|
1049
1281
|
attestationsAndSigners,
|
|
1050
|
-
attestationsAndSignersSignature
|
|
1282
|
+
attestationsAndSignersSignature,
|
|
1283
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier
|
|
1051
1284
|
};
|
|
1052
1285
|
let ts;
|
|
1053
1286
|
try {
|
|
@@ -1123,28 +1356,60 @@ export class SequencerPublisher {
|
|
|
1123
1356
|
const cachedLastActionSlot = this.lastActions[action];
|
|
1124
1357
|
this.lastActions[action] = slotNumber;
|
|
1125
1358
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
1359
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1126
1360
|
let gasUsed;
|
|
1361
|
+
const simulateAbi = mergeAbis([
|
|
1362
|
+
request.abi ?? [],
|
|
1363
|
+
ErrorsAbi
|
|
1364
|
+
]);
|
|
1127
1365
|
try {
|
|
1128
1366
|
({ gasUsed } = await this.l1TxUtils.simulate(request, {
|
|
1129
1367
|
time: timestamp
|
|
1130
|
-
}, [],
|
|
1368
|
+
}, [], simulateAbi)); // TODO(palla/slash): Check the timestamp logic
|
|
1131
1369
|
this.log.verbose(`Simulation for ${action} succeeded`, {
|
|
1132
1370
|
...logData,
|
|
1133
1371
|
request,
|
|
1134
1372
|
gasUsed
|
|
1135
1373
|
});
|
|
1136
1374
|
} catch (err) {
|
|
1137
|
-
const viemError = formatViemError(err);
|
|
1375
|
+
const viemError = formatViemError(err, simulateAbi);
|
|
1138
1376
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1377
|
+
this.backupFailedTx({
|
|
1378
|
+
id: keccak256(request.data),
|
|
1379
|
+
failureType: 'simulation',
|
|
1380
|
+
request: {
|
|
1381
|
+
to: request.to,
|
|
1382
|
+
data: request.data,
|
|
1383
|
+
value: request.value?.toString()
|
|
1384
|
+
},
|
|
1385
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1386
|
+
error: {
|
|
1387
|
+
message: viemError.message,
|
|
1388
|
+
name: viemError.name
|
|
1389
|
+
},
|
|
1390
|
+
context: {
|
|
1391
|
+
actions: [
|
|
1392
|
+
action
|
|
1393
|
+
],
|
|
1394
|
+
slot: slotNumber,
|
|
1395
|
+
sender: this.getSenderAddress().toString()
|
|
1396
|
+
}
|
|
1397
|
+
});
|
|
1139
1398
|
return false;
|
|
1140
1399
|
}
|
|
1141
1400
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
1142
1401
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil(Number(gasUsed) * 64 / 63)));
|
|
1143
1402
|
logData.gasLimit = gasLimit;
|
|
1403
|
+
// Store the ABI used for simulation on the request so Multicall3.forward can decode errors
|
|
1404
|
+
// when the tx is sent and a revert is diagnosed via simulation.
|
|
1405
|
+
const requestWithAbi = {
|
|
1406
|
+
...request,
|
|
1407
|
+
abi: simulateAbi
|
|
1408
|
+
};
|
|
1144
1409
|
this.log.debug(`Enqueuing ${action}`, logData);
|
|
1145
1410
|
this.addRequest({
|
|
1146
1411
|
action,
|
|
1147
|
-
request,
|
|
1412
|
+
request: requestWithAbi,
|
|
1148
1413
|
gasConfig: {
|
|
1149
1414
|
gasLimit
|
|
1150
1415
|
},
|
|
@@ -1208,10 +1473,38 @@ export class SequencerPublisher {
|
|
|
1208
1473
|
}, {}, {
|
|
1209
1474
|
blobs: encodedData.blobs.map((b)=>b.data),
|
|
1210
1475
|
kzg
|
|
1211
|
-
}).catch((err)=>{
|
|
1212
|
-
const
|
|
1213
|
-
this.log.error(`Failed to validate blobs`, message, {
|
|
1214
|
-
metaMessages
|
|
1476
|
+
}).catch(async (err)=>{
|
|
1477
|
+
const viemError = formatViemError(err);
|
|
1478
|
+
this.log.error(`Failed to validate blobs`, viemError.message, {
|
|
1479
|
+
metaMessages: viemError.metaMessages
|
|
1480
|
+
});
|
|
1481
|
+
const validateBlobsData = encodeFunctionData({
|
|
1482
|
+
abi: RollupAbi,
|
|
1483
|
+
functionName: 'validateBlobs',
|
|
1484
|
+
args: [
|
|
1485
|
+
blobInput
|
|
1486
|
+
]
|
|
1487
|
+
});
|
|
1488
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1489
|
+
this.backupFailedTx({
|
|
1490
|
+
id: keccak256(validateBlobsData),
|
|
1491
|
+
failureType: 'simulation',
|
|
1492
|
+
request: {
|
|
1493
|
+
to: this.rollupContract.address,
|
|
1494
|
+
data: validateBlobsData
|
|
1495
|
+
},
|
|
1496
|
+
blobData: encodedData.blobs.map((b)=>toHex(b.data)),
|
|
1497
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1498
|
+
error: {
|
|
1499
|
+
message: viemError.message,
|
|
1500
|
+
name: viemError.name
|
|
1501
|
+
},
|
|
1502
|
+
context: {
|
|
1503
|
+
actions: [
|
|
1504
|
+
'validate-blobs'
|
|
1505
|
+
],
|
|
1506
|
+
sender: this.getSenderAddress().toString()
|
|
1507
|
+
}
|
|
1215
1508
|
});
|
|
1216
1509
|
throw new Error('Failed to validate blobs');
|
|
1217
1510
|
});
|
|
@@ -1222,8 +1515,7 @@ export class SequencerPublisher {
|
|
|
1222
1515
|
header: encodedData.header.toViem(),
|
|
1223
1516
|
archive: toHex(encodedData.archive),
|
|
1224
1517
|
oracleInput: {
|
|
1225
|
-
|
|
1226
|
-
feeAssetPriceModifier: 0n
|
|
1518
|
+
feeAssetPriceModifier: encodedData.feeAssetPriceModifier
|
|
1227
1519
|
}
|
|
1228
1520
|
},
|
|
1229
1521
|
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
@@ -1272,10 +1564,11 @@ export class SequencerPublisher {
|
|
|
1272
1564
|
balance: 10n * WEI_CONST * WEI_CONST
|
|
1273
1565
|
});
|
|
1274
1566
|
}
|
|
1567
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1275
1568
|
const simulationResult = await this.l1TxUtils.simulate({
|
|
1276
1569
|
to: this.rollupContract.address,
|
|
1277
1570
|
data: rollupData,
|
|
1278
|
-
gas:
|
|
1571
|
+
gas: MAX_L1_TX_LIMIT,
|
|
1279
1572
|
...this.proposerAddressForSimulation && {
|
|
1280
1573
|
from: this.proposerAddressForSimulation.toString()
|
|
1281
1574
|
}
|
|
@@ -1283,10 +1576,10 @@ export class SequencerPublisher {
|
|
|
1283
1576
|
// @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
|
|
1284
1577
|
time: timestamp + 1n,
|
|
1285
1578
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1286
|
-
gasLimit:
|
|
1579
|
+
gasLimit: MAX_L1_TX_LIMIT * 2n
|
|
1287
1580
|
}, stateOverrides, RollupAbi, {
|
|
1288
1581
|
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
1289
|
-
fallbackGasEstimate:
|
|
1582
|
+
fallbackGasEstimate: MAX_L1_TX_LIMIT
|
|
1290
1583
|
}).catch((err)=>{
|
|
1291
1584
|
// In fisherman mode, we expect ValidatorSelection__MissingProposerSignature since fisherman doesn't have proposer signature
|
|
1292
1585
|
const viemError = formatViemError(err);
|
|
@@ -1294,11 +1587,31 @@ export class SequencerPublisher {
|
|
|
1294
1587
|
this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
|
|
1295
1588
|
// Return a minimal simulation result with the fallback gas estimate
|
|
1296
1589
|
return {
|
|
1297
|
-
gasUsed:
|
|
1590
|
+
gasUsed: MAX_L1_TX_LIMIT,
|
|
1298
1591
|
logs: []
|
|
1299
1592
|
};
|
|
1300
1593
|
}
|
|
1301
1594
|
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1595
|
+
this.backupFailedTx({
|
|
1596
|
+
id: keccak256(rollupData),
|
|
1597
|
+
failureType: 'simulation',
|
|
1598
|
+
request: {
|
|
1599
|
+
to: this.rollupContract.address,
|
|
1600
|
+
data: rollupData
|
|
1601
|
+
},
|
|
1602
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1603
|
+
error: {
|
|
1604
|
+
message: viemError.message,
|
|
1605
|
+
name: viemError.name
|
|
1606
|
+
},
|
|
1607
|
+
context: {
|
|
1608
|
+
actions: [
|
|
1609
|
+
'propose'
|
|
1610
|
+
],
|
|
1611
|
+
slot: Number(args[0].header.slotNumber),
|
|
1612
|
+
sender: this.getSenderAddress().toString()
|
|
1613
|
+
}
|
|
1614
|
+
});
|
|
1302
1615
|
throw err;
|
|
1303
1616
|
});
|
|
1304
1617
|
return {
|
|
@@ -1375,4 +1688,8 @@ export class SequencerPublisher {
|
|
|
1375
1688
|
}
|
|
1376
1689
|
});
|
|
1377
1690
|
}
|
|
1691
|
+
/** Returns the timestamp to use when simulating L1 proposal calls */ getNextL1SlotTimestamp() {
|
|
1692
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1693
|
+
return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
|
|
1694
|
+
}
|
|
1378
1695
|
}
|