@aztec/sequencer-client 0.0.1-commit.c2595eba → 0.0.1-commit.c2eed6949
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 -28
- 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 +22 -21
- 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.d.ts +33 -10
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +371 -57
- package/dest/sequencer/checkpoint_proposal_job.d.ts +39 -10
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +287 -167
- 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 +97 -15
- 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 +12 -12
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +45 -36
- 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 +27 -28
- package/src/client/sequencer-client.ts +76 -23
- package/src/config.ts +65 -38
- package/src/global_variable_builder/global_builder.ts +23 -24
- 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.ts +371 -70
- package/src/sequencer/checkpoint_proposal_job.ts +392 -193
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/metrics.ts +106 -18
- package/src/sequencer/sequencer.ts +131 -94
- 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 +65 -53
- package/src/test/utils.ts +5 -2
|
@@ -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,18 @@ 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
|
-
// so that the committee is recalculated correctly
|
|
833
|
-
// const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
834
|
-
// if (ignoreSignatures) {
|
|
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
|
+
// Anchor the simulation timestamp to the checkpoint's own slot start time
|
|
1023
|
+
// rather than the current L1 block timestamp, which may overshoot into the next slot if the build ran late.
|
|
1024
|
+
const ts = checkpoint.header.timestamp;
|
|
844
1025
|
const blobFields = checkpoint.toBlobFields();
|
|
845
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
1026
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
846
1027
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
847
1028
|
const args = [
|
|
848
1029
|
{
|
|
849
1030
|
header: checkpoint.header.toViem(),
|
|
850
1031
|
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
851
1032
|
oracleInput: {
|
|
852
|
-
feeAssetPriceModifier:
|
|
1033
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier
|
|
853
1034
|
}
|
|
854
1035
|
},
|
|
855
1036
|
attestationsAndSigners.getPackedAttestations(),
|
|
@@ -884,6 +1065,28 @@ export class SequencerPublisher {
|
|
|
884
1065
|
this.log.warn(`Skipping vote cast for payload with empty code`);
|
|
885
1066
|
return false;
|
|
886
1067
|
}
|
|
1068
|
+
// Check if payload was already submitted to governance
|
|
1069
|
+
const cacheKey = payload.toString();
|
|
1070
|
+
if (!this.payloadProposedCache.has(cacheKey)) {
|
|
1071
|
+
try {
|
|
1072
|
+
const l1StartBlock = await this.rollupContract.getL1StartBlock();
|
|
1073
|
+
const proposed = await retry(()=>base.hasPayloadBeenProposed(payload.toString(), l1StartBlock), 'Check if payload was proposed', makeBackoff([
|
|
1074
|
+
0,
|
|
1075
|
+
1,
|
|
1076
|
+
2
|
|
1077
|
+
]), this.log, true);
|
|
1078
|
+
if (proposed) {
|
|
1079
|
+
this.payloadProposedCache.add(cacheKey);
|
|
1080
|
+
}
|
|
1081
|
+
} catch (err) {
|
|
1082
|
+
this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
|
|
1083
|
+
return false;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
if (this.payloadProposedCache.has(cacheKey)) {
|
|
1087
|
+
this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
|
|
1088
|
+
return false;
|
|
1089
|
+
}
|
|
887
1090
|
const cachedLastVote = this.lastActions[signalType];
|
|
888
1091
|
this.lastActions[signalType] = slotNumber;
|
|
889
1092
|
const action = signalType;
|
|
@@ -894,15 +1097,41 @@ export class SequencerPublisher {
|
|
|
894
1097
|
signer: this.l1TxUtils.client.account?.address,
|
|
895
1098
|
lastValidL2Slot: slotNumber
|
|
896
1099
|
});
|
|
1100
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
897
1101
|
try {
|
|
898
1102
|
await this.l1TxUtils.simulate(request, {
|
|
899
1103
|
time: timestamp
|
|
900
|
-
}, [],
|
|
1104
|
+
}, [], mergeAbis([
|
|
1105
|
+
request.abi ?? [],
|
|
1106
|
+
ErrorsAbi
|
|
1107
|
+
]));
|
|
901
1108
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, {
|
|
902
1109
|
request
|
|
903
1110
|
});
|
|
904
1111
|
} catch (err) {
|
|
905
|
-
|
|
1112
|
+
const viemError = formatViemError(err);
|
|
1113
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError);
|
|
1114
|
+
this.backupFailedTx({
|
|
1115
|
+
id: keccak256(request.data),
|
|
1116
|
+
failureType: 'simulation',
|
|
1117
|
+
request: {
|
|
1118
|
+
to: request.to,
|
|
1119
|
+
data: request.data,
|
|
1120
|
+
value: request.value?.toString()
|
|
1121
|
+
},
|
|
1122
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1123
|
+
error: {
|
|
1124
|
+
message: viemError.message,
|
|
1125
|
+
name: viemError.name
|
|
1126
|
+
},
|
|
1127
|
+
context: {
|
|
1128
|
+
actions: [
|
|
1129
|
+
action
|
|
1130
|
+
],
|
|
1131
|
+
slot: slotNumber,
|
|
1132
|
+
sender: this.getSenderAddress().toString()
|
|
1133
|
+
}
|
|
1134
|
+
});
|
|
906
1135
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
907
1136
|
}
|
|
908
1137
|
// TODO(palla/slash): All votes (governance and slashing) should txTimeoutAt at the end of the slot.
|
|
@@ -1041,13 +1270,14 @@ export class SequencerPublisher {
|
|
|
1041
1270
|
/** Simulates and enqueues a proposal for a checkpoint on L1 */ async enqueueProposeCheckpoint(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, opts = {}) {
|
|
1042
1271
|
const checkpointHeader = checkpoint.header;
|
|
1043
1272
|
const blobFields = checkpoint.toBlobFields();
|
|
1044
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
1273
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
1045
1274
|
const proposeTxArgs = {
|
|
1046
1275
|
header: checkpointHeader,
|
|
1047
1276
|
archive: checkpoint.archive.root.toBuffer(),
|
|
1048
1277
|
blobs,
|
|
1049
1278
|
attestationsAndSigners,
|
|
1050
|
-
attestationsAndSignersSignature
|
|
1279
|
+
attestationsAndSignersSignature,
|
|
1280
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier
|
|
1051
1281
|
};
|
|
1052
1282
|
let ts;
|
|
1053
1283
|
try {
|
|
@@ -1123,28 +1353,60 @@ export class SequencerPublisher {
|
|
|
1123
1353
|
const cachedLastActionSlot = this.lastActions[action];
|
|
1124
1354
|
this.lastActions[action] = slotNumber;
|
|
1125
1355
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
1356
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1126
1357
|
let gasUsed;
|
|
1358
|
+
const simulateAbi = mergeAbis([
|
|
1359
|
+
request.abi ?? [],
|
|
1360
|
+
ErrorsAbi
|
|
1361
|
+
]);
|
|
1127
1362
|
try {
|
|
1128
1363
|
({ gasUsed } = await this.l1TxUtils.simulate(request, {
|
|
1129
1364
|
time: timestamp
|
|
1130
|
-
}, [],
|
|
1365
|
+
}, [], simulateAbi)); // TODO(palla/slash): Check the timestamp logic
|
|
1131
1366
|
this.log.verbose(`Simulation for ${action} succeeded`, {
|
|
1132
1367
|
...logData,
|
|
1133
1368
|
request,
|
|
1134
1369
|
gasUsed
|
|
1135
1370
|
});
|
|
1136
1371
|
} catch (err) {
|
|
1137
|
-
const viemError = formatViemError(err);
|
|
1372
|
+
const viemError = formatViemError(err, simulateAbi);
|
|
1138
1373
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1374
|
+
this.backupFailedTx({
|
|
1375
|
+
id: keccak256(request.data),
|
|
1376
|
+
failureType: 'simulation',
|
|
1377
|
+
request: {
|
|
1378
|
+
to: request.to,
|
|
1379
|
+
data: request.data,
|
|
1380
|
+
value: request.value?.toString()
|
|
1381
|
+
},
|
|
1382
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1383
|
+
error: {
|
|
1384
|
+
message: viemError.message,
|
|
1385
|
+
name: viemError.name
|
|
1386
|
+
},
|
|
1387
|
+
context: {
|
|
1388
|
+
actions: [
|
|
1389
|
+
action
|
|
1390
|
+
],
|
|
1391
|
+
slot: slotNumber,
|
|
1392
|
+
sender: this.getSenderAddress().toString()
|
|
1393
|
+
}
|
|
1394
|
+
});
|
|
1139
1395
|
return false;
|
|
1140
1396
|
}
|
|
1141
1397
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
1142
1398
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil(Number(gasUsed) * 64 / 63)));
|
|
1143
1399
|
logData.gasLimit = gasLimit;
|
|
1400
|
+
// Store the ABI used for simulation on the request so Multicall3.forward can decode errors
|
|
1401
|
+
// when the tx is sent and a revert is diagnosed via simulation.
|
|
1402
|
+
const requestWithAbi = {
|
|
1403
|
+
...request,
|
|
1404
|
+
abi: simulateAbi
|
|
1405
|
+
};
|
|
1144
1406
|
this.log.debug(`Enqueuing ${action}`, logData);
|
|
1145
1407
|
this.addRequest({
|
|
1146
1408
|
action,
|
|
1147
|
-
request,
|
|
1409
|
+
request: requestWithAbi,
|
|
1148
1410
|
gasConfig: {
|
|
1149
1411
|
gasLimit
|
|
1150
1412
|
},
|
|
@@ -1208,10 +1470,38 @@ export class SequencerPublisher {
|
|
|
1208
1470
|
}, {}, {
|
|
1209
1471
|
blobs: encodedData.blobs.map((b)=>b.data),
|
|
1210
1472
|
kzg
|
|
1211
|
-
}).catch((err)=>{
|
|
1212
|
-
const
|
|
1213
|
-
this.log.error(`Failed to validate blobs`, message, {
|
|
1214
|
-
metaMessages
|
|
1473
|
+
}).catch(async (err)=>{
|
|
1474
|
+
const viemError = formatViemError(err);
|
|
1475
|
+
this.log.error(`Failed to validate blobs`, viemError.message, {
|
|
1476
|
+
metaMessages: viemError.metaMessages
|
|
1477
|
+
});
|
|
1478
|
+
const validateBlobsData = encodeFunctionData({
|
|
1479
|
+
abi: RollupAbi,
|
|
1480
|
+
functionName: 'validateBlobs',
|
|
1481
|
+
args: [
|
|
1482
|
+
blobInput
|
|
1483
|
+
]
|
|
1484
|
+
});
|
|
1485
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1486
|
+
this.backupFailedTx({
|
|
1487
|
+
id: keccak256(validateBlobsData),
|
|
1488
|
+
failureType: 'simulation',
|
|
1489
|
+
request: {
|
|
1490
|
+
to: this.rollupContract.address,
|
|
1491
|
+
data: validateBlobsData
|
|
1492
|
+
},
|
|
1493
|
+
blobData: encodedData.blobs.map((b)=>toHex(b.data)),
|
|
1494
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1495
|
+
error: {
|
|
1496
|
+
message: viemError.message,
|
|
1497
|
+
name: viemError.name
|
|
1498
|
+
},
|
|
1499
|
+
context: {
|
|
1500
|
+
actions: [
|
|
1501
|
+
'validate-blobs'
|
|
1502
|
+
],
|
|
1503
|
+
sender: this.getSenderAddress().toString()
|
|
1504
|
+
}
|
|
1215
1505
|
});
|
|
1216
1506
|
throw new Error('Failed to validate blobs');
|
|
1217
1507
|
});
|
|
@@ -1222,8 +1512,7 @@ export class SequencerPublisher {
|
|
|
1222
1512
|
header: encodedData.header.toViem(),
|
|
1223
1513
|
archive: toHex(encodedData.archive),
|
|
1224
1514
|
oracleInput: {
|
|
1225
|
-
|
|
1226
|
-
feeAssetPriceModifier: 0n
|
|
1515
|
+
feeAssetPriceModifier: encodedData.feeAssetPriceModifier
|
|
1227
1516
|
}
|
|
1228
1517
|
},
|
|
1229
1518
|
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
@@ -1272,10 +1561,11 @@ export class SequencerPublisher {
|
|
|
1272
1561
|
balance: 10n * WEI_CONST * WEI_CONST
|
|
1273
1562
|
});
|
|
1274
1563
|
}
|
|
1564
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1275
1565
|
const simulationResult = await this.l1TxUtils.simulate({
|
|
1276
1566
|
to: this.rollupContract.address,
|
|
1277
1567
|
data: rollupData,
|
|
1278
|
-
gas:
|
|
1568
|
+
gas: MAX_L1_TX_LIMIT,
|
|
1279
1569
|
...this.proposerAddressForSimulation && {
|
|
1280
1570
|
from: this.proposerAddressForSimulation.toString()
|
|
1281
1571
|
}
|
|
@@ -1283,10 +1573,10 @@ export class SequencerPublisher {
|
|
|
1283
1573
|
// @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
|
|
1284
1574
|
time: timestamp + 1n,
|
|
1285
1575
|
// @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:
|
|
1576
|
+
gasLimit: MAX_L1_TX_LIMIT * 2n
|
|
1287
1577
|
}, stateOverrides, RollupAbi, {
|
|
1288
1578
|
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
1289
|
-
fallbackGasEstimate:
|
|
1579
|
+
fallbackGasEstimate: MAX_L1_TX_LIMIT
|
|
1290
1580
|
}).catch((err)=>{
|
|
1291
1581
|
// In fisherman mode, we expect ValidatorSelection__MissingProposerSignature since fisherman doesn't have proposer signature
|
|
1292
1582
|
const viemError = formatViemError(err);
|
|
@@ -1294,11 +1584,31 @@ export class SequencerPublisher {
|
|
|
1294
1584
|
this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
|
|
1295
1585
|
// Return a minimal simulation result with the fallback gas estimate
|
|
1296
1586
|
return {
|
|
1297
|
-
gasUsed:
|
|
1587
|
+
gasUsed: MAX_L1_TX_LIMIT,
|
|
1298
1588
|
logs: []
|
|
1299
1589
|
};
|
|
1300
1590
|
}
|
|
1301
1591
|
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1592
|
+
this.backupFailedTx({
|
|
1593
|
+
id: keccak256(rollupData),
|
|
1594
|
+
failureType: 'simulation',
|
|
1595
|
+
request: {
|
|
1596
|
+
to: this.rollupContract.address,
|
|
1597
|
+
data: rollupData
|
|
1598
|
+
},
|
|
1599
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1600
|
+
error: {
|
|
1601
|
+
message: viemError.message,
|
|
1602
|
+
name: viemError.name
|
|
1603
|
+
},
|
|
1604
|
+
context: {
|
|
1605
|
+
actions: [
|
|
1606
|
+
'propose'
|
|
1607
|
+
],
|
|
1608
|
+
slot: Number(args[0].header.slotNumber),
|
|
1609
|
+
sender: this.getSenderAddress().toString()
|
|
1610
|
+
}
|
|
1611
|
+
});
|
|
1302
1612
|
throw err;
|
|
1303
1613
|
});
|
|
1304
1614
|
return {
|
|
@@ -1375,4 +1685,8 @@ export class SequencerPublisher {
|
|
|
1375
1685
|
}
|
|
1376
1686
|
});
|
|
1377
1687
|
}
|
|
1688
|
+
/** Returns the timestamp to use when simulating L1 proposal calls */ getNextL1SlotTimestamp() {
|
|
1689
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1690
|
+
return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
|
|
1691
|
+
}
|
|
1378
1692
|
}
|