@aztec/sequencer-client 0.0.1-commit.9d2bcf6d → 0.0.1-commit.9ef841308
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 +39 -13
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +364 -66
- package/dest/sequencer/checkpoint_proposal_job.d.ts +15 -7
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +241 -140
- package/dest/sequencer/checkpoint_voter.d.ts +1 -2
- package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_voter.js +2 -5
- 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 +28 -15
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +93 -84
- 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 +11 -11
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +45 -34
- 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 +359 -86
- package/src/sequencer/checkpoint_proposal_job.ts +328 -151
- package/src/sequencer/checkpoint_voter.ts +1 -12
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/metrics.ts +106 -18
- package/src/sequencer/sequencer.ts +127 -96
- 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 +63 -49
- 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
377
|
import { MAX_L1_TX_LIMIT, WEI_CONST } from '@aztec/ethereum/l1-tx-utils';
|
|
378
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 { getLastL1SlotTimestampForL2Slot, 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,15 +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;
|
|
451
|
+
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */ feeAssetPriceOracle;
|
|
441
452
|
// A CALL to a cold address is 2700 gas
|
|
442
453
|
static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
443
454
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
@@ -456,9 +467,12 @@ export class SequencerPublisher {
|
|
|
456
467
|
this.slashingLog = createLogger('sequencer:publisher:slashing');
|
|
457
468
|
this.lastActions = {};
|
|
458
469
|
this.isPayloadEmptyCache = new Map();
|
|
470
|
+
this.payloadProposedCache = new Set();
|
|
459
471
|
this.requests = [];
|
|
460
472
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
461
473
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
474
|
+
this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
|
|
475
|
+
this.dateProvider = deps.dateProvider;
|
|
462
476
|
this.epochCache = deps.epochCache;
|
|
463
477
|
this.lastActions = deps.lastActions;
|
|
464
478
|
this.blobClient = deps.blobClient;
|
|
@@ -466,6 +480,7 @@ export class SequencerPublisher {
|
|
|
466
480
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
467
481
|
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
468
482
|
this.l1TxUtils = deps.l1TxUtils;
|
|
483
|
+
this.getNextPublisher = deps.getNextPublisher;
|
|
469
484
|
this.rollupContract = deps.rollupContract;
|
|
470
485
|
this.govProposerContract = deps.governanceProposerContract;
|
|
471
486
|
this.slashingProposerContract = deps.slashingProposerContract;
|
|
@@ -479,10 +494,36 @@ export class SequencerPublisher {
|
|
|
479
494
|
if (config.fishermanMode) {
|
|
480
495
|
this.l1FeeAnalyzer = new L1FeeAnalyzer(this.l1TxUtils.client, deps.dateProvider, createLogger('sequencer:publisher:fee-analyzer'));
|
|
481
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
|
+
});
|
|
482
517
|
}
|
|
483
518
|
getRollupContract() {
|
|
484
519
|
return this.rollupContract;
|
|
485
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
|
+
}
|
|
486
527
|
getSenderAddress() {
|
|
487
528
|
return this.l1TxUtils.getSenderAddress();
|
|
488
529
|
}
|
|
@@ -501,7 +542,7 @@ export class SequencerPublisher {
|
|
|
501
542
|
this.requests.push(request);
|
|
502
543
|
}
|
|
503
544
|
getCurrentL2Slot() {
|
|
504
|
-
return this.epochCache.
|
|
545
|
+
return this.epochCache.getSlotNow();
|
|
505
546
|
}
|
|
506
547
|
/**
|
|
507
548
|
* Clears all pending requests without sending them.
|
|
@@ -590,8 +631,8 @@ export class SequencerPublisher {
|
|
|
590
631
|
// @note - we can only have one blob config per bundle
|
|
591
632
|
// find requests with gas and blob configs
|
|
592
633
|
// See https://github.com/AztecProtocol/aztec-packages/issues/11513
|
|
593
|
-
const gasConfigs =
|
|
594
|
-
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);
|
|
595
636
|
if (blobConfigs.length > 1) {
|
|
596
637
|
throw new Error('Multiple blob configs found');
|
|
597
638
|
}
|
|
@@ -618,12 +659,34 @@ export class SequencerPublisher {
|
|
|
618
659
|
// This ensures the committee gets precomputed correctly
|
|
619
660
|
validRequests.sort((a, b)=>compareActions(a.action, b.action));
|
|
620
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
|
+
};
|
|
621
681
|
this.log.debug('Forwarding transactions', {
|
|
622
682
|
validRequests: validRequests.map((request)=>request.action),
|
|
623
683
|
txConfig
|
|
624
684
|
});
|
|
625
|
-
const result = await
|
|
626
|
-
|
|
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);
|
|
627
690
|
return {
|
|
628
691
|
result,
|
|
629
692
|
expiredActions,
|
|
@@ -643,17 +706,83 @@ export class SequencerPublisher {
|
|
|
643
706
|
}
|
|
644
707
|
}
|
|
645
708
|
}
|
|
646
|
-
|
|
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) {
|
|
647
744
|
const actionsListStr = requests.map((r)=>r.action).join(', ');
|
|
648
745
|
if (result instanceof FormattedViemError) {
|
|
649
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
|
+
});
|
|
650
770
|
return {
|
|
651
771
|
failedActions: requests.map((r)=>r.action)
|
|
652
772
|
};
|
|
653
773
|
} else {
|
|
654
774
|
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
655
775
|
result,
|
|
656
|
-
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
|
+
}))
|
|
657
786
|
});
|
|
658
787
|
const successfulActions = [];
|
|
659
788
|
const failedActions = [];
|
|
@@ -664,6 +793,37 @@ export class SequencerPublisher {
|
|
|
664
793
|
failedActions.push(request.action);
|
|
665
794
|
}
|
|
666
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
|
+
}
|
|
667
827
|
return {
|
|
668
828
|
successfulActions,
|
|
669
829
|
failedActions
|
|
@@ -671,17 +831,20 @@ export class SequencerPublisher {
|
|
|
671
831
|
}
|
|
672
832
|
}
|
|
673
833
|
/**
|
|
674
|
-
* @notice Will call `
|
|
834
|
+
* @notice Will call `canProposeAt` to make sure that it is possible to propose
|
|
675
835
|
* @param tipArchive - The archive to check
|
|
676
836
|
* @returns The slot and block number if it is possible to propose, undefined otherwise
|
|
677
|
-
*/
|
|
837
|
+
*/ canProposeAt(tipArchive, msgSender, opts = {}) {
|
|
678
838
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
679
839
|
const ignoredErrors = [
|
|
680
840
|
'SlotAlreadyInChain',
|
|
681
841
|
'InvalidProposer',
|
|
682
842
|
'InvalidArchive'
|
|
683
843
|
];
|
|
684
|
-
|
|
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, {
|
|
685
848
|
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber
|
|
686
849
|
}).catch((err)=>{
|
|
687
850
|
if (err instanceof FormattedViemError && ignoredErrors.find((e)=>err.message.includes(e))) {
|
|
@@ -713,7 +876,7 @@ export class SequencerPublisher {
|
|
|
713
876
|
header.blobsHash.toString(),
|
|
714
877
|
flags
|
|
715
878
|
];
|
|
716
|
-
const ts =
|
|
879
|
+
const ts = this.getSimulationTimestamp(header.slotNumber);
|
|
717
880
|
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(opts?.forcePendingCheckpointNumber);
|
|
718
881
|
let balance = 0n;
|
|
719
882
|
if (this.config.fishermanMode) {
|
|
@@ -736,7 +899,7 @@ export class SequencerPublisher {
|
|
|
736
899
|
}),
|
|
737
900
|
from: MULTI_CALL_3_ADDRESS
|
|
738
901
|
}, {
|
|
739
|
-
time: ts
|
|
902
|
+
time: ts
|
|
740
903
|
}, stateOverrides);
|
|
741
904
|
this.log.debug(`Simulated validateHeader`);
|
|
742
905
|
}
|
|
@@ -766,6 +929,7 @@ export class SequencerPublisher {
|
|
|
766
929
|
...logData,
|
|
767
930
|
request
|
|
768
931
|
});
|
|
932
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
769
933
|
try {
|
|
770
934
|
const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, mergeAbis([
|
|
771
935
|
request.abi ?? [],
|
|
@@ -808,6 +972,27 @@ export class SequencerPublisher {
|
|
|
808
972
|
}
|
|
809
973
|
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
810
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
|
+
});
|
|
811
996
|
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, {
|
|
812
997
|
cause: viemError
|
|
813
998
|
});
|
|
@@ -834,30 +1019,15 @@ export class SequencerPublisher {
|
|
|
834
1019
|
}
|
|
835
1020
|
}
|
|
836
1021
|
/** Simulates `propose` to make sure that the checkpoint is valid for submission */ async validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, options) {
|
|
837
|
-
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
838
|
-
// TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
|
|
839
|
-
// If we have no attestations, we still need to provide the empty attestations
|
|
840
|
-
// so that the committee is recalculated correctly
|
|
841
|
-
// const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
842
|
-
// if (ignoreSignatures) {
|
|
843
|
-
// const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
|
|
844
|
-
// if (!committee) {
|
|
845
|
-
// this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
846
|
-
// throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
847
|
-
// }
|
|
848
|
-
// attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
849
|
-
// CommitteeAttestation.fromAddress(committeeMember),
|
|
850
|
-
// );
|
|
851
|
-
// }
|
|
852
1022
|
const blobFields = checkpoint.toBlobFields();
|
|
853
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
1023
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
854
1024
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
855
1025
|
const args = [
|
|
856
1026
|
{
|
|
857
1027
|
header: checkpoint.header.toViem(),
|
|
858
1028
|
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
859
1029
|
oracleInput: {
|
|
860
|
-
feeAssetPriceModifier:
|
|
1030
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier
|
|
861
1031
|
}
|
|
862
1032
|
},
|
|
863
1033
|
attestationsAndSigners.getPackedAttestations(),
|
|
@@ -865,10 +1035,9 @@ export class SequencerPublisher {
|
|
|
865
1035
|
attestationsAndSignersSignature.toViemSignature(),
|
|
866
1036
|
blobInput
|
|
867
1037
|
];
|
|
868
|
-
await this.simulateProposeTx(args,
|
|
869
|
-
return ts;
|
|
1038
|
+
await this.simulateProposeTx(args, options);
|
|
870
1039
|
}
|
|
871
|
-
async enqueueCastSignalHelper(slotNumber,
|
|
1040
|
+
async enqueueCastSignalHelper(slotNumber, signalType, payload, base, signerAddress, signer) {
|
|
872
1041
|
if (this.lastActions[signalType] && this.lastActions[signalType] === slotNumber) {
|
|
873
1042
|
this.log.debug(`Skipping duplicate vote cast signal ${signalType} for slot ${slotNumber}`);
|
|
874
1043
|
return false;
|
|
@@ -892,6 +1061,28 @@ export class SequencerPublisher {
|
|
|
892
1061
|
this.log.warn(`Skipping vote cast for payload with empty code`);
|
|
893
1062
|
return false;
|
|
894
1063
|
}
|
|
1064
|
+
// Check if payload was already submitted to governance
|
|
1065
|
+
const cacheKey = payload.toString();
|
|
1066
|
+
if (!this.payloadProposedCache.has(cacheKey)) {
|
|
1067
|
+
try {
|
|
1068
|
+
const l1StartBlock = await this.rollupContract.getL1StartBlock();
|
|
1069
|
+
const proposed = await retry(()=>base.hasPayloadBeenProposed(payload.toString(), l1StartBlock), 'Check if payload was proposed', makeBackoff([
|
|
1070
|
+
0,
|
|
1071
|
+
1,
|
|
1072
|
+
2
|
|
1073
|
+
]), this.log, true);
|
|
1074
|
+
if (proposed) {
|
|
1075
|
+
this.payloadProposedCache.add(cacheKey);
|
|
1076
|
+
}
|
|
1077
|
+
} catch (err) {
|
|
1078
|
+
this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
|
|
1079
|
+
return false;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
if (this.payloadProposedCache.has(cacheKey)) {
|
|
1083
|
+
this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
|
|
1084
|
+
return false;
|
|
1085
|
+
}
|
|
895
1086
|
const cachedLastVote = this.lastActions[signalType];
|
|
896
1087
|
this.lastActions[signalType] = slotNumber;
|
|
897
1088
|
const action = signalType;
|
|
@@ -902,6 +1093,8 @@ export class SequencerPublisher {
|
|
|
902
1093
|
signer: this.l1TxUtils.client.account?.address,
|
|
903
1094
|
lastValidL2Slot: slotNumber
|
|
904
1095
|
});
|
|
1096
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1097
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
905
1098
|
try {
|
|
906
1099
|
await this.l1TxUtils.simulate(request, {
|
|
907
1100
|
time: timestamp
|
|
@@ -913,7 +1106,32 @@ export class SequencerPublisher {
|
|
|
913
1106
|
request
|
|
914
1107
|
});
|
|
915
1108
|
} catch (err) {
|
|
916
|
-
|
|
1109
|
+
const viemError = formatViemError(err);
|
|
1110
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError, {
|
|
1111
|
+
simulationTimestamp: timestamp,
|
|
1112
|
+
l1BlockNumber
|
|
1113
|
+
});
|
|
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
|
+
});
|
|
917
1135
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
918
1136
|
}
|
|
919
1137
|
// TODO(palla/slash): All votes (governance and slashing) should txTimeoutAt at the end of the slot.
|
|
@@ -957,12 +1175,11 @@ export class SequencerPublisher {
|
|
|
957
1175
|
/**
|
|
958
1176
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
959
1177
|
* @param slotNumber - The slot number to cast a signal for.
|
|
960
|
-
* @param timestamp - The timestamp of the slot to cast a signal for.
|
|
961
1178
|
* @returns True if the signal was successfully enqueued, false otherwise.
|
|
962
|
-
*/ enqueueGovernanceCastSignal(governancePayload, slotNumber,
|
|
963
|
-
return this.enqueueCastSignalHelper(slotNumber,
|
|
1179
|
+
*/ enqueueGovernanceCastSignal(governancePayload, slotNumber, signerAddress, signer) {
|
|
1180
|
+
return this.enqueueCastSignalHelper(slotNumber, 'governance-signal', governancePayload, this.govProposerContract, signerAddress, signer);
|
|
964
1181
|
}
|
|
965
|
-
/** Enqueues all slashing actions as returned by the slasher client. */ async enqueueSlashingActions(actions, slotNumber,
|
|
1182
|
+
/** Enqueues all slashing actions as returned by the slasher client. */ async enqueueSlashingActions(actions, slotNumber, signerAddress, signer) {
|
|
966
1183
|
if (actions.length === 0) {
|
|
967
1184
|
this.log.debug(`No slashing actions to enqueue for slot ${slotNumber}`);
|
|
968
1185
|
return false;
|
|
@@ -978,7 +1195,7 @@ export class SequencerPublisher {
|
|
|
978
1195
|
this.log.debug(`Enqueuing slashing vote for payload ${action.payload} at slot ${slotNumber}`, {
|
|
979
1196
|
signerAddress
|
|
980
1197
|
});
|
|
981
|
-
await this.enqueueCastSignalHelper(slotNumber,
|
|
1198
|
+
await this.enqueueCastSignalHelper(slotNumber, 'empire-slashing-signal', action.payload, this.slashingProposerContract, signerAddress, signer);
|
|
982
1199
|
break;
|
|
983
1200
|
}
|
|
984
1201
|
case 'create-empire-payload':
|
|
@@ -988,7 +1205,7 @@ export class SequencerPublisher {
|
|
|
988
1205
|
signerAddress
|
|
989
1206
|
});
|
|
990
1207
|
const request = this.slashFactoryContract.buildCreatePayloadRequest(action.data);
|
|
991
|
-
await this.simulateAndEnqueueRequest('create-empire-payload', request, (receipt)=>!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs), slotNumber
|
|
1208
|
+
await this.simulateAndEnqueueRequest('create-empire-payload', request, (receipt)=>!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs), slotNumber);
|
|
992
1209
|
break;
|
|
993
1210
|
}
|
|
994
1211
|
case 'execute-empire-payload':
|
|
@@ -1003,7 +1220,7 @@ export class SequencerPublisher {
|
|
|
1003
1220
|
}
|
|
1004
1221
|
const empireSlashingProposer = this.slashingProposerContract;
|
|
1005
1222
|
const request = empireSlashingProposer.buildExecuteRoundRequest(action.round);
|
|
1006
|
-
await this.simulateAndEnqueueRequest('execute-empire-payload', request, (receipt)=>!!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs), slotNumber
|
|
1223
|
+
await this.simulateAndEnqueueRequest('execute-empire-payload', request, (receipt)=>!!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs), slotNumber);
|
|
1007
1224
|
break;
|
|
1008
1225
|
}
|
|
1009
1226
|
case 'vote-offenses':
|
|
@@ -1021,7 +1238,7 @@ export class SequencerPublisher {
|
|
|
1021
1238
|
const tallySlashingProposer = this.slashingProposerContract;
|
|
1022
1239
|
const votes = bufferToHex(encodeSlashConsensusVotes(action.votes));
|
|
1023
1240
|
const request = await tallySlashingProposer.buildVoteRequestFromSigner(votes, slotNumber, signer);
|
|
1024
|
-
await this.simulateAndEnqueueRequest('vote-offenses', request, (receipt)=>!!tallySlashingProposer.tryExtractVoteCastEvent(receipt.logs), slotNumber
|
|
1241
|
+
await this.simulateAndEnqueueRequest('vote-offenses', request, (receipt)=>!!tallySlashingProposer.tryExtractVoteCastEvent(receipt.logs), slotNumber);
|
|
1025
1242
|
break;
|
|
1026
1243
|
}
|
|
1027
1244
|
case 'execute-slash':
|
|
@@ -1037,7 +1254,7 @@ export class SequencerPublisher {
|
|
|
1037
1254
|
}
|
|
1038
1255
|
const tallySlashingProposer = this.slashingProposerContract;
|
|
1039
1256
|
const request = tallySlashingProposer.buildExecuteRoundRequest(action.round, action.committees);
|
|
1040
|
-
await this.simulateAndEnqueueRequest('execute-slash', request, (receipt)=>!!tallySlashingProposer.tryExtractRoundExecutedEvent(receipt.logs), slotNumber
|
|
1257
|
+
await this.simulateAndEnqueueRequest('execute-slash', request, (receipt)=>!!tallySlashingProposer.tryExtractRoundExecutedEvent(receipt.logs), slotNumber);
|
|
1041
1258
|
break;
|
|
1042
1259
|
}
|
|
1043
1260
|
default:
|
|
@@ -1052,22 +1269,22 @@ export class SequencerPublisher {
|
|
|
1052
1269
|
/** Simulates and enqueues a proposal for a checkpoint on L1 */ async enqueueProposeCheckpoint(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, opts = {}) {
|
|
1053
1270
|
const checkpointHeader = checkpoint.header;
|
|
1054
1271
|
const blobFields = checkpoint.toBlobFields();
|
|
1055
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
1272
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
1056
1273
|
const proposeTxArgs = {
|
|
1057
1274
|
header: checkpointHeader,
|
|
1058
1275
|
archive: checkpoint.archive.root.toBuffer(),
|
|
1059
1276
|
blobs,
|
|
1060
1277
|
attestationsAndSigners,
|
|
1061
|
-
attestationsAndSignersSignature
|
|
1278
|
+
attestationsAndSignersSignature,
|
|
1279
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier
|
|
1062
1280
|
};
|
|
1063
|
-
let ts;
|
|
1064
1281
|
try {
|
|
1065
1282
|
// @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
|
|
1066
1283
|
// This means that we can avoid the simulation issues in later checks.
|
|
1067
1284
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
1068
1285
|
// make time consistency checks break.
|
|
1069
1286
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
1070
|
-
|
|
1287
|
+
await this.validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, opts);
|
|
1071
1288
|
} catch (err) {
|
|
1072
1289
|
this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
1073
1290
|
...checkpoint.getStats(),
|
|
@@ -1080,7 +1297,7 @@ export class SequencerPublisher {
|
|
|
1080
1297
|
...checkpoint.toCheckpointInfo(),
|
|
1081
1298
|
...opts
|
|
1082
1299
|
});
|
|
1083
|
-
await this.addProposeTx(checkpoint, proposeTxArgs, opts
|
|
1300
|
+
await this.addProposeTx(checkpoint, proposeTxArgs, opts);
|
|
1084
1301
|
}
|
|
1085
1302
|
enqueueInvalidateCheckpoint(request, opts = {}) {
|
|
1086
1303
|
if (!request) {
|
|
@@ -1121,7 +1338,8 @@ export class SequencerPublisher {
|
|
|
1121
1338
|
}
|
|
1122
1339
|
});
|
|
1123
1340
|
}
|
|
1124
|
-
async simulateAndEnqueueRequest(action, request, checkSuccess, slotNumber
|
|
1341
|
+
async simulateAndEnqueueRequest(action, request, checkSuccess, slotNumber) {
|
|
1342
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
1125
1343
|
const logData = {
|
|
1126
1344
|
slotNumber,
|
|
1127
1345
|
timestamp,
|
|
@@ -1134,6 +1352,7 @@ export class SequencerPublisher {
|
|
|
1134
1352
|
const cachedLastActionSlot = this.lastActions[action];
|
|
1135
1353
|
this.lastActions[action] = slotNumber;
|
|
1136
1354
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
1355
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1137
1356
|
let gasUsed;
|
|
1138
1357
|
const simulateAbi = mergeAbis([
|
|
1139
1358
|
request.abi ?? [],
|
|
@@ -1142,7 +1361,7 @@ export class SequencerPublisher {
|
|
|
1142
1361
|
try {
|
|
1143
1362
|
({ gasUsed } = await this.l1TxUtils.simulate(request, {
|
|
1144
1363
|
time: timestamp
|
|
1145
|
-
}, [], simulateAbi));
|
|
1364
|
+
}, [], simulateAbi));
|
|
1146
1365
|
this.log.verbose(`Simulation for ${action} succeeded`, {
|
|
1147
1366
|
...logData,
|
|
1148
1367
|
request,
|
|
@@ -1151,6 +1370,27 @@ export class SequencerPublisher {
|
|
|
1151
1370
|
} catch (err) {
|
|
1152
1371
|
const viemError = formatViemError(err, simulateAbi);
|
|
1153
1372
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1373
|
+
this.backupFailedTx({
|
|
1374
|
+
id: keccak256(request.data),
|
|
1375
|
+
failureType: 'simulation',
|
|
1376
|
+
request: {
|
|
1377
|
+
to: request.to,
|
|
1378
|
+
data: request.data,
|
|
1379
|
+
value: request.value?.toString()
|
|
1380
|
+
},
|
|
1381
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1382
|
+
error: {
|
|
1383
|
+
message: viemError.message,
|
|
1384
|
+
name: viemError.name
|
|
1385
|
+
},
|
|
1386
|
+
context: {
|
|
1387
|
+
actions: [
|
|
1388
|
+
action
|
|
1389
|
+
],
|
|
1390
|
+
slot: slotNumber,
|
|
1391
|
+
sender: this.getSenderAddress().toString()
|
|
1392
|
+
}
|
|
1393
|
+
});
|
|
1154
1394
|
return false;
|
|
1155
1395
|
}
|
|
1156
1396
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
@@ -1202,7 +1442,7 @@ export class SequencerPublisher {
|
|
|
1202
1442
|
this.interrupted = false;
|
|
1203
1443
|
this.l1TxUtils.restart();
|
|
1204
1444
|
}
|
|
1205
|
-
async prepareProposeTx(encodedData,
|
|
1445
|
+
async prepareProposeTx(encodedData, options) {
|
|
1206
1446
|
const kzg = Blob.getViemKzgInstance();
|
|
1207
1447
|
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
1208
1448
|
this.log.debug('Validating blob input', {
|
|
@@ -1229,10 +1469,38 @@ export class SequencerPublisher {
|
|
|
1229
1469
|
}, {}, {
|
|
1230
1470
|
blobs: encodedData.blobs.map((b)=>b.data),
|
|
1231
1471
|
kzg
|
|
1232
|
-
}).catch((err)=>{
|
|
1233
|
-
const
|
|
1234
|
-
this.log.error(`Failed to validate blobs`, message, {
|
|
1235
|
-
metaMessages
|
|
1472
|
+
}).catch(async (err)=>{
|
|
1473
|
+
const viemError = formatViemError(err);
|
|
1474
|
+
this.log.error(`Failed to validate blobs`, viemError.message, {
|
|
1475
|
+
metaMessages: viemError.metaMessages
|
|
1476
|
+
});
|
|
1477
|
+
const validateBlobsData = encodeFunctionData({
|
|
1478
|
+
abi: RollupAbi,
|
|
1479
|
+
functionName: 'validateBlobs',
|
|
1480
|
+
args: [
|
|
1481
|
+
blobInput
|
|
1482
|
+
]
|
|
1483
|
+
});
|
|
1484
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1485
|
+
this.backupFailedTx({
|
|
1486
|
+
id: keccak256(validateBlobsData),
|
|
1487
|
+
failureType: 'simulation',
|
|
1488
|
+
request: {
|
|
1489
|
+
to: this.rollupContract.address,
|
|
1490
|
+
data: validateBlobsData
|
|
1491
|
+
},
|
|
1492
|
+
blobData: encodedData.blobs.map((b)=>toHex(b.data)),
|
|
1493
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1494
|
+
error: {
|
|
1495
|
+
message: viemError.message,
|
|
1496
|
+
name: viemError.name
|
|
1497
|
+
},
|
|
1498
|
+
context: {
|
|
1499
|
+
actions: [
|
|
1500
|
+
'validate-blobs'
|
|
1501
|
+
],
|
|
1502
|
+
sender: this.getSenderAddress().toString()
|
|
1503
|
+
}
|
|
1236
1504
|
});
|
|
1237
1505
|
throw new Error('Failed to validate blobs');
|
|
1238
1506
|
});
|
|
@@ -1243,8 +1511,7 @@ export class SequencerPublisher {
|
|
|
1243
1511
|
header: encodedData.header.toViem(),
|
|
1244
1512
|
archive: toHex(encodedData.archive),
|
|
1245
1513
|
oracleInput: {
|
|
1246
|
-
|
|
1247
|
-
feeAssetPriceModifier: 0n
|
|
1514
|
+
feeAssetPriceModifier: encodedData.feeAssetPriceModifier
|
|
1248
1515
|
}
|
|
1249
1516
|
},
|
|
1250
1517
|
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
@@ -1252,7 +1519,7 @@ export class SequencerPublisher {
|
|
|
1252
1519
|
encodedData.attestationsAndSignersSignature.toViemSignature(),
|
|
1253
1520
|
blobInput
|
|
1254
1521
|
];
|
|
1255
|
-
const { rollupData, simulationResult } = await this.simulateProposeTx(args,
|
|
1522
|
+
const { rollupData, simulationResult } = await this.simulateProposeTx(args, options);
|
|
1256
1523
|
return {
|
|
1257
1524
|
args,
|
|
1258
1525
|
blobEvaluationGas,
|
|
@@ -1263,9 +1530,8 @@ export class SequencerPublisher {
|
|
|
1263
1530
|
/**
|
|
1264
1531
|
* Simulates the propose tx with eth_simulateV1
|
|
1265
1532
|
* @param args - The propose tx args
|
|
1266
|
-
* @param timestamp - The timestamp to simulate proposal at
|
|
1267
1533
|
* @returns The simulation result
|
|
1268
|
-
*/ async simulateProposeTx(args,
|
|
1534
|
+
*/ async simulateProposeTx(args, options) {
|
|
1269
1535
|
const rollupData = encodeFunctionData({
|
|
1270
1536
|
abi: RollupAbi,
|
|
1271
1537
|
functionName: 'propose',
|
|
@@ -1293,6 +1559,8 @@ export class SequencerPublisher {
|
|
|
1293
1559
|
balance: 10n * WEI_CONST * WEI_CONST
|
|
1294
1560
|
});
|
|
1295
1561
|
}
|
|
1562
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1563
|
+
const simTs = this.getSimulationTimestamp(SlotNumber.fromBigInt(args[0].header.slotNumber));
|
|
1296
1564
|
const simulationResult = await this.l1TxUtils.simulate({
|
|
1297
1565
|
to: this.rollupContract.address,
|
|
1298
1566
|
data: rollupData,
|
|
@@ -1301,8 +1569,7 @@ export class SequencerPublisher {
|
|
|
1301
1569
|
from: this.proposerAddressForSimulation.toString()
|
|
1302
1570
|
}
|
|
1303
1571
|
}, {
|
|
1304
|
-
|
|
1305
|
-
time: timestamp + 1n,
|
|
1572
|
+
time: simTs,
|
|
1306
1573
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1307
1574
|
gasLimit: MAX_L1_TX_LIMIT * 2n
|
|
1308
1575
|
}, stateOverrides, RollupAbi, {
|
|
@@ -1319,7 +1586,29 @@ export class SequencerPublisher {
|
|
|
1319
1586
|
logs: []
|
|
1320
1587
|
};
|
|
1321
1588
|
}
|
|
1322
|
-
this.log.error(`Failed to simulate propose tx`, viemError
|
|
1589
|
+
this.log.error(`Failed to simulate propose tx`, viemError, {
|
|
1590
|
+
simulationTimestamp: simTs
|
|
1591
|
+
});
|
|
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
|
+
});
|
|
1323
1612
|
throw err;
|
|
1324
1613
|
});
|
|
1325
1614
|
return {
|
|
@@ -1327,11 +1616,11 @@ export class SequencerPublisher {
|
|
|
1327
1616
|
simulationResult
|
|
1328
1617
|
};
|
|
1329
1618
|
}
|
|
1330
|
-
async addProposeTx(checkpoint, encodedData, opts = {}
|
|
1619
|
+
async addProposeTx(checkpoint, encodedData, opts = {}) {
|
|
1331
1620
|
const slot = checkpoint.header.slotNumber;
|
|
1332
1621
|
const timer = new Timer();
|
|
1333
1622
|
const kzg = Blob.getViemKzgInstance();
|
|
1334
|
-
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData,
|
|
1623
|
+
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, opts);
|
|
1335
1624
|
const startBlock = await this.l1TxUtils.getBlockNumber();
|
|
1336
1625
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil(Number(simulationResult.gasUsed) * 64 / 63)) + blobEvaluationGas + SequencerPublisher.MULTICALL_OVERHEAD_GAS_GUESS);
|
|
1337
1626
|
// Send the blobs to the blob client preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
|
|
@@ -1396,4 +1685,13 @@ export class SequencerPublisher {
|
|
|
1396
1685
|
}
|
|
1397
1686
|
});
|
|
1398
1687
|
}
|
|
1688
|
+
/** Returns the timestamp of the last L1 slot within a given L2 slot. Used as the simulation timestamp
|
|
1689
|
+
* for eth_simulateV1 calls, since it's guaranteed to be greater than any L1 block produced during the slot. */ getSimulationTimestamp(slot) {
|
|
1690
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1691
|
+
return getLastL1SlotTimestampForL2Slot(slot, l1Constants);
|
|
1692
|
+
}
|
|
1693
|
+
/** Returns the timestamp of the next L1 slot boundary after now. */ getNextL1SlotTimestamp() {
|
|
1694
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1695
|
+
return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
|
|
1696
|
+
}
|
|
1399
1697
|
}
|