@aztec/sequencer-client 0.0.1-commit.0b941701 → 0.0.1-commit.0dc957cde
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 +17 -7
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +63 -30
- package/dest/config.d.ts +26 -7
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +52 -36
- package/dest/global_variable_builder/fee_predictor.d.ts +37 -0
- package/dest/global_variable_builder/fee_predictor.d.ts.map +1 -0
- package/dest/global_variable_builder/fee_predictor.js +128 -0
- package/dest/global_variable_builder/fee_provider.d.ts +21 -0
- package/dest/global_variable_builder/fee_provider.d.ts.map +1 -0
- package/dest/global_variable_builder/fee_provider.js +58 -0
- package/dest/global_variable_builder/global_builder.d.ts +15 -16
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +16 -50
- package/dest/global_variable_builder/index.d.ts +4 -2
- package/dest/global_variable_builder/index.d.ts.map +1 -1
- package/dest/global_variable_builder/index.js +2 -0
- 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 +58 -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 -5
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +27 -3
- 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 +75 -49
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +470 -156
- package/dest/sequencer/chain_state_overrides.d.ts +25 -0
- package/dest/sequencer/chain_state_overrides.d.ts.map +1 -0
- package/dest/sequencer/chain_state_overrides.js +39 -0
- package/dest/sequencer/checkpoint_proposal_job.d.ts +67 -14
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +580 -242
- package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts +34 -0
- package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_proposal_job_metrics.js +72 -0
- 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 +7 -1
- package/dest/sequencer/events.d.ts.map +1 -1
- package/dest/sequencer/metrics.d.ts +21 -8
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +135 -36
- package/dest/sequencer/sequencer.d.ts +45 -17
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +160 -94
- package/dest/sequencer/timetable.d.ts +17 -6
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +51 -46
- 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 +81 -30
- package/src/config.ts +70 -46
- package/src/global_variable_builder/README.md +44 -0
- package/src/global_variable_builder/fee_predictor.ts +172 -0
- package/src/global_variable_builder/fee_provider.ts +75 -0
- package/src/global_variable_builder/global_builder.ts +27 -63
- package/src/global_variable_builder/index.ts +3 -1
- package/src/publisher/config.ts +157 -45
- 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 +57 -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 -9
- package/src/publisher/sequencer-publisher-metrics.ts +7 -3
- package/src/publisher/sequencer-publisher.ts +531 -214
- package/src/sequencer/README.md +83 -13
- package/src/sequencer/chain_state_overrides.ts +87 -0
- package/src/sequencer/checkpoint_proposal_job.ts +748 -280
- package/src/sequencer/checkpoint_proposal_job_metrics.ts +128 -0
- package/src/sequencer/checkpoint_voter.ts +1 -12
- package/src/sequencer/events.ts +6 -1
- package/src/sequencer/metrics.ts +156 -40
- package/src/sequencer/sequencer.ts +232 -107
- package/src/sequencer/timetable.ts +70 -57
- 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,33 +372,35 @@ 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,
|
|
375
|
+
import { FeeAssetPriceOracle, MULTI_CALL_3_ADDRESS, Multicall3, SimulationOverridesBuilder, buildSimulationOverridesStateOverride } 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
|
-
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
381
380
|
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
381
|
+
import { trimmedBytesLength } from '@aztec/foundation/buffer';
|
|
382
382
|
import { pick } from '@aztec/foundation/collection';
|
|
383
|
+
import { TimeoutError } from '@aztec/foundation/error';
|
|
383
384
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
384
385
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
385
386
|
import { createLogger } from '@aztec/foundation/log';
|
|
387
|
+
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
388
|
+
import { InterruptibleSleep } from '@aztec/foundation/sleep';
|
|
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',
|
|
396
401
|
'invalidate-by-insufficient-attestations',
|
|
397
402
|
'propose',
|
|
398
403
|
'governance-signal',
|
|
399
|
-
'empire-slashing-signal',
|
|
400
|
-
'create-empire-payload',
|
|
401
|
-
'execute-empire-payload',
|
|
402
404
|
'vote-offenses',
|
|
403
405
|
'execute-slash'
|
|
404
406
|
];
|
|
@@ -429,19 +431,22 @@ 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;
|
|
442
|
+
aztecSlotDuration;
|
|
443
|
+
/** Date provider for wall-clock time. */ dateProvider;
|
|
438
444
|
blobClient;
|
|
439
445
|
/** Address to use for simulations in fisherman mode (actual proposer's address) */ proposerAddressForSimulation;
|
|
446
|
+
/** Optional callback to obtain a replacement publisher when the current one fails to send. */ getNextPublisher;
|
|
440
447
|
/** L1 fee analyzer for fisherman mode */ l1FeeAnalyzer;
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
// Total used for emptier block from above test: 429k (of which 84k is 1x blob)
|
|
444
|
-
static PROPOSE_GAS_GUESS = 12_000_000n;
|
|
448
|
+
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */ feeAssetPriceOracle;
|
|
449
|
+
/** Interruptible sleep used by sendRequestsAt to wait until a target timestamp. */ interruptibleSleep;
|
|
445
450
|
// A CALL to a cold address is 2700 gas
|
|
446
451
|
static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
447
452
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
@@ -450,7 +455,6 @@ export class SequencerPublisher {
|
|
|
450
455
|
rollupContract;
|
|
451
456
|
govProposerContract;
|
|
452
457
|
slashingProposerContract;
|
|
453
|
-
slashFactoryContract;
|
|
454
458
|
tracer;
|
|
455
459
|
requests;
|
|
456
460
|
constructor(config, deps){
|
|
@@ -460,16 +464,22 @@ export class SequencerPublisher {
|
|
|
460
464
|
this.slashingLog = createLogger('sequencer:publisher:slashing');
|
|
461
465
|
this.lastActions = {};
|
|
462
466
|
this.isPayloadEmptyCache = new Map();
|
|
467
|
+
this.payloadProposedCache = new Set();
|
|
468
|
+
this.interruptibleSleep = new InterruptibleSleep();
|
|
463
469
|
this.requests = [];
|
|
464
470
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
465
471
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
472
|
+
this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
|
|
473
|
+
this.dateProvider = deps.dateProvider;
|
|
466
474
|
this.epochCache = deps.epochCache;
|
|
467
475
|
this.lastActions = deps.lastActions;
|
|
468
476
|
this.blobClient = deps.blobClient;
|
|
477
|
+
this.dateProvider = deps.dateProvider;
|
|
469
478
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
470
479
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
471
480
|
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
472
481
|
this.l1TxUtils = deps.l1TxUtils;
|
|
482
|
+
this.getNextPublisher = deps.getNextPublisher;
|
|
473
483
|
this.rollupContract = deps.rollupContract;
|
|
474
484
|
this.govProposerContract = deps.governanceProposerContract;
|
|
475
485
|
this.slashingProposerContract = deps.slashingProposerContract;
|
|
@@ -478,15 +488,40 @@ export class SequencerPublisher {
|
|
|
478
488
|
const newSlashingProposer = await this.rollupContract.getSlashingProposer();
|
|
479
489
|
this.slashingProposerContract = newSlashingProposer;
|
|
480
490
|
});
|
|
481
|
-
this.slashFactoryContract = deps.slashFactoryContract;
|
|
482
491
|
// Initialize L1 fee analyzer for fisherman mode
|
|
483
492
|
if (config.fishermanMode) {
|
|
484
493
|
this.l1FeeAnalyzer = new L1FeeAnalyzer(this.l1TxUtils.client, deps.dateProvider, createLogger('sequencer:publisher:fee-analyzer'));
|
|
485
494
|
}
|
|
495
|
+
// Initialize fee asset price oracle
|
|
496
|
+
this.feeAssetPriceOracle = new FeeAssetPriceOracle(this.l1TxUtils.client, this.rollupContract, createLogger('sequencer:publisher:price-oracle'));
|
|
497
|
+
// Initialize failed L1 tx store (optional, for test networks)
|
|
498
|
+
this.failedTxStore = createL1TxFailedStore(config.l1TxFailedStore, this.log);
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Backs up a failed L1 transaction to the configured store for debugging.
|
|
502
|
+
* Does nothing if no store is configured.
|
|
503
|
+
*/ backupFailedTx(failedTx) {
|
|
504
|
+
if (!this.failedTxStore) {
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
const tx = {
|
|
508
|
+
...failedTx,
|
|
509
|
+
timestamp: Date.now()
|
|
510
|
+
};
|
|
511
|
+
// Fire and forget - don't block on backup
|
|
512
|
+
void this.failedTxStore.then((store)=>store?.saveFailedTx(tx)).catch((err)=>{
|
|
513
|
+
this.log.warn(`Failed to backup failed L1 tx to store`, err);
|
|
514
|
+
});
|
|
486
515
|
}
|
|
487
516
|
getRollupContract() {
|
|
488
517
|
return this.rollupContract;
|
|
489
518
|
}
|
|
519
|
+
/**
|
|
520
|
+
* Gets the fee asset price modifier from the oracle.
|
|
521
|
+
* Returns 0n if the oracle query fails.
|
|
522
|
+
*/ getFeeAssetPriceModifier() {
|
|
523
|
+
return this.feeAssetPriceOracle.computePriceModifier();
|
|
524
|
+
}
|
|
490
525
|
getSenderAddress() {
|
|
491
526
|
return this.l1TxUtils.getSenderAddress();
|
|
492
527
|
}
|
|
@@ -505,7 +540,7 @@ export class SequencerPublisher {
|
|
|
505
540
|
this.requests.push(request);
|
|
506
541
|
}
|
|
507
542
|
getCurrentL2Slot() {
|
|
508
|
-
return this.epochCache.
|
|
543
|
+
return this.epochCache.getSlotNow();
|
|
509
544
|
}
|
|
510
545
|
/**
|
|
511
546
|
* Clears all pending requests without sending them.
|
|
@@ -544,7 +579,7 @@ export class SequencerPublisher {
|
|
|
544
579
|
// Get the transaction requests
|
|
545
580
|
const l1Requests = requestsToAnalyze.map((r)=>r.request);
|
|
546
581
|
// Start the analysis
|
|
547
|
-
const analysisId = await this.l1FeeAnalyzer.startAnalysis(l2SlotNumber, gasLimit > 0n ? gasLimit :
|
|
582
|
+
const analysisId = await this.l1FeeAnalyzer.startAnalysis(l2SlotNumber, gasLimit > 0n ? gasLimit : MAX_L1_TX_LIMIT, l1Requests, blobConfig, onComplete);
|
|
548
583
|
this.log.info('Started L1 fee analysis', {
|
|
549
584
|
analysisId,
|
|
550
585
|
l2SlotNumber: l2SlotNumber.toString(),
|
|
@@ -594,15 +629,24 @@ export class SequencerPublisher {
|
|
|
594
629
|
// @note - we can only have one blob config per bundle
|
|
595
630
|
// find requests with gas and blob configs
|
|
596
631
|
// See https://github.com/AztecProtocol/aztec-packages/issues/11513
|
|
597
|
-
const gasConfigs =
|
|
598
|
-
const blobConfigs =
|
|
632
|
+
const gasConfigs = validRequests.filter((request)=>request.gasConfig).map((request)=>request.gasConfig);
|
|
633
|
+
const blobConfigs = validRequests.filter((request)=>request.blobConfig).map((request)=>request.blobConfig);
|
|
599
634
|
if (blobConfigs.length > 1) {
|
|
600
635
|
throw new Error('Multiple blob configs found');
|
|
601
636
|
}
|
|
602
637
|
const blobConfig = blobConfigs[0];
|
|
603
638
|
// Merge gasConfigs. Yields the sum of gasLimits, and the earliest txTimeoutAt, or undefined if no gasConfig sets them.
|
|
604
639
|
const gasLimits = gasConfigs.map((g)=>g?.gasLimit).filter((g)=>g !== undefined);
|
|
605
|
-
|
|
640
|
+
let gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
|
|
641
|
+
// Cap at L1 block gas limit so the node accepts the tx ("gas limit too high" otherwise).
|
|
642
|
+
const maxGas = MAX_L1_TX_LIMIT;
|
|
643
|
+
if (gasLimit !== undefined && gasLimit > maxGas) {
|
|
644
|
+
this.log.debug('Capping bundled tx gas limit to L1 max', {
|
|
645
|
+
requested: gasLimit,
|
|
646
|
+
capped: maxGas
|
|
647
|
+
});
|
|
648
|
+
gasLimit = maxGas;
|
|
649
|
+
}
|
|
606
650
|
const txTimeoutAts = gasConfigs.map((g)=>g?.txTimeoutAt).filter((g)=>g !== undefined);
|
|
607
651
|
const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map((g)=>g.getTime()))) : undefined; // earliest
|
|
608
652
|
const txConfig = {
|
|
@@ -613,12 +657,34 @@ export class SequencerPublisher {
|
|
|
613
657
|
// This ensures the committee gets precomputed correctly
|
|
614
658
|
validRequests.sort((a, b)=>compareActions(a.action, b.action));
|
|
615
659
|
try {
|
|
660
|
+
// Capture context for failed tx backup before sending
|
|
661
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
662
|
+
const multicallData = encodeFunctionData({
|
|
663
|
+
abi: multicall3Abi,
|
|
664
|
+
functionName: 'aggregate3',
|
|
665
|
+
args: [
|
|
666
|
+
validRequests.map((r)=>({
|
|
667
|
+
target: r.request.to,
|
|
668
|
+
callData: r.request.data,
|
|
669
|
+
allowFailure: true
|
|
670
|
+
}))
|
|
671
|
+
]
|
|
672
|
+
});
|
|
673
|
+
const blobDataHex = blobConfig?.blobs?.map((b)=>toHex(b));
|
|
674
|
+
const txContext = {
|
|
675
|
+
multicallData,
|
|
676
|
+
blobData: blobDataHex,
|
|
677
|
+
l1BlockNumber
|
|
678
|
+
};
|
|
616
679
|
this.log.debug('Forwarding transactions', {
|
|
617
680
|
validRequests: validRequests.map((request)=>request.action),
|
|
618
681
|
txConfig
|
|
619
682
|
});
|
|
620
|
-
const result = await
|
|
621
|
-
|
|
683
|
+
const result = await this.forwardWithPublisherRotation(validRequests, txConfig, blobConfig);
|
|
684
|
+
if (result === undefined) {
|
|
685
|
+
return undefined;
|
|
686
|
+
}
|
|
687
|
+
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(validRequests, result, txContext);
|
|
622
688
|
return {
|
|
623
689
|
result,
|
|
624
690
|
expiredActions,
|
|
@@ -638,17 +704,118 @@ export class SequencerPublisher {
|
|
|
638
704
|
}
|
|
639
705
|
}
|
|
640
706
|
}
|
|
641
|
-
|
|
707
|
+
/**
|
|
708
|
+
* Forwards transactions via Multicall3, rotating to the next available publisher if a send
|
|
709
|
+
* failure occurs (i.e. the tx never reached the chain).
|
|
710
|
+
* On-chain reverts and simulation errors are returned as-is without rotation.
|
|
711
|
+
*/ async forwardWithPublisherRotation(validRequests, txConfig, blobConfig) {
|
|
712
|
+
const triedAddresses = [];
|
|
713
|
+
let currentPublisher = this.l1TxUtils;
|
|
714
|
+
while(true){
|
|
715
|
+
triedAddresses.push(currentPublisher.getSenderAddress());
|
|
716
|
+
try {
|
|
717
|
+
const result = await Multicall3.forward(validRequests.map((r)=>r.request), currentPublisher, txConfig, blobConfig, this.rollupContract.address, this.log);
|
|
718
|
+
this.l1TxUtils = currentPublisher;
|
|
719
|
+
return result;
|
|
720
|
+
} catch (err) {
|
|
721
|
+
if (err instanceof TimeoutError) {
|
|
722
|
+
throw err;
|
|
723
|
+
}
|
|
724
|
+
const viemError = formatViemError(err);
|
|
725
|
+
if (!this.getNextPublisher) {
|
|
726
|
+
this.log.error('Failed to publish bundled transactions', viemError);
|
|
727
|
+
return undefined;
|
|
728
|
+
}
|
|
729
|
+
this.log.warn(`Publisher ${currentPublisher.getSenderAddress()} failed to send, rotating to next publisher`, viemError);
|
|
730
|
+
const nextPublisher = await this.getNextPublisher([
|
|
731
|
+
...triedAddresses
|
|
732
|
+
]);
|
|
733
|
+
if (!nextPublisher) {
|
|
734
|
+
this.log.error('All available publishers exhausted, failed to publish bundled transactions');
|
|
735
|
+
return undefined;
|
|
736
|
+
}
|
|
737
|
+
currentPublisher = nextPublisher;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
/*
|
|
742
|
+
* Schedules sending all enqueued requests at (or after) the given timestamp.
|
|
743
|
+
* Uses InterruptibleSleep so it can be cancelled via interrupt().
|
|
744
|
+
* Returns the promise for the L1 response (caller should NOT await this in the work loop).
|
|
745
|
+
*/ async sendRequestsAt(submitAfter) {
|
|
746
|
+
const ms = submitAfter.getTime() - this.dateProvider.now();
|
|
747
|
+
if (ms > 0) {
|
|
748
|
+
this.log.debug(`Sleeping ${ms}ms before sending requests`, {
|
|
749
|
+
submitAfter
|
|
750
|
+
});
|
|
751
|
+
await this.interruptibleSleep.sleep(ms);
|
|
752
|
+
}
|
|
753
|
+
if (this.interrupted) {
|
|
754
|
+
return undefined;
|
|
755
|
+
}
|
|
756
|
+
// Re-validate enqueued requests after the sleep (state may have changed, e.g. prune or L1 reorg)
|
|
757
|
+
const validRequests = [];
|
|
758
|
+
for (const request of this.requests){
|
|
759
|
+
if (!request.preCheck) {
|
|
760
|
+
validRequests.push(request);
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
try {
|
|
764
|
+
await request.preCheck();
|
|
765
|
+
validRequests.push(request);
|
|
766
|
+
} catch (err) {
|
|
767
|
+
this.log.warn(`Pre-send validation failed for ${request.action}, discarding request`, err);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
this.requests = validRequests;
|
|
771
|
+
if (this.requests.length === 0) {
|
|
772
|
+
return undefined;
|
|
773
|
+
}
|
|
774
|
+
return this.sendRequests();
|
|
775
|
+
}
|
|
776
|
+
callbackBundledTransactions(requests, result, txContext) {
|
|
642
777
|
const actionsListStr = requests.map((r)=>r.action).join(', ');
|
|
643
778
|
if (result instanceof FormattedViemError) {
|
|
644
779
|
this.log.error(`Failed to publish bundled transactions (${actionsListStr})`, result);
|
|
780
|
+
this.backupFailedTx({
|
|
781
|
+
id: keccak256(txContext.multicallData),
|
|
782
|
+
failureType: 'send-error',
|
|
783
|
+
request: {
|
|
784
|
+
to: MULTI_CALL_3_ADDRESS,
|
|
785
|
+
data: txContext.multicallData
|
|
786
|
+
},
|
|
787
|
+
blobData: txContext.blobData,
|
|
788
|
+
l1BlockNumber: txContext.l1BlockNumber.toString(),
|
|
789
|
+
error: {
|
|
790
|
+
message: result.message,
|
|
791
|
+
name: result.name
|
|
792
|
+
},
|
|
793
|
+
context: {
|
|
794
|
+
actions: requests.map((r)=>r.action),
|
|
795
|
+
requests: requests.map((r)=>({
|
|
796
|
+
action: r.action,
|
|
797
|
+
to: r.request.to,
|
|
798
|
+
data: r.request.data
|
|
799
|
+
})),
|
|
800
|
+
sender: this.getSenderAddress().toString()
|
|
801
|
+
}
|
|
802
|
+
});
|
|
645
803
|
return {
|
|
646
804
|
failedActions: requests.map((r)=>r.action)
|
|
647
805
|
};
|
|
648
806
|
} else {
|
|
649
807
|
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
650
808
|
result,
|
|
651
|
-
requests
|
|
809
|
+
requests: requests.map((r)=>({
|
|
810
|
+
...r,
|
|
811
|
+
// Avoid logging large blob data
|
|
812
|
+
blobConfig: r.blobConfig ? {
|
|
813
|
+
...r.blobConfig,
|
|
814
|
+
blobs: r.blobConfig.blobs.map((b)=>({
|
|
815
|
+
size: trimmedBytesLength(b)
|
|
816
|
+
}))
|
|
817
|
+
} : undefined
|
|
818
|
+
}))
|
|
652
819
|
});
|
|
653
820
|
const successfulActions = [];
|
|
654
821
|
const failedActions = [];
|
|
@@ -659,6 +826,37 @@ export class SequencerPublisher {
|
|
|
659
826
|
failedActions.push(request.action);
|
|
660
827
|
}
|
|
661
828
|
}
|
|
829
|
+
// Single backup for the whole reverted tx
|
|
830
|
+
if (failedActions.length > 0 && result?.receipt?.status === 'reverted') {
|
|
831
|
+
this.backupFailedTx({
|
|
832
|
+
id: result.receipt.transactionHash,
|
|
833
|
+
failureType: 'revert',
|
|
834
|
+
request: {
|
|
835
|
+
to: MULTI_CALL_3_ADDRESS,
|
|
836
|
+
data: txContext.multicallData
|
|
837
|
+
},
|
|
838
|
+
blobData: txContext.blobData,
|
|
839
|
+
l1BlockNumber: result.receipt.blockNumber.toString(),
|
|
840
|
+
receipt: {
|
|
841
|
+
transactionHash: result.receipt.transactionHash,
|
|
842
|
+
blockNumber: result.receipt.blockNumber.toString(),
|
|
843
|
+
gasUsed: (result.receipt.gasUsed ?? 0n).toString(),
|
|
844
|
+
status: 'reverted'
|
|
845
|
+
},
|
|
846
|
+
error: {
|
|
847
|
+
message: result.errorMsg ?? 'Transaction reverted'
|
|
848
|
+
},
|
|
849
|
+
context: {
|
|
850
|
+
actions: failedActions,
|
|
851
|
+
requests: requests.filter((r)=>failedActions.includes(r.action)).map((r)=>({
|
|
852
|
+
action: r.action,
|
|
853
|
+
to: r.request.to,
|
|
854
|
+
data: r.request.data
|
|
855
|
+
})),
|
|
856
|
+
sender: this.getSenderAddress().toString()
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
}
|
|
662
860
|
return {
|
|
663
861
|
successfulActions,
|
|
664
862
|
failedActions
|
|
@@ -666,19 +864,20 @@ export class SequencerPublisher {
|
|
|
666
864
|
}
|
|
667
865
|
}
|
|
668
866
|
/**
|
|
669
|
-
* @notice Will call `
|
|
867
|
+
* @notice Will call `canProposeAt` to make sure that it is possible to propose
|
|
670
868
|
* @param tipArchive - The archive to check
|
|
671
869
|
* @returns The slot and block number if it is possible to propose, undefined otherwise
|
|
672
|
-
*/
|
|
870
|
+
*/ async canProposeAt(tipArchive, msgSender, simulationOverridesPlan) {
|
|
673
871
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
674
872
|
const ignoredErrors = [
|
|
675
873
|
'SlotAlreadyInChain',
|
|
676
874
|
'InvalidProposer',
|
|
677
875
|
'InvalidArchive'
|
|
678
876
|
];
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
877
|
+
const pipelined = this.epochCache.isProposerPipeliningEnabled();
|
|
878
|
+
const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
|
|
879
|
+
const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
|
|
880
|
+
return this.rollupContract.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan)).catch((err)=>{
|
|
682
881
|
if (err instanceof FormattedViemError && ignoredErrors.find((e)=>err.message.includes(e))) {
|
|
683
882
|
this.log.warn(`Failed canProposeAtTime check with ${ignoredErrors.find((e)=>err.message.includes(e))}`, {
|
|
684
883
|
error: err.message
|
|
@@ -694,7 +893,7 @@ export class SequencerPublisher {
|
|
|
694
893
|
* @dev This is a convenience function that can be used by the sequencer to validate a "partial" header.
|
|
695
894
|
* It will throw if the block header is invalid.
|
|
696
895
|
* @param header - The block header to validate
|
|
697
|
-
*/ async validateBlockHeader(header,
|
|
896
|
+
*/ async validateBlockHeader(header, simulationOverridesPlan) {
|
|
698
897
|
const flags = {
|
|
699
898
|
ignoreDA: true,
|
|
700
899
|
ignoreSignatures: true
|
|
@@ -708,8 +907,8 @@ export class SequencerPublisher {
|
|
|
708
907
|
header.blobsHash.toString(),
|
|
709
908
|
flags
|
|
710
909
|
];
|
|
711
|
-
const ts =
|
|
712
|
-
const stateOverrides = await this.rollupContract
|
|
910
|
+
const ts = this.getSimulationTimestamp(header.slotNumber);
|
|
911
|
+
const stateOverrides = await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan);
|
|
713
912
|
let balance = 0n;
|
|
714
913
|
if (this.config.fishermanMode) {
|
|
715
914
|
// In fisherman mode, we can't know where the proposer is publishing from
|
|
@@ -731,7 +930,7 @@ export class SequencerPublisher {
|
|
|
731
930
|
}),
|
|
732
931
|
from: MULTI_CALL_3_ADDRESS
|
|
733
932
|
}, {
|
|
734
|
-
time: ts
|
|
933
|
+
time: ts
|
|
735
934
|
}, stateOverrides);
|
|
736
935
|
this.log.debug(`Simulated validateHeader`);
|
|
737
936
|
}
|
|
@@ -761,8 +960,12 @@ export class SequencerPublisher {
|
|
|
761
960
|
...logData,
|
|
762
961
|
request
|
|
763
962
|
});
|
|
963
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
764
964
|
try {
|
|
765
|
-
const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined,
|
|
965
|
+
const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, mergeAbis([
|
|
966
|
+
request.abi ?? [],
|
|
967
|
+
ErrorsAbi
|
|
968
|
+
]));
|
|
766
969
|
this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} succeeded`, {
|
|
767
970
|
...logData,
|
|
768
971
|
request,
|
|
@@ -773,20 +976,21 @@ export class SequencerPublisher {
|
|
|
773
976
|
gasUsed,
|
|
774
977
|
checkpointNumber,
|
|
775
978
|
forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
|
|
979
|
+
lastArchive: validationResult.checkpoint.lastArchive,
|
|
776
980
|
reason
|
|
777
981
|
};
|
|
778
982
|
} catch (err) {
|
|
779
983
|
const viemError = formatViemError(err);
|
|
780
984
|
// If the error is due to the checkpoint not being in the pending chain, and it was indeed removed by someone else,
|
|
781
985
|
// we can safely ignore it and return undefined so we go ahead with checkpoint building.
|
|
782
|
-
if (viemError.message?.includes('
|
|
986
|
+
if (viemError.message?.includes('Rollup__CheckpointNotInPendingChain')) {
|
|
783
987
|
this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`, {
|
|
784
988
|
...logData,
|
|
785
989
|
request,
|
|
786
990
|
error: viemError.message
|
|
787
991
|
});
|
|
788
|
-
const
|
|
789
|
-
if (
|
|
992
|
+
const latestProposedCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
993
|
+
if (latestProposedCheckpointNumber < checkpointNumber) {
|
|
790
994
|
this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, {
|
|
791
995
|
...logData
|
|
792
996
|
});
|
|
@@ -800,6 +1004,27 @@ export class SequencerPublisher {
|
|
|
800
1004
|
}
|
|
801
1005
|
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
802
1006
|
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
|
|
1007
|
+
this.backupFailedTx({
|
|
1008
|
+
id: keccak256(request.data),
|
|
1009
|
+
failureType: 'simulation',
|
|
1010
|
+
request: {
|
|
1011
|
+
to: request.to,
|
|
1012
|
+
data: request.data,
|
|
1013
|
+
value: request.value?.toString()
|
|
1014
|
+
},
|
|
1015
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1016
|
+
error: {
|
|
1017
|
+
message: viemError.message,
|
|
1018
|
+
name: viemError.name
|
|
1019
|
+
},
|
|
1020
|
+
context: {
|
|
1021
|
+
actions: [
|
|
1022
|
+
`invalidate-${reason}`
|
|
1023
|
+
],
|
|
1024
|
+
checkpointNumber,
|
|
1025
|
+
sender: this.getSenderAddress().toString()
|
|
1026
|
+
}
|
|
1027
|
+
});
|
|
803
1028
|
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, {
|
|
804
1029
|
cause: viemError
|
|
805
1030
|
});
|
|
@@ -825,31 +1050,16 @@ export class SequencerPublisher {
|
|
|
825
1050
|
throw new Error(`Unknown reason for invalidation`);
|
|
826
1051
|
}
|
|
827
1052
|
}
|
|
828
|
-
/** Simulates `propose` to make sure that the checkpoint is valid for submission */ async validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature,
|
|
829
|
-
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
|
-
// }
|
|
1053
|
+
/** Simulates `propose` to make sure that the checkpoint is valid for submission */ async validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, simulationOverridesPlan) {
|
|
844
1054
|
const blobFields = checkpoint.toBlobFields();
|
|
845
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
1055
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
846
1056
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
847
1057
|
const args = [
|
|
848
1058
|
{
|
|
849
1059
|
header: checkpoint.header.toViem(),
|
|
850
1060
|
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
851
1061
|
oracleInput: {
|
|
852
|
-
feeAssetPriceModifier:
|
|
1062
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier
|
|
853
1063
|
}
|
|
854
1064
|
},
|
|
855
1065
|
attestationsAndSigners.getPackedAttestations(),
|
|
@@ -857,10 +1067,9 @@ export class SequencerPublisher {
|
|
|
857
1067
|
attestationsAndSignersSignature.toViemSignature(),
|
|
858
1068
|
blobInput
|
|
859
1069
|
];
|
|
860
|
-
await this.simulateProposeTx(args,
|
|
861
|
-
return ts;
|
|
1070
|
+
await this.simulateProposeTx(args, simulationOverridesPlan);
|
|
862
1071
|
}
|
|
863
|
-
async enqueueCastSignalHelper(slotNumber,
|
|
1072
|
+
async enqueueCastSignalHelper(slotNumber, signalType, payload, base, signerAddress, signer) {
|
|
864
1073
|
if (this.lastActions[signalType] && this.lastActions[signalType] === slotNumber) {
|
|
865
1074
|
this.log.debug(`Skipping duplicate vote cast signal ${signalType} for slot ${slotNumber}`);
|
|
866
1075
|
return false;
|
|
@@ -884,6 +1093,28 @@ export class SequencerPublisher {
|
|
|
884
1093
|
this.log.warn(`Skipping vote cast for payload with empty code`);
|
|
885
1094
|
return false;
|
|
886
1095
|
}
|
|
1096
|
+
// Check if payload was already submitted to governance
|
|
1097
|
+
const cacheKey = payload.toString();
|
|
1098
|
+
if (!this.payloadProposedCache.has(cacheKey)) {
|
|
1099
|
+
try {
|
|
1100
|
+
const l1StartBlock = await this.rollupContract.getL1StartBlock();
|
|
1101
|
+
const proposed = await retry(()=>base.hasPayloadBeenProposed(payload.toString(), l1StartBlock), 'Check if payload was proposed', makeBackoff([
|
|
1102
|
+
0,
|
|
1103
|
+
1,
|
|
1104
|
+
2
|
|
1105
|
+
]), this.log, true);
|
|
1106
|
+
if (proposed) {
|
|
1107
|
+
this.payloadProposedCache.add(cacheKey);
|
|
1108
|
+
}
|
|
1109
|
+
} catch (err) {
|
|
1110
|
+
this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
|
|
1111
|
+
return false;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
if (this.payloadProposedCache.has(cacheKey)) {
|
|
1115
|
+
this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
|
|
1116
|
+
return false;
|
|
1117
|
+
}
|
|
887
1118
|
const cachedLastVote = this.lastActions[signalType];
|
|
888
1119
|
this.lastActions[signalType] = slotNumber;
|
|
889
1120
|
const action = signalType;
|
|
@@ -894,15 +1125,45 @@ export class SequencerPublisher {
|
|
|
894
1125
|
signer: this.l1TxUtils.client.account?.address,
|
|
895
1126
|
lastValidL2Slot: slotNumber
|
|
896
1127
|
});
|
|
1128
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1129
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
897
1130
|
try {
|
|
898
1131
|
await this.l1TxUtils.simulate(request, {
|
|
899
1132
|
time: timestamp
|
|
900
|
-
}, [],
|
|
1133
|
+
}, [], mergeAbis([
|
|
1134
|
+
request.abi ?? [],
|
|
1135
|
+
ErrorsAbi
|
|
1136
|
+
]));
|
|
901
1137
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, {
|
|
902
1138
|
request
|
|
903
1139
|
});
|
|
904
1140
|
} catch (err) {
|
|
905
|
-
|
|
1141
|
+
const viemError = formatViemError(err);
|
|
1142
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError, {
|
|
1143
|
+
simulationTimestamp: timestamp,
|
|
1144
|
+
l1BlockNumber
|
|
1145
|
+
});
|
|
1146
|
+
this.backupFailedTx({
|
|
1147
|
+
id: keccak256(request.data),
|
|
1148
|
+
failureType: 'simulation',
|
|
1149
|
+
request: {
|
|
1150
|
+
to: request.to,
|
|
1151
|
+
data: request.data,
|
|
1152
|
+
value: request.value?.toString()
|
|
1153
|
+
},
|
|
1154
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1155
|
+
error: {
|
|
1156
|
+
message: viemError.message,
|
|
1157
|
+
name: viemError.name
|
|
1158
|
+
},
|
|
1159
|
+
context: {
|
|
1160
|
+
actions: [
|
|
1161
|
+
action
|
|
1162
|
+
],
|
|
1163
|
+
slot: slotNumber,
|
|
1164
|
+
sender: this.getSenderAddress().toString()
|
|
1165
|
+
}
|
|
1166
|
+
});
|
|
906
1167
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
907
1168
|
}
|
|
908
1169
|
// TODO(palla/slash): All votes (governance and slashing) should txTimeoutAt at the end of the slot.
|
|
@@ -946,55 +1207,17 @@ export class SequencerPublisher {
|
|
|
946
1207
|
/**
|
|
947
1208
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
948
1209
|
* @param slotNumber - The slot number to cast a signal for.
|
|
949
|
-
* @param timestamp - The timestamp of the slot to cast a signal for.
|
|
950
1210
|
* @returns True if the signal was successfully enqueued, false otherwise.
|
|
951
|
-
*/ enqueueGovernanceCastSignal(governancePayload, slotNumber,
|
|
952
|
-
return this.enqueueCastSignalHelper(slotNumber,
|
|
1211
|
+
*/ enqueueGovernanceCastSignal(governancePayload, slotNumber, signerAddress, signer) {
|
|
1212
|
+
return this.enqueueCastSignalHelper(slotNumber, 'governance-signal', governancePayload, this.govProposerContract, signerAddress, signer);
|
|
953
1213
|
}
|
|
954
|
-
/** Enqueues all slashing actions as returned by the slasher client. */ async enqueueSlashingActions(actions, slotNumber,
|
|
1214
|
+
/** Enqueues all slashing actions as returned by the slasher client. */ async enqueueSlashingActions(actions, slotNumber, signerAddress, signer) {
|
|
955
1215
|
if (actions.length === 0) {
|
|
956
1216
|
this.log.debug(`No slashing actions to enqueue for slot ${slotNumber}`);
|
|
957
1217
|
return false;
|
|
958
1218
|
}
|
|
959
1219
|
for (const action of actions){
|
|
960
1220
|
switch(action.type){
|
|
961
|
-
case 'vote-empire-payload':
|
|
962
|
-
{
|
|
963
|
-
if (this.slashingProposerContract?.type !== 'empire') {
|
|
964
|
-
this.log.error('Cannot vote for empire payload on non-empire slashing contract');
|
|
965
|
-
break;
|
|
966
|
-
}
|
|
967
|
-
this.log.debug(`Enqueuing slashing vote for payload ${action.payload} at slot ${slotNumber}`, {
|
|
968
|
-
signerAddress
|
|
969
|
-
});
|
|
970
|
-
await this.enqueueCastSignalHelper(slotNumber, timestamp, 'empire-slashing-signal', action.payload, this.slashingProposerContract, signerAddress, signer);
|
|
971
|
-
break;
|
|
972
|
-
}
|
|
973
|
-
case 'create-empire-payload':
|
|
974
|
-
{
|
|
975
|
-
this.log.debug(`Enqueuing slashing create payload at slot ${slotNumber}`, {
|
|
976
|
-
slotNumber,
|
|
977
|
-
signerAddress
|
|
978
|
-
});
|
|
979
|
-
const request = this.slashFactoryContract.buildCreatePayloadRequest(action.data);
|
|
980
|
-
await this.simulateAndEnqueueRequest('create-empire-payload', request, (receipt)=>!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs), slotNumber, timestamp);
|
|
981
|
-
break;
|
|
982
|
-
}
|
|
983
|
-
case 'execute-empire-payload':
|
|
984
|
-
{
|
|
985
|
-
this.log.debug(`Enqueuing slashing execute payload at slot ${slotNumber}`, {
|
|
986
|
-
slotNumber,
|
|
987
|
-
signerAddress
|
|
988
|
-
});
|
|
989
|
-
if (this.slashingProposerContract?.type !== 'empire') {
|
|
990
|
-
this.log.error('Cannot execute slashing payload on non-empire slashing contract');
|
|
991
|
-
return false;
|
|
992
|
-
}
|
|
993
|
-
const empireSlashingProposer = this.slashingProposerContract;
|
|
994
|
-
const request = empireSlashingProposer.buildExecuteRoundRequest(action.round);
|
|
995
|
-
await this.simulateAndEnqueueRequest('execute-empire-payload', request, (receipt)=>!!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs), slotNumber, timestamp);
|
|
996
|
-
break;
|
|
997
|
-
}
|
|
998
1221
|
case 'vote-offenses':
|
|
999
1222
|
{
|
|
1000
1223
|
this.log.debug(`Enqueuing slashing vote for ${action.votes.length} votes at slot ${slotNumber}`, {
|
|
@@ -1003,14 +1226,13 @@ export class SequencerPublisher {
|
|
|
1003
1226
|
votesCount: action.votes.length,
|
|
1004
1227
|
signerAddress
|
|
1005
1228
|
});
|
|
1006
|
-
if (this.slashingProposerContract
|
|
1007
|
-
this.log.error('
|
|
1229
|
+
if (!this.slashingProposerContract) {
|
|
1230
|
+
this.log.error('No slashing proposer contract available');
|
|
1008
1231
|
return false;
|
|
1009
1232
|
}
|
|
1010
|
-
const tallySlashingProposer = this.slashingProposerContract;
|
|
1011
1233
|
const votes = bufferToHex(encodeSlashConsensusVotes(action.votes));
|
|
1012
|
-
const request = await
|
|
1013
|
-
await this.simulateAndEnqueueRequest('vote-offenses', request, (receipt)=>!!
|
|
1234
|
+
const request = await this.slashingProposerContract.buildVoteRequestFromSigner(votes, slotNumber, signer);
|
|
1235
|
+
await this.simulateAndEnqueueRequest('vote-offenses', request, (receipt)=>!!this.slashingProposerContract.tryExtractVoteCastEvent(receipt.logs), slotNumber);
|
|
1014
1236
|
break;
|
|
1015
1237
|
}
|
|
1016
1238
|
case 'execute-slash':
|
|
@@ -1020,13 +1242,12 @@ export class SequencerPublisher {
|
|
|
1020
1242
|
round: action.round,
|
|
1021
1243
|
signerAddress
|
|
1022
1244
|
});
|
|
1023
|
-
if (this.slashingProposerContract
|
|
1024
|
-
this.log.error('
|
|
1245
|
+
if (!this.slashingProposerContract) {
|
|
1246
|
+
this.log.error('No slashing proposer contract available');
|
|
1025
1247
|
return false;
|
|
1026
1248
|
}
|
|
1027
|
-
const
|
|
1028
|
-
|
|
1029
|
-
await this.simulateAndEnqueueRequest('execute-slash', request, (receipt)=>!!tallySlashingProposer.tryExtractRoundExecutedEvent(receipt.logs), slotNumber, timestamp);
|
|
1249
|
+
const executeRequest = this.slashingProposerContract.buildExecuteRoundRequest(action.round, action.committees);
|
|
1250
|
+
await this.simulateAndEnqueueRequest('execute-slash', executeRequest, (receipt)=>!!this.slashingProposerContract.tryExtractRoundExecutedEvent(receipt.logs), slotNumber);
|
|
1030
1251
|
break;
|
|
1031
1252
|
}
|
|
1032
1253
|
default:
|
|
@@ -1041,35 +1262,49 @@ export class SequencerPublisher {
|
|
|
1041
1262
|
/** Simulates and enqueues a proposal for a checkpoint on L1 */ async enqueueProposeCheckpoint(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, opts = {}) {
|
|
1042
1263
|
const checkpointHeader = checkpoint.header;
|
|
1043
1264
|
const blobFields = checkpoint.toBlobFields();
|
|
1044
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
1265
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
1045
1266
|
const proposeTxArgs = {
|
|
1046
1267
|
header: checkpointHeader,
|
|
1047
1268
|
archive: checkpoint.archive.root.toBuffer(),
|
|
1048
1269
|
blobs,
|
|
1049
1270
|
attestationsAndSigners,
|
|
1050
|
-
attestationsAndSignersSignature
|
|
1271
|
+
attestationsAndSignersSignature,
|
|
1272
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier
|
|
1051
1273
|
};
|
|
1052
|
-
|
|
1274
|
+
const simulationOverridesPlan = SimulationOverridesBuilder.from(opts.simulationOverridesPlan).withoutBlobCheck().build();
|
|
1053
1275
|
try {
|
|
1054
1276
|
// @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
|
|
1055
1277
|
// This means that we can avoid the simulation issues in later checks.
|
|
1056
1278
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
1057
1279
|
// make time consistency checks break.
|
|
1058
1280
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
1059
|
-
|
|
1281
|
+
await this.validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, simulationOverridesPlan);
|
|
1060
1282
|
} catch (err) {
|
|
1061
1283
|
this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
1062
1284
|
...checkpoint.getStats(),
|
|
1063
1285
|
slotNumber: checkpoint.header.slotNumber,
|
|
1064
|
-
|
|
1286
|
+
simulationOverridesPlan
|
|
1065
1287
|
});
|
|
1066
1288
|
throw err;
|
|
1067
1289
|
}
|
|
1290
|
+
// Build a pre-check callback that re-validates the checkpoint before L1 submission.
|
|
1291
|
+
// During pipelining this catches stale proposals due to prunes or L1 reorgs that occur during the pipeline sleep.
|
|
1292
|
+
let preCheck = undefined;
|
|
1293
|
+
if (this.epochCache.isProposerPipeliningEnabled()) {
|
|
1294
|
+
preCheck = async ()=>{
|
|
1295
|
+
this.log.debug(`Re-validating checkpoint ${checkpoint.number} before L1 submission`);
|
|
1296
|
+
await this.validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, simulationOverridesPlan);
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1068
1299
|
this.log.verbose(`Enqueuing checkpoint propose transaction`, {
|
|
1069
1300
|
...checkpoint.toCheckpointInfo(),
|
|
1070
|
-
|
|
1301
|
+
txTimeoutAt: opts.txTimeoutAt,
|
|
1302
|
+
simulationOverridesPlan
|
|
1071
1303
|
});
|
|
1072
|
-
await this.addProposeTx(checkpoint, proposeTxArgs,
|
|
1304
|
+
await this.addProposeTx(checkpoint, proposeTxArgs, {
|
|
1305
|
+
txTimeoutAt: opts.txTimeoutAt,
|
|
1306
|
+
simulationOverridesPlan
|
|
1307
|
+
}, preCheck);
|
|
1073
1308
|
}
|
|
1074
1309
|
enqueueInvalidateCheckpoint(request, opts = {}) {
|
|
1075
1310
|
if (!request) {
|
|
@@ -1110,7 +1345,8 @@ export class SequencerPublisher {
|
|
|
1110
1345
|
}
|
|
1111
1346
|
});
|
|
1112
1347
|
}
|
|
1113
|
-
async simulateAndEnqueueRequest(action, request, checkSuccess, slotNumber
|
|
1348
|
+
async simulateAndEnqueueRequest(action, request, checkSuccess, slotNumber) {
|
|
1349
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
1114
1350
|
const logData = {
|
|
1115
1351
|
slotNumber,
|
|
1116
1352
|
timestamp,
|
|
@@ -1123,28 +1359,60 @@ export class SequencerPublisher {
|
|
|
1123
1359
|
const cachedLastActionSlot = this.lastActions[action];
|
|
1124
1360
|
this.lastActions[action] = slotNumber;
|
|
1125
1361
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
1362
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1126
1363
|
let gasUsed;
|
|
1364
|
+
const simulateAbi = mergeAbis([
|
|
1365
|
+
request.abi ?? [],
|
|
1366
|
+
ErrorsAbi
|
|
1367
|
+
]);
|
|
1127
1368
|
try {
|
|
1128
1369
|
({ gasUsed } = await this.l1TxUtils.simulate(request, {
|
|
1129
1370
|
time: timestamp
|
|
1130
|
-
}, [],
|
|
1371
|
+
}, [], simulateAbi));
|
|
1131
1372
|
this.log.verbose(`Simulation for ${action} succeeded`, {
|
|
1132
1373
|
...logData,
|
|
1133
1374
|
request,
|
|
1134
1375
|
gasUsed
|
|
1135
1376
|
});
|
|
1136
1377
|
} catch (err) {
|
|
1137
|
-
const viemError = formatViemError(err);
|
|
1378
|
+
const viemError = formatViemError(err, simulateAbi);
|
|
1138
1379
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1380
|
+
this.backupFailedTx({
|
|
1381
|
+
id: keccak256(request.data),
|
|
1382
|
+
failureType: 'simulation',
|
|
1383
|
+
request: {
|
|
1384
|
+
to: request.to,
|
|
1385
|
+
data: request.data,
|
|
1386
|
+
value: request.value?.toString()
|
|
1387
|
+
},
|
|
1388
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1389
|
+
error: {
|
|
1390
|
+
message: viemError.message,
|
|
1391
|
+
name: viemError.name
|
|
1392
|
+
},
|
|
1393
|
+
context: {
|
|
1394
|
+
actions: [
|
|
1395
|
+
action
|
|
1396
|
+
],
|
|
1397
|
+
slot: slotNumber,
|
|
1398
|
+
sender: this.getSenderAddress().toString()
|
|
1399
|
+
}
|
|
1400
|
+
});
|
|
1139
1401
|
return false;
|
|
1140
1402
|
}
|
|
1141
1403
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
1142
1404
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil(Number(gasUsed) * 64 / 63)));
|
|
1143
1405
|
logData.gasLimit = gasLimit;
|
|
1406
|
+
// Store the ABI used for simulation on the request so Multicall3.forward can decode errors
|
|
1407
|
+
// when the tx is sent and a revert is diagnosed via simulation.
|
|
1408
|
+
const requestWithAbi = {
|
|
1409
|
+
...request,
|
|
1410
|
+
abi: simulateAbi
|
|
1411
|
+
};
|
|
1144
1412
|
this.log.debug(`Enqueuing ${action}`, logData);
|
|
1145
1413
|
this.addRequest({
|
|
1146
1414
|
action,
|
|
1147
|
-
request,
|
|
1415
|
+
request: requestWithAbi,
|
|
1148
1416
|
gasConfig: {
|
|
1149
1417
|
gasLimit
|
|
1150
1418
|
},
|
|
@@ -1175,13 +1443,14 @@ export class SequencerPublisher {
|
|
|
1175
1443
|
* A call to `restart` is required before you can continue publishing.
|
|
1176
1444
|
*/ interrupt() {
|
|
1177
1445
|
this.interrupted = true;
|
|
1446
|
+
this.interruptibleSleep.interrupt();
|
|
1178
1447
|
this.l1TxUtils.interrupt();
|
|
1179
1448
|
}
|
|
1180
1449
|
/** Restarts the publisher after calling `interrupt`. */ restart() {
|
|
1181
1450
|
this.interrupted = false;
|
|
1182
1451
|
this.l1TxUtils.restart();
|
|
1183
1452
|
}
|
|
1184
|
-
async prepareProposeTx(encodedData,
|
|
1453
|
+
async prepareProposeTx(encodedData, simulationOverridesPlan) {
|
|
1185
1454
|
const kzg = Blob.getViemKzgInstance();
|
|
1186
1455
|
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
1187
1456
|
this.log.debug('Validating blob input', {
|
|
@@ -1208,10 +1477,38 @@ export class SequencerPublisher {
|
|
|
1208
1477
|
}, {}, {
|
|
1209
1478
|
blobs: encodedData.blobs.map((b)=>b.data),
|
|
1210
1479
|
kzg
|
|
1211
|
-
}).catch((err)=>{
|
|
1212
|
-
const
|
|
1213
|
-
this.log.error(`Failed to validate blobs`, message, {
|
|
1214
|
-
metaMessages
|
|
1480
|
+
}).catch(async (err)=>{
|
|
1481
|
+
const viemError = formatViemError(err);
|
|
1482
|
+
this.log.error(`Failed to validate blobs`, viemError.message, {
|
|
1483
|
+
metaMessages: viemError.metaMessages
|
|
1484
|
+
});
|
|
1485
|
+
const validateBlobsData = encodeFunctionData({
|
|
1486
|
+
abi: RollupAbi,
|
|
1487
|
+
functionName: 'validateBlobs',
|
|
1488
|
+
args: [
|
|
1489
|
+
blobInput
|
|
1490
|
+
]
|
|
1491
|
+
});
|
|
1492
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1493
|
+
this.backupFailedTx({
|
|
1494
|
+
id: keccak256(validateBlobsData),
|
|
1495
|
+
failureType: 'simulation',
|
|
1496
|
+
request: {
|
|
1497
|
+
to: this.rollupContract.address,
|
|
1498
|
+
data: validateBlobsData
|
|
1499
|
+
},
|
|
1500
|
+
blobData: encodedData.blobs.map((b)=>toHex(b.data)),
|
|
1501
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1502
|
+
error: {
|
|
1503
|
+
message: viemError.message,
|
|
1504
|
+
name: viemError.name
|
|
1505
|
+
},
|
|
1506
|
+
context: {
|
|
1507
|
+
actions: [
|
|
1508
|
+
'validate-blobs'
|
|
1509
|
+
],
|
|
1510
|
+
sender: this.getSenderAddress().toString()
|
|
1511
|
+
}
|
|
1215
1512
|
});
|
|
1216
1513
|
throw new Error('Failed to validate blobs');
|
|
1217
1514
|
});
|
|
@@ -1222,8 +1519,7 @@ export class SequencerPublisher {
|
|
|
1222
1519
|
header: encodedData.header.toViem(),
|
|
1223
1520
|
archive: toHex(encodedData.archive),
|
|
1224
1521
|
oracleInput: {
|
|
1225
|
-
|
|
1226
|
-
feeAssetPriceModifier: 0n
|
|
1522
|
+
feeAssetPriceModifier: encodedData.feeAssetPriceModifier
|
|
1227
1523
|
}
|
|
1228
1524
|
},
|
|
1229
1525
|
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
@@ -1231,7 +1527,7 @@ export class SequencerPublisher {
|
|
|
1231
1527
|
encodedData.attestationsAndSignersSignature.toViemSignature(),
|
|
1232
1528
|
blobInput
|
|
1233
1529
|
];
|
|
1234
|
-
const { rollupData, simulationResult } = await this.simulateProposeTx(args,
|
|
1530
|
+
const { rollupData, simulationResult } = await this.simulateProposeTx(args, simulationOverridesPlan);
|
|
1235
1531
|
return {
|
|
1236
1532
|
args,
|
|
1237
1533
|
blobEvaluationGas,
|
|
@@ -1242,29 +1538,14 @@ export class SequencerPublisher {
|
|
|
1242
1538
|
/**
|
|
1243
1539
|
* Simulates the propose tx with eth_simulateV1
|
|
1244
1540
|
* @param args - The propose tx args
|
|
1245
|
-
* @param timestamp - The timestamp to simulate proposal at
|
|
1246
1541
|
* @returns The simulation result
|
|
1247
|
-
*/ async simulateProposeTx(args,
|
|
1542
|
+
*/ async simulateProposeTx(args, simulationOverridesPlan) {
|
|
1248
1543
|
const rollupData = encodeFunctionData({
|
|
1249
1544
|
abi: RollupAbi,
|
|
1250
1545
|
functionName: 'propose',
|
|
1251
1546
|
args
|
|
1252
1547
|
});
|
|
1253
|
-
|
|
1254
|
-
const forcePendingCheckpointNumberStateDiff = (options.forcePendingCheckpointNumber !== undefined ? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber) : []).flatMap((override)=>override.stateDiff ?? []);
|
|
1255
|
-
const stateOverrides = [
|
|
1256
|
-
{
|
|
1257
|
-
address: this.rollupContract.address,
|
|
1258
|
-
// @note we override checkBlob to false since blobs are not part simulate()
|
|
1259
|
-
stateDiff: [
|
|
1260
|
-
{
|
|
1261
|
-
slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true),
|
|
1262
|
-
value: toPaddedHex(0n, true)
|
|
1263
|
-
},
|
|
1264
|
-
...forcePendingCheckpointNumberStateDiff
|
|
1265
|
-
]
|
|
1266
|
-
}
|
|
1267
|
-
];
|
|
1548
|
+
const stateOverrides = await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan);
|
|
1268
1549
|
// In fisherman mode, simulate as the proposer but with sufficient balance
|
|
1269
1550
|
if (this.proposerAddressForSimulation) {
|
|
1270
1551
|
stateOverrides.push({
|
|
@@ -1272,21 +1553,22 @@ export class SequencerPublisher {
|
|
|
1272
1553
|
balance: 10n * WEI_CONST * WEI_CONST
|
|
1273
1554
|
});
|
|
1274
1555
|
}
|
|
1556
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1557
|
+
const simTs = this.getSimulationTimestamp(SlotNumber.fromBigInt(args[0].header.slotNumber));
|
|
1275
1558
|
const simulationResult = await this.l1TxUtils.simulate({
|
|
1276
1559
|
to: this.rollupContract.address,
|
|
1277
1560
|
data: rollupData,
|
|
1278
|
-
gas:
|
|
1561
|
+
gas: MAX_L1_TX_LIMIT,
|
|
1279
1562
|
...this.proposerAddressForSimulation && {
|
|
1280
1563
|
from: this.proposerAddressForSimulation.toString()
|
|
1281
1564
|
}
|
|
1282
1565
|
}, {
|
|
1283
|
-
|
|
1284
|
-
time: timestamp + 1n,
|
|
1566
|
+
time: simTs,
|
|
1285
1567
|
// @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:
|
|
1568
|
+
gasLimit: MAX_L1_TX_LIMIT * 2n
|
|
1287
1569
|
}, stateOverrides, RollupAbi, {
|
|
1288
1570
|
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
1289
|
-
fallbackGasEstimate:
|
|
1571
|
+
fallbackGasEstimate: MAX_L1_TX_LIMIT
|
|
1290
1572
|
}).catch((err)=>{
|
|
1291
1573
|
// In fisherman mode, we expect ValidatorSelection__MissingProposerSignature since fisherman doesn't have proposer signature
|
|
1292
1574
|
const viemError = formatViemError(err);
|
|
@@ -1294,11 +1576,33 @@ export class SequencerPublisher {
|
|
|
1294
1576
|
this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
|
|
1295
1577
|
// Return a minimal simulation result with the fallback gas estimate
|
|
1296
1578
|
return {
|
|
1297
|
-
gasUsed:
|
|
1579
|
+
gasUsed: MAX_L1_TX_LIMIT,
|
|
1298
1580
|
logs: []
|
|
1299
1581
|
};
|
|
1300
1582
|
}
|
|
1301
|
-
this.log.error(`Failed to simulate propose tx`, viemError
|
|
1583
|
+
this.log.error(`Failed to simulate propose tx`, viemError, {
|
|
1584
|
+
simulationTimestamp: simTs
|
|
1585
|
+
});
|
|
1586
|
+
this.backupFailedTx({
|
|
1587
|
+
id: keccak256(rollupData),
|
|
1588
|
+
failureType: 'simulation',
|
|
1589
|
+
request: {
|
|
1590
|
+
to: this.rollupContract.address,
|
|
1591
|
+
data: rollupData
|
|
1592
|
+
},
|
|
1593
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1594
|
+
error: {
|
|
1595
|
+
message: viemError.message,
|
|
1596
|
+
name: viemError.name
|
|
1597
|
+
},
|
|
1598
|
+
context: {
|
|
1599
|
+
actions: [
|
|
1600
|
+
'propose'
|
|
1601
|
+
],
|
|
1602
|
+
slot: Number(args[0].header.slotNumber),
|
|
1603
|
+
sender: this.getSenderAddress().toString()
|
|
1604
|
+
}
|
|
1605
|
+
});
|
|
1302
1606
|
throw err;
|
|
1303
1607
|
});
|
|
1304
1608
|
return {
|
|
@@ -1306,11 +1610,11 @@ export class SequencerPublisher {
|
|
|
1306
1610
|
simulationResult
|
|
1307
1611
|
};
|
|
1308
1612
|
}
|
|
1309
|
-
async addProposeTx(checkpoint, encodedData, opts = {},
|
|
1613
|
+
async addProposeTx(checkpoint, encodedData, opts = {}, preCheck) {
|
|
1310
1614
|
const slot = checkpoint.header.slotNumber;
|
|
1311
1615
|
const timer = new Timer();
|
|
1312
1616
|
const kzg = Blob.getViemKzgInstance();
|
|
1313
|
-
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData,
|
|
1617
|
+
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, opts.simulationOverridesPlan);
|
|
1314
1618
|
const startBlock = await this.l1TxUtils.getBlockNumber();
|
|
1315
1619
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil(Number(simulationResult.gasUsed) * 64 / 63)) + blobEvaluationGas + SequencerPublisher.MULTICALL_OVERHEAD_GAS_GUESS);
|
|
1316
1620
|
// Send the blobs to the blob client preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
|
|
@@ -1326,9 +1630,10 @@ export class SequencerPublisher {
|
|
|
1326
1630
|
},
|
|
1327
1631
|
lastValidL2Slot: checkpoint.header.slotNumber,
|
|
1328
1632
|
gasConfig: {
|
|
1329
|
-
|
|
1633
|
+
txTimeoutAt: opts.txTimeoutAt,
|
|
1330
1634
|
gasLimit
|
|
1331
1635
|
},
|
|
1636
|
+
preCheck,
|
|
1332
1637
|
blobConfig: {
|
|
1333
1638
|
blobs: encodedData.blobs.map((b)=>b.data),
|
|
1334
1639
|
kzg
|
|
@@ -1375,4 +1680,13 @@ export class SequencerPublisher {
|
|
|
1375
1680
|
}
|
|
1376
1681
|
});
|
|
1377
1682
|
}
|
|
1683
|
+
/** Returns the timestamp of the last L1 slot within a given L2 slot. Used as the simulation timestamp
|
|
1684
|
+
* for eth_simulateV1 calls, since it's guaranteed to be greater than any L1 block produced during the slot. */ getSimulationTimestamp(slot) {
|
|
1685
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1686
|
+
return getLastL1SlotTimestampForL2Slot(slot, l1Constants);
|
|
1687
|
+
}
|
|
1688
|
+
/** Returns the timestamp of the next L1 slot boundary after now. */ getNextL1SlotTimestamp() {
|
|
1689
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1690
|
+
return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
|
|
1691
|
+
}
|
|
1378
1692
|
}
|