@aztec/sequencer-client 0.0.1-commit.e61ad554 → 0.0.1-commit.ec5f612
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 +12 -7
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +15 -4
- package/dest/config.d.ts +3 -4
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +17 -12
- package/dest/global_variable_builder/global_builder.d.ts +2 -4
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/publisher/config.d.ts +35 -17
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +106 -42
- package/dest/publisher/index.d.ts +2 -1
- package/dest/publisher/index.d.ts.map +1 -1
- package/dest/publisher/l1_tx_failed_store/factory.d.ts +11 -0
- package/dest/publisher/l1_tx_failed_store/factory.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/factory.js +22 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +59 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +1 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +15 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +34 -0
- package/dest/publisher/l1_tx_failed_store/index.d.ts +4 -0
- package/dest/publisher/l1_tx_failed_store/index.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/index.js +2 -0
- package/dest/publisher/sequencer-publisher-factory.d.ts +11 -3
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +13 -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 +22 -8
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +297 -47
- package/dest/sequencer/checkpoint_proposal_job.d.ts +32 -9
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +113 -59
- package/dest/sequencer/metrics.d.ts +17 -5
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +111 -30
- package/dest/sequencer/sequencer.d.ts +17 -7
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +30 -27
- package/dest/sequencer/timetable.d.ts +1 -4
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +2 -5
- 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 +7 -5
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +6 -6
- 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 +28 -28
- package/src/client/sequencer-client.ts +25 -7
- package/src/config.ts +26 -19
- package/src/global_variable_builder/global_builder.ts +1 -1
- package/src/publisher/config.ts +121 -43
- package/src/publisher/index.ts +3 -0
- package/src/publisher/l1_tx_failed_store/factory.ts +32 -0
- package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +55 -0
- package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +46 -0
- package/src/publisher/l1_tx_failed_store/index.ts +3 -0
- package/src/publisher/sequencer-publisher-factory.ts +23 -6
- package/src/publisher/sequencer-publisher-metrics.ts +7 -3
- package/src/publisher/sequencer-publisher.ts +274 -53
- package/src/sequencer/checkpoint_proposal_job.ts +157 -76
- package/src/sequencer/metrics.ts +124 -32
- package/src/sequencer/sequencer.ts +40 -32
- package/src/sequencer/timetable.ts +7 -6
- package/src/test/index.ts +2 -4
- package/src/test/mock_checkpoint_builder.ts +14 -5
- package/src/test/utils.ts +5 -2
|
@@ -372,10 +372,10 @@ 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';
|
|
@@ -383,13 +383,15 @@ import { pick } from '@aztec/foundation/collection';
|
|
|
383
383
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
384
384
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
385
385
|
import { createLogger } from '@aztec/foundation/log';
|
|
386
|
+
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
386
387
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
387
388
|
import { Timer } from '@aztec/foundation/timer';
|
|
388
389
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
389
390
|
import { encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
390
391
|
import { CommitteeAttestationsAndSigners } from '@aztec/stdlib/block';
|
|
391
392
|
import { getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
392
|
-
import { encodeFunctionData, toHex } from 'viem';
|
|
393
|
+
import { encodeFunctionData, keccak256, multicall3Abi, toHex } from 'viem';
|
|
394
|
+
import { createL1TxFailedStore } from './l1_tx_failed_store/index.js';
|
|
393
395
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
394
396
|
export const Actions = [
|
|
395
397
|
'invalidate-by-invalid-attestation',
|
|
@@ -429,19 +431,18 @@ export class SequencerPublisher {
|
|
|
429
431
|
interrupted;
|
|
430
432
|
metrics;
|
|
431
433
|
epochCache;
|
|
434
|
+
failedTxStore;
|
|
432
435
|
governanceLog;
|
|
433
436
|
slashingLog;
|
|
434
437
|
lastActions;
|
|
435
438
|
isPayloadEmptyCache;
|
|
439
|
+
payloadProposedCache;
|
|
436
440
|
log;
|
|
437
441
|
ethereumSlotDuration;
|
|
438
442
|
blobClient;
|
|
439
443
|
/** Address to use for simulations in fisherman mode (actual proposer's address) */ proposerAddressForSimulation;
|
|
440
444
|
/** 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;
|
|
445
|
+
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */ feeAssetPriceOracle;
|
|
445
446
|
// A CALL to a cold address is 2700 gas
|
|
446
447
|
static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
447
448
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
@@ -460,6 +461,7 @@ export class SequencerPublisher {
|
|
|
460
461
|
this.slashingLog = createLogger('sequencer:publisher:slashing');
|
|
461
462
|
this.lastActions = {};
|
|
462
463
|
this.isPayloadEmptyCache = new Map();
|
|
464
|
+
this.payloadProposedCache = new Set();
|
|
463
465
|
this.requests = [];
|
|
464
466
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
465
467
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
@@ -483,10 +485,36 @@ export class SequencerPublisher {
|
|
|
483
485
|
if (config.fishermanMode) {
|
|
484
486
|
this.l1FeeAnalyzer = new L1FeeAnalyzer(this.l1TxUtils.client, deps.dateProvider, createLogger('sequencer:publisher:fee-analyzer'));
|
|
485
487
|
}
|
|
488
|
+
// Initialize fee asset price oracle
|
|
489
|
+
this.feeAssetPriceOracle = new FeeAssetPriceOracle(this.l1TxUtils.client, this.rollupContract, createLogger('sequencer:publisher:price-oracle'));
|
|
490
|
+
// Initialize failed L1 tx store (optional, for test networks)
|
|
491
|
+
this.failedTxStore = createL1TxFailedStore(config.l1TxFailedStore, this.log);
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Backs up a failed L1 transaction to the configured store for debugging.
|
|
495
|
+
* Does nothing if no store is configured.
|
|
496
|
+
*/ backupFailedTx(failedTx) {
|
|
497
|
+
if (!this.failedTxStore) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
const tx = {
|
|
501
|
+
...failedTx,
|
|
502
|
+
timestamp: Date.now()
|
|
503
|
+
};
|
|
504
|
+
// Fire and forget - don't block on backup
|
|
505
|
+
void this.failedTxStore.then((store)=>store?.saveFailedTx(tx)).catch((err)=>{
|
|
506
|
+
this.log.warn(`Failed to backup failed L1 tx to store`, err);
|
|
507
|
+
});
|
|
486
508
|
}
|
|
487
509
|
getRollupContract() {
|
|
488
510
|
return this.rollupContract;
|
|
489
511
|
}
|
|
512
|
+
/**
|
|
513
|
+
* Gets the fee asset price modifier from the oracle.
|
|
514
|
+
* Returns 0n if the oracle query fails.
|
|
515
|
+
*/ getFeeAssetPriceModifier() {
|
|
516
|
+
return this.feeAssetPriceOracle.computePriceModifier();
|
|
517
|
+
}
|
|
490
518
|
getSenderAddress() {
|
|
491
519
|
return this.l1TxUtils.getSenderAddress();
|
|
492
520
|
}
|
|
@@ -544,7 +572,7 @@ export class SequencerPublisher {
|
|
|
544
572
|
// Get the transaction requests
|
|
545
573
|
const l1Requests = requestsToAnalyze.map((r)=>r.request);
|
|
546
574
|
// Start the analysis
|
|
547
|
-
const analysisId = await this.l1FeeAnalyzer.startAnalysis(l2SlotNumber, gasLimit > 0n ? gasLimit :
|
|
575
|
+
const analysisId = await this.l1FeeAnalyzer.startAnalysis(l2SlotNumber, gasLimit > 0n ? gasLimit : MAX_L1_TX_LIMIT, l1Requests, blobConfig, onComplete);
|
|
548
576
|
this.log.info('Started L1 fee analysis', {
|
|
549
577
|
analysisId,
|
|
550
578
|
l2SlotNumber: l2SlotNumber.toString(),
|
|
@@ -602,7 +630,16 @@ export class SequencerPublisher {
|
|
|
602
630
|
const blobConfig = blobConfigs[0];
|
|
603
631
|
// Merge gasConfigs. Yields the sum of gasLimits, and the earliest txTimeoutAt, or undefined if no gasConfig sets them.
|
|
604
632
|
const gasLimits = gasConfigs.map((g)=>g?.gasLimit).filter((g)=>g !== undefined);
|
|
605
|
-
|
|
633
|
+
let gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
|
|
634
|
+
// Cap at L1 block gas limit so the node accepts the tx ("gas limit too high" otherwise).
|
|
635
|
+
const maxGas = MAX_L1_TX_LIMIT;
|
|
636
|
+
if (gasLimit !== undefined && gasLimit > maxGas) {
|
|
637
|
+
this.log.debug('Capping bundled tx gas limit to L1 max', {
|
|
638
|
+
requested: gasLimit,
|
|
639
|
+
capped: maxGas
|
|
640
|
+
});
|
|
641
|
+
gasLimit = maxGas;
|
|
642
|
+
}
|
|
606
643
|
const txTimeoutAts = gasConfigs.map((g)=>g?.txTimeoutAt).filter((g)=>g !== undefined);
|
|
607
644
|
const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map((g)=>g.getTime()))) : undefined; // earliest
|
|
608
645
|
const txConfig = {
|
|
@@ -613,12 +650,31 @@ export class SequencerPublisher {
|
|
|
613
650
|
// This ensures the committee gets precomputed correctly
|
|
614
651
|
validRequests.sort((a, b)=>compareActions(a.action, b.action));
|
|
615
652
|
try {
|
|
653
|
+
// Capture context for failed tx backup before sending
|
|
654
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
655
|
+
const multicallData = encodeFunctionData({
|
|
656
|
+
abi: multicall3Abi,
|
|
657
|
+
functionName: 'aggregate3',
|
|
658
|
+
args: [
|
|
659
|
+
validRequests.map((r)=>({
|
|
660
|
+
target: r.request.to,
|
|
661
|
+
callData: r.request.data,
|
|
662
|
+
allowFailure: true
|
|
663
|
+
}))
|
|
664
|
+
]
|
|
665
|
+
});
|
|
666
|
+
const blobDataHex = blobConfig?.blobs?.map((b)=>toHex(b));
|
|
616
667
|
this.log.debug('Forwarding transactions', {
|
|
617
668
|
validRequests: validRequests.map((request)=>request.action),
|
|
618
669
|
txConfig
|
|
619
670
|
});
|
|
620
671
|
const result = await Multicall3.forward(validRequests.map((request)=>request.request), this.l1TxUtils, txConfig, blobConfig, this.rollupContract.address, this.log);
|
|
621
|
-
const
|
|
672
|
+
const txContext = {
|
|
673
|
+
multicallData,
|
|
674
|
+
blobData: blobDataHex,
|
|
675
|
+
l1BlockNumber
|
|
676
|
+
};
|
|
677
|
+
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(validRequests, result, txContext);
|
|
622
678
|
return {
|
|
623
679
|
result,
|
|
624
680
|
expiredActions,
|
|
@@ -638,10 +694,33 @@ export class SequencerPublisher {
|
|
|
638
694
|
}
|
|
639
695
|
}
|
|
640
696
|
}
|
|
641
|
-
callbackBundledTransactions(requests, result) {
|
|
697
|
+
callbackBundledTransactions(requests, result, txContext) {
|
|
642
698
|
const actionsListStr = requests.map((r)=>r.action).join(', ');
|
|
643
699
|
if (result instanceof FormattedViemError) {
|
|
644
700
|
this.log.error(`Failed to publish bundled transactions (${actionsListStr})`, result);
|
|
701
|
+
this.backupFailedTx({
|
|
702
|
+
id: keccak256(txContext.multicallData),
|
|
703
|
+
failureType: 'send-error',
|
|
704
|
+
request: {
|
|
705
|
+
to: MULTI_CALL_3_ADDRESS,
|
|
706
|
+
data: txContext.multicallData
|
|
707
|
+
},
|
|
708
|
+
blobData: txContext.blobData,
|
|
709
|
+
l1BlockNumber: txContext.l1BlockNumber.toString(),
|
|
710
|
+
error: {
|
|
711
|
+
message: result.message,
|
|
712
|
+
name: result.name
|
|
713
|
+
},
|
|
714
|
+
context: {
|
|
715
|
+
actions: requests.map((r)=>r.action),
|
|
716
|
+
requests: requests.map((r)=>({
|
|
717
|
+
action: r.action,
|
|
718
|
+
to: r.request.to,
|
|
719
|
+
data: r.request.data
|
|
720
|
+
})),
|
|
721
|
+
sender: this.getSenderAddress().toString()
|
|
722
|
+
}
|
|
723
|
+
});
|
|
645
724
|
return {
|
|
646
725
|
failedActions: requests.map((r)=>r.action)
|
|
647
726
|
};
|
|
@@ -659,6 +738,37 @@ export class SequencerPublisher {
|
|
|
659
738
|
failedActions.push(request.action);
|
|
660
739
|
}
|
|
661
740
|
}
|
|
741
|
+
// Single backup for the whole reverted tx
|
|
742
|
+
if (failedActions.length > 0 && result?.receipt?.status === 'reverted') {
|
|
743
|
+
this.backupFailedTx({
|
|
744
|
+
id: result.receipt.transactionHash,
|
|
745
|
+
failureType: 'revert',
|
|
746
|
+
request: {
|
|
747
|
+
to: MULTI_CALL_3_ADDRESS,
|
|
748
|
+
data: txContext.multicallData
|
|
749
|
+
},
|
|
750
|
+
blobData: txContext.blobData,
|
|
751
|
+
l1BlockNumber: result.receipt.blockNumber.toString(),
|
|
752
|
+
receipt: {
|
|
753
|
+
transactionHash: result.receipt.transactionHash,
|
|
754
|
+
blockNumber: result.receipt.blockNumber.toString(),
|
|
755
|
+
gasUsed: (result.receipt.gasUsed ?? 0n).toString(),
|
|
756
|
+
status: 'reverted'
|
|
757
|
+
},
|
|
758
|
+
error: {
|
|
759
|
+
message: result.errorMsg ?? 'Transaction reverted'
|
|
760
|
+
},
|
|
761
|
+
context: {
|
|
762
|
+
actions: failedActions,
|
|
763
|
+
requests: requests.filter((r)=>failedActions.includes(r.action)).map((r)=>({
|
|
764
|
+
action: r.action,
|
|
765
|
+
to: r.request.to,
|
|
766
|
+
data: r.request.data
|
|
767
|
+
})),
|
|
768
|
+
sender: this.getSenderAddress().toString()
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
}
|
|
662
772
|
return {
|
|
663
773
|
successfulActions,
|
|
664
774
|
failedActions
|
|
@@ -761,8 +871,12 @@ export class SequencerPublisher {
|
|
|
761
871
|
...logData,
|
|
762
872
|
request
|
|
763
873
|
});
|
|
874
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
764
875
|
try {
|
|
765
|
-
const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined,
|
|
876
|
+
const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, mergeAbis([
|
|
877
|
+
request.abi ?? [],
|
|
878
|
+
ErrorsAbi
|
|
879
|
+
]));
|
|
766
880
|
this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} succeeded`, {
|
|
767
881
|
...logData,
|
|
768
882
|
request,
|
|
@@ -779,7 +893,7 @@ export class SequencerPublisher {
|
|
|
779
893
|
const viemError = formatViemError(err);
|
|
780
894
|
// If the error is due to the checkpoint not being in the pending chain, and it was indeed removed by someone else,
|
|
781
895
|
// we can safely ignore it and return undefined so we go ahead with checkpoint building.
|
|
782
|
-
if (viemError.message?.includes('
|
|
896
|
+
if (viemError.message?.includes('Rollup__CheckpointNotInPendingChain')) {
|
|
783
897
|
this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`, {
|
|
784
898
|
...logData,
|
|
785
899
|
request,
|
|
@@ -800,6 +914,27 @@ export class SequencerPublisher {
|
|
|
800
914
|
}
|
|
801
915
|
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
802
916
|
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
|
|
917
|
+
this.backupFailedTx({
|
|
918
|
+
id: keccak256(request.data),
|
|
919
|
+
failureType: 'simulation',
|
|
920
|
+
request: {
|
|
921
|
+
to: request.to,
|
|
922
|
+
data: request.data,
|
|
923
|
+
value: request.value?.toString()
|
|
924
|
+
},
|
|
925
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
926
|
+
error: {
|
|
927
|
+
message: viemError.message,
|
|
928
|
+
name: viemError.name
|
|
929
|
+
},
|
|
930
|
+
context: {
|
|
931
|
+
actions: [
|
|
932
|
+
`invalidate-${reason}`
|
|
933
|
+
],
|
|
934
|
+
checkpointNumber,
|
|
935
|
+
sender: this.getSenderAddress().toString()
|
|
936
|
+
}
|
|
937
|
+
});
|
|
803
938
|
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, {
|
|
804
939
|
cause: viemError
|
|
805
940
|
});
|
|
@@ -827,29 +962,15 @@ export class SequencerPublisher {
|
|
|
827
962
|
}
|
|
828
963
|
/** Simulates `propose` to make sure that the checkpoint is valid for submission */ async validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, options) {
|
|
829
964
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
830
|
-
// TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
|
|
831
|
-
// If we have no attestations, we still need to provide the empty attestations
|
|
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
|
-
// }
|
|
844
965
|
const blobFields = checkpoint.toBlobFields();
|
|
845
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
966
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
846
967
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
847
968
|
const args = [
|
|
848
969
|
{
|
|
849
970
|
header: checkpoint.header.toViem(),
|
|
850
971
|
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
851
972
|
oracleInput: {
|
|
852
|
-
feeAssetPriceModifier:
|
|
973
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier
|
|
853
974
|
}
|
|
854
975
|
},
|
|
855
976
|
attestationsAndSigners.getPackedAttestations(),
|
|
@@ -884,6 +1005,28 @@ export class SequencerPublisher {
|
|
|
884
1005
|
this.log.warn(`Skipping vote cast for payload with empty code`);
|
|
885
1006
|
return false;
|
|
886
1007
|
}
|
|
1008
|
+
// Check if payload was already submitted to governance
|
|
1009
|
+
const cacheKey = payload.toString();
|
|
1010
|
+
if (!this.payloadProposedCache.has(cacheKey)) {
|
|
1011
|
+
try {
|
|
1012
|
+
const l1StartBlock = await this.rollupContract.getL1StartBlock();
|
|
1013
|
+
const proposed = await retry(()=>base.hasPayloadBeenProposed(payload.toString(), l1StartBlock), 'Check if payload was proposed', makeBackoff([
|
|
1014
|
+
0,
|
|
1015
|
+
1,
|
|
1016
|
+
2
|
|
1017
|
+
]), this.log, true);
|
|
1018
|
+
if (proposed) {
|
|
1019
|
+
this.payloadProposedCache.add(cacheKey);
|
|
1020
|
+
}
|
|
1021
|
+
} catch (err) {
|
|
1022
|
+
this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
|
|
1023
|
+
return false;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
if (this.payloadProposedCache.has(cacheKey)) {
|
|
1027
|
+
this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
|
|
1028
|
+
return false;
|
|
1029
|
+
}
|
|
887
1030
|
const cachedLastVote = this.lastActions[signalType];
|
|
888
1031
|
this.lastActions[signalType] = slotNumber;
|
|
889
1032
|
const action = signalType;
|
|
@@ -894,15 +1037,41 @@ export class SequencerPublisher {
|
|
|
894
1037
|
signer: this.l1TxUtils.client.account?.address,
|
|
895
1038
|
lastValidL2Slot: slotNumber
|
|
896
1039
|
});
|
|
1040
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
897
1041
|
try {
|
|
898
1042
|
await this.l1TxUtils.simulate(request, {
|
|
899
1043
|
time: timestamp
|
|
900
|
-
}, [],
|
|
1044
|
+
}, [], mergeAbis([
|
|
1045
|
+
request.abi ?? [],
|
|
1046
|
+
ErrorsAbi
|
|
1047
|
+
]));
|
|
901
1048
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, {
|
|
902
1049
|
request
|
|
903
1050
|
});
|
|
904
1051
|
} catch (err) {
|
|
905
|
-
|
|
1052
|
+
const viemError = formatViemError(err);
|
|
1053
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError);
|
|
1054
|
+
this.backupFailedTx({
|
|
1055
|
+
id: keccak256(request.data),
|
|
1056
|
+
failureType: 'simulation',
|
|
1057
|
+
request: {
|
|
1058
|
+
to: request.to,
|
|
1059
|
+
data: request.data,
|
|
1060
|
+
value: request.value?.toString()
|
|
1061
|
+
},
|
|
1062
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1063
|
+
error: {
|
|
1064
|
+
message: viemError.message,
|
|
1065
|
+
name: viemError.name
|
|
1066
|
+
},
|
|
1067
|
+
context: {
|
|
1068
|
+
actions: [
|
|
1069
|
+
action
|
|
1070
|
+
],
|
|
1071
|
+
slot: slotNumber,
|
|
1072
|
+
sender: this.getSenderAddress().toString()
|
|
1073
|
+
}
|
|
1074
|
+
});
|
|
906
1075
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
907
1076
|
}
|
|
908
1077
|
// TODO(palla/slash): All votes (governance and slashing) should txTimeoutAt at the end of the slot.
|
|
@@ -1041,13 +1210,14 @@ export class SequencerPublisher {
|
|
|
1041
1210
|
/** Simulates and enqueues a proposal for a checkpoint on L1 */ async enqueueProposeCheckpoint(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, opts = {}) {
|
|
1042
1211
|
const checkpointHeader = checkpoint.header;
|
|
1043
1212
|
const blobFields = checkpoint.toBlobFields();
|
|
1044
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
1213
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
1045
1214
|
const proposeTxArgs = {
|
|
1046
1215
|
header: checkpointHeader,
|
|
1047
1216
|
archive: checkpoint.archive.root.toBuffer(),
|
|
1048
1217
|
blobs,
|
|
1049
1218
|
attestationsAndSigners,
|
|
1050
|
-
attestationsAndSignersSignature
|
|
1219
|
+
attestationsAndSignersSignature,
|
|
1220
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier
|
|
1051
1221
|
};
|
|
1052
1222
|
let ts;
|
|
1053
1223
|
try {
|
|
@@ -1123,28 +1293,60 @@ export class SequencerPublisher {
|
|
|
1123
1293
|
const cachedLastActionSlot = this.lastActions[action];
|
|
1124
1294
|
this.lastActions[action] = slotNumber;
|
|
1125
1295
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
1296
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1126
1297
|
let gasUsed;
|
|
1298
|
+
const simulateAbi = mergeAbis([
|
|
1299
|
+
request.abi ?? [],
|
|
1300
|
+
ErrorsAbi
|
|
1301
|
+
]);
|
|
1127
1302
|
try {
|
|
1128
1303
|
({ gasUsed } = await this.l1TxUtils.simulate(request, {
|
|
1129
1304
|
time: timestamp
|
|
1130
|
-
}, [],
|
|
1305
|
+
}, [], simulateAbi)); // TODO(palla/slash): Check the timestamp logic
|
|
1131
1306
|
this.log.verbose(`Simulation for ${action} succeeded`, {
|
|
1132
1307
|
...logData,
|
|
1133
1308
|
request,
|
|
1134
1309
|
gasUsed
|
|
1135
1310
|
});
|
|
1136
1311
|
} catch (err) {
|
|
1137
|
-
const viemError = formatViemError(err);
|
|
1312
|
+
const viemError = formatViemError(err, simulateAbi);
|
|
1138
1313
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1314
|
+
this.backupFailedTx({
|
|
1315
|
+
id: keccak256(request.data),
|
|
1316
|
+
failureType: 'simulation',
|
|
1317
|
+
request: {
|
|
1318
|
+
to: request.to,
|
|
1319
|
+
data: request.data,
|
|
1320
|
+
value: request.value?.toString()
|
|
1321
|
+
},
|
|
1322
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1323
|
+
error: {
|
|
1324
|
+
message: viemError.message,
|
|
1325
|
+
name: viemError.name
|
|
1326
|
+
},
|
|
1327
|
+
context: {
|
|
1328
|
+
actions: [
|
|
1329
|
+
action
|
|
1330
|
+
],
|
|
1331
|
+
slot: slotNumber,
|
|
1332
|
+
sender: this.getSenderAddress().toString()
|
|
1333
|
+
}
|
|
1334
|
+
});
|
|
1139
1335
|
return false;
|
|
1140
1336
|
}
|
|
1141
1337
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
1142
1338
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil(Number(gasUsed) * 64 / 63)));
|
|
1143
1339
|
logData.gasLimit = gasLimit;
|
|
1340
|
+
// Store the ABI used for simulation on the request so Multicall3.forward can decode errors
|
|
1341
|
+
// when the tx is sent and a revert is diagnosed via simulation.
|
|
1342
|
+
const requestWithAbi = {
|
|
1343
|
+
...request,
|
|
1344
|
+
abi: simulateAbi
|
|
1345
|
+
};
|
|
1144
1346
|
this.log.debug(`Enqueuing ${action}`, logData);
|
|
1145
1347
|
this.addRequest({
|
|
1146
1348
|
action,
|
|
1147
|
-
request,
|
|
1349
|
+
request: requestWithAbi,
|
|
1148
1350
|
gasConfig: {
|
|
1149
1351
|
gasLimit
|
|
1150
1352
|
},
|
|
@@ -1208,10 +1410,38 @@ export class SequencerPublisher {
|
|
|
1208
1410
|
}, {}, {
|
|
1209
1411
|
blobs: encodedData.blobs.map((b)=>b.data),
|
|
1210
1412
|
kzg
|
|
1211
|
-
}).catch((err)=>{
|
|
1212
|
-
const
|
|
1213
|
-
this.log.error(`Failed to validate blobs`, message, {
|
|
1214
|
-
metaMessages
|
|
1413
|
+
}).catch(async (err)=>{
|
|
1414
|
+
const viemError = formatViemError(err);
|
|
1415
|
+
this.log.error(`Failed to validate blobs`, viemError.message, {
|
|
1416
|
+
metaMessages: viemError.metaMessages
|
|
1417
|
+
});
|
|
1418
|
+
const validateBlobsData = encodeFunctionData({
|
|
1419
|
+
abi: RollupAbi,
|
|
1420
|
+
functionName: 'validateBlobs',
|
|
1421
|
+
args: [
|
|
1422
|
+
blobInput
|
|
1423
|
+
]
|
|
1424
|
+
});
|
|
1425
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1426
|
+
this.backupFailedTx({
|
|
1427
|
+
id: keccak256(validateBlobsData),
|
|
1428
|
+
failureType: 'simulation',
|
|
1429
|
+
request: {
|
|
1430
|
+
to: this.rollupContract.address,
|
|
1431
|
+
data: validateBlobsData
|
|
1432
|
+
},
|
|
1433
|
+
blobData: encodedData.blobs.map((b)=>toHex(b.data)),
|
|
1434
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1435
|
+
error: {
|
|
1436
|
+
message: viemError.message,
|
|
1437
|
+
name: viemError.name
|
|
1438
|
+
},
|
|
1439
|
+
context: {
|
|
1440
|
+
actions: [
|
|
1441
|
+
'validate-blobs'
|
|
1442
|
+
],
|
|
1443
|
+
sender: this.getSenderAddress().toString()
|
|
1444
|
+
}
|
|
1215
1445
|
});
|
|
1216
1446
|
throw new Error('Failed to validate blobs');
|
|
1217
1447
|
});
|
|
@@ -1222,8 +1452,7 @@ export class SequencerPublisher {
|
|
|
1222
1452
|
header: encodedData.header.toViem(),
|
|
1223
1453
|
archive: toHex(encodedData.archive),
|
|
1224
1454
|
oracleInput: {
|
|
1225
|
-
|
|
1226
|
-
feeAssetPriceModifier: 0n
|
|
1455
|
+
feeAssetPriceModifier: encodedData.feeAssetPriceModifier
|
|
1227
1456
|
}
|
|
1228
1457
|
},
|
|
1229
1458
|
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
@@ -1272,10 +1501,11 @@ export class SequencerPublisher {
|
|
|
1272
1501
|
balance: 10n * WEI_CONST * WEI_CONST
|
|
1273
1502
|
});
|
|
1274
1503
|
}
|
|
1504
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1275
1505
|
const simulationResult = await this.l1TxUtils.simulate({
|
|
1276
1506
|
to: this.rollupContract.address,
|
|
1277
1507
|
data: rollupData,
|
|
1278
|
-
gas:
|
|
1508
|
+
gas: MAX_L1_TX_LIMIT,
|
|
1279
1509
|
...this.proposerAddressForSimulation && {
|
|
1280
1510
|
from: this.proposerAddressForSimulation.toString()
|
|
1281
1511
|
}
|
|
@@ -1283,10 +1513,10 @@ export class SequencerPublisher {
|
|
|
1283
1513
|
// @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
|
|
1284
1514
|
time: timestamp + 1n,
|
|
1285
1515
|
// @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:
|
|
1516
|
+
gasLimit: MAX_L1_TX_LIMIT * 2n
|
|
1287
1517
|
}, stateOverrides, RollupAbi, {
|
|
1288
1518
|
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
1289
|
-
fallbackGasEstimate:
|
|
1519
|
+
fallbackGasEstimate: MAX_L1_TX_LIMIT
|
|
1290
1520
|
}).catch((err)=>{
|
|
1291
1521
|
// In fisherman mode, we expect ValidatorSelection__MissingProposerSignature since fisherman doesn't have proposer signature
|
|
1292
1522
|
const viemError = formatViemError(err);
|
|
@@ -1294,11 +1524,31 @@ export class SequencerPublisher {
|
|
|
1294
1524
|
this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
|
|
1295
1525
|
// Return a minimal simulation result with the fallback gas estimate
|
|
1296
1526
|
return {
|
|
1297
|
-
gasUsed:
|
|
1527
|
+
gasUsed: MAX_L1_TX_LIMIT,
|
|
1298
1528
|
logs: []
|
|
1299
1529
|
};
|
|
1300
1530
|
}
|
|
1301
1531
|
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1532
|
+
this.backupFailedTx({
|
|
1533
|
+
id: keccak256(rollupData),
|
|
1534
|
+
failureType: 'simulation',
|
|
1535
|
+
request: {
|
|
1536
|
+
to: this.rollupContract.address,
|
|
1537
|
+
data: rollupData
|
|
1538
|
+
},
|
|
1539
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1540
|
+
error: {
|
|
1541
|
+
message: viemError.message,
|
|
1542
|
+
name: viemError.name
|
|
1543
|
+
},
|
|
1544
|
+
context: {
|
|
1545
|
+
actions: [
|
|
1546
|
+
'propose'
|
|
1547
|
+
],
|
|
1548
|
+
slot: Number(args[0].header.slotNumber),
|
|
1549
|
+
sender: this.getSenderAddress().toString()
|
|
1550
|
+
}
|
|
1551
|
+
});
|
|
1302
1552
|
throw err;
|
|
1303
1553
|
});
|
|
1304
1554
|
return {
|