@aztec/sequencer-client 0.0.1-commit.8c0b8ff → 0.0.1-commit.8cb2d04d8
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 +2 -1
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +8 -8
- package/dest/config.d.ts +3 -3
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +10 -12
- 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 +3 -8
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +4 -39
- package/dest/global_variable_builder/index.d.ts +3 -1
- 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 +17 -1
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +25 -5
- 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 +3 -5
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +16 -3
- package/dest/publisher/sequencer-publisher.d.ts +52 -41
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +345 -101
- 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 +24 -7
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +277 -169
- 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/events.d.ts +2 -1
- package/dest/sequencer/events.d.ts.map +1 -1
- package/dest/sequencer/metrics.d.ts +9 -10
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +23 -20
- package/dest/sequencer/sequencer.d.ts +17 -5
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +120 -58
- package/dest/sequencer/timetable.d.ts +14 -1
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +45 -36
- package/dest/test/mock_checkpoint_builder.d.ts +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +0 -2
- package/package.json +27 -28
- package/src/client/sequencer-client.ts +8 -14
- package/src/config.ts +12 -11
- 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 +8 -43
- package/src/global_variable_builder/index.ts +2 -0
- package/src/publisher/config.ts +47 -4
- 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 +18 -6
- package/src/publisher/sequencer-publisher.ts +389 -140
- package/src/sequencer/README.md +83 -13
- package/src/sequencer/chain_state_overrides.ts +87 -0
- package/src/sequencer/checkpoint_proposal_job.ts +338 -177
- package/src/sequencer/checkpoint_proposal_job_metrics.ts +128 -0
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/metrics.ts +29 -25
- package/src/sequencer/sequencer.ts +177 -63
- package/src/sequencer/timetable.ts +57 -45
- package/src/test/mock_checkpoint_builder.ts +0 -2
|
@@ -372,19 +372,20 @@ 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 { FeeAssetPriceOracle, 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
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
|
-
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
381
380
|
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
382
381
|
import { trimmedBytesLength } from '@aztec/foundation/buffer';
|
|
383
382
|
import { pick } from '@aztec/foundation/collection';
|
|
383
|
+
import { TimeoutError } from '@aztec/foundation/error';
|
|
384
384
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
385
385
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
386
386
|
import { createLogger } from '@aztec/foundation/log';
|
|
387
387
|
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
388
|
+
import { InterruptibleSleep } from '@aztec/foundation/sleep';
|
|
388
389
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
389
390
|
import { Timer } from '@aztec/foundation/timer';
|
|
390
391
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
@@ -392,16 +393,14 @@ import { encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
|
392
393
|
import { CommitteeAttestationsAndSigners } from '@aztec/stdlib/block';
|
|
393
394
|
import { getLastL1SlotTimestampForL2Slot, getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
394
395
|
import { getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
395
|
-
import { encodeFunctionData, toHex } from 'viem';
|
|
396
|
+
import { encodeFunctionData, keccak256, multicall3Abi, toHex } from 'viem';
|
|
397
|
+
import { createL1TxFailedStore } from './l1_tx_failed_store/index.js';
|
|
396
398
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
397
399
|
export const Actions = [
|
|
398
400
|
'invalidate-by-invalid-attestation',
|
|
399
401
|
'invalidate-by-insufficient-attestations',
|
|
400
402
|
'propose',
|
|
401
403
|
'governance-signal',
|
|
402
|
-
'empire-slashing-signal',
|
|
403
|
-
'create-empire-payload',
|
|
404
|
-
'execute-empire-payload',
|
|
405
404
|
'vote-offenses',
|
|
406
405
|
'execute-slash'
|
|
407
406
|
];
|
|
@@ -432,6 +431,7 @@ export class SequencerPublisher {
|
|
|
432
431
|
interrupted;
|
|
433
432
|
metrics;
|
|
434
433
|
epochCache;
|
|
434
|
+
failedTxStore;
|
|
435
435
|
governanceLog;
|
|
436
436
|
slashingLog;
|
|
437
437
|
lastActions;
|
|
@@ -440,11 +440,13 @@ export class SequencerPublisher {
|
|
|
440
440
|
log;
|
|
441
441
|
ethereumSlotDuration;
|
|
442
442
|
aztecSlotDuration;
|
|
443
|
-
dateProvider;
|
|
443
|
+
/** Date provider for wall-clock time. */ dateProvider;
|
|
444
444
|
blobClient;
|
|
445
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;
|
|
446
447
|
/** L1 fee analyzer for fisherman mode */ l1FeeAnalyzer;
|
|
447
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;
|
|
448
450
|
// A CALL to a cold address is 2700 gas
|
|
449
451
|
static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
450
452
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
@@ -453,7 +455,6 @@ export class SequencerPublisher {
|
|
|
453
455
|
rollupContract;
|
|
454
456
|
govProposerContract;
|
|
455
457
|
slashingProposerContract;
|
|
456
|
-
slashFactoryContract;
|
|
457
458
|
tracer;
|
|
458
459
|
requests;
|
|
459
460
|
constructor(config, deps){
|
|
@@ -464,6 +465,7 @@ export class SequencerPublisher {
|
|
|
464
465
|
this.lastActions = {};
|
|
465
466
|
this.isPayloadEmptyCache = new Map();
|
|
466
467
|
this.payloadProposedCache = new Set();
|
|
468
|
+
this.interruptibleSleep = new InterruptibleSleep();
|
|
467
469
|
this.requests = [];
|
|
468
470
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
469
471
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
@@ -472,10 +474,12 @@ export class SequencerPublisher {
|
|
|
472
474
|
this.epochCache = deps.epochCache;
|
|
473
475
|
this.lastActions = deps.lastActions;
|
|
474
476
|
this.blobClient = deps.blobClient;
|
|
477
|
+
this.dateProvider = deps.dateProvider;
|
|
475
478
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
476
479
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
477
480
|
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
478
481
|
this.l1TxUtils = deps.l1TxUtils;
|
|
482
|
+
this.getNextPublisher = deps.getNextPublisher;
|
|
479
483
|
this.rollupContract = deps.rollupContract;
|
|
480
484
|
this.govProposerContract = deps.governanceProposerContract;
|
|
481
485
|
this.slashingProposerContract = deps.slashingProposerContract;
|
|
@@ -484,13 +488,30 @@ export class SequencerPublisher {
|
|
|
484
488
|
const newSlashingProposer = await this.rollupContract.getSlashingProposer();
|
|
485
489
|
this.slashingProposerContract = newSlashingProposer;
|
|
486
490
|
});
|
|
487
|
-
this.slashFactoryContract = deps.slashFactoryContract;
|
|
488
491
|
// Initialize L1 fee analyzer for fisherman mode
|
|
489
492
|
if (config.fishermanMode) {
|
|
490
493
|
this.l1FeeAnalyzer = new L1FeeAnalyzer(this.l1TxUtils.client, deps.dateProvider, createLogger('sequencer:publisher:fee-analyzer'));
|
|
491
494
|
}
|
|
492
495
|
// Initialize fee asset price oracle
|
|
493
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
|
+
});
|
|
494
515
|
}
|
|
495
516
|
getRollupContract() {
|
|
496
517
|
return this.rollupContract;
|
|
@@ -519,7 +540,7 @@ export class SequencerPublisher {
|
|
|
519
540
|
this.requests.push(request);
|
|
520
541
|
}
|
|
521
542
|
getCurrentL2Slot() {
|
|
522
|
-
return this.epochCache.
|
|
543
|
+
return this.epochCache.getSlotNow();
|
|
523
544
|
}
|
|
524
545
|
/**
|
|
525
546
|
* Clears all pending requests without sending them.
|
|
@@ -636,12 +657,34 @@ export class SequencerPublisher {
|
|
|
636
657
|
// This ensures the committee gets precomputed correctly
|
|
637
658
|
validRequests.sort((a, b)=>compareActions(a.action, b.action));
|
|
638
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
|
+
};
|
|
639
679
|
this.log.debug('Forwarding transactions', {
|
|
640
680
|
validRequests: validRequests.map((request)=>request.action),
|
|
641
681
|
txConfig
|
|
642
682
|
});
|
|
643
|
-
const result = await
|
|
644
|
-
|
|
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);
|
|
645
688
|
return {
|
|
646
689
|
result,
|
|
647
690
|
expiredActions,
|
|
@@ -661,10 +704,102 @@ export class SequencerPublisher {
|
|
|
661
704
|
}
|
|
662
705
|
}
|
|
663
706
|
}
|
|
664
|
-
|
|
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) {
|
|
665
777
|
const actionsListStr = requests.map((r)=>r.action).join(', ');
|
|
666
778
|
if (result instanceof FormattedViemError) {
|
|
667
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
|
+
});
|
|
668
803
|
return {
|
|
669
804
|
failedActions: requests.map((r)=>r.action)
|
|
670
805
|
};
|
|
@@ -691,6 +826,37 @@ export class SequencerPublisher {
|
|
|
691
826
|
failedActions.push(request.action);
|
|
692
827
|
}
|
|
693
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
|
+
}
|
|
694
860
|
return {
|
|
695
861
|
successfulActions,
|
|
696
862
|
failedActions
|
|
@@ -701,19 +867,17 @@ export class SequencerPublisher {
|
|
|
701
867
|
* @notice Will call `canProposeAt` to make sure that it is possible to propose
|
|
702
868
|
* @param tipArchive - The archive to check
|
|
703
869
|
* @returns The slot and block number if it is possible to propose, undefined otherwise
|
|
704
|
-
*/ canProposeAt(tipArchive, msgSender,
|
|
870
|
+
*/ async canProposeAt(tipArchive, msgSender, simulationOverridesPlan) {
|
|
705
871
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
706
872
|
const ignoredErrors = [
|
|
707
873
|
'SlotAlreadyInChain',
|
|
708
874
|
'InvalidProposer',
|
|
709
875
|
'InvalidArchive'
|
|
710
876
|
];
|
|
711
|
-
const pipelined =
|
|
877
|
+
const pipelined = this.epochCache.isProposerPipeliningEnabled();
|
|
712
878
|
const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
|
|
713
879
|
const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
|
|
714
|
-
return this.rollupContract.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
|
|
715
|
-
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber
|
|
716
|
-
}).catch((err)=>{
|
|
880
|
+
return this.rollupContract.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan)).catch((err)=>{
|
|
717
881
|
if (err instanceof FormattedViemError && ignoredErrors.find((e)=>err.message.includes(e))) {
|
|
718
882
|
this.log.warn(`Failed canProposeAtTime check with ${ignoredErrors.find((e)=>err.message.includes(e))}`, {
|
|
719
883
|
error: err.message
|
|
@@ -729,7 +893,7 @@ export class SequencerPublisher {
|
|
|
729
893
|
* @dev This is a convenience function that can be used by the sequencer to validate a "partial" header.
|
|
730
894
|
* It will throw if the block header is invalid.
|
|
731
895
|
* @param header - The block header to validate
|
|
732
|
-
*/ async validateBlockHeader(header,
|
|
896
|
+
*/ async validateBlockHeader(header, simulationOverridesPlan) {
|
|
733
897
|
const flags = {
|
|
734
898
|
ignoreDA: true,
|
|
735
899
|
ignoreSignatures: true
|
|
@@ -744,7 +908,7 @@ export class SequencerPublisher {
|
|
|
744
908
|
flags
|
|
745
909
|
];
|
|
746
910
|
const ts = this.getSimulationTimestamp(header.slotNumber);
|
|
747
|
-
const stateOverrides = await this.rollupContract
|
|
911
|
+
const stateOverrides = await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan);
|
|
748
912
|
let balance = 0n;
|
|
749
913
|
if (this.config.fishermanMode) {
|
|
750
914
|
// In fisherman mode, we can't know where the proposer is publishing from
|
|
@@ -796,6 +960,7 @@ export class SequencerPublisher {
|
|
|
796
960
|
...logData,
|
|
797
961
|
request
|
|
798
962
|
});
|
|
963
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
799
964
|
try {
|
|
800
965
|
const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, mergeAbis([
|
|
801
966
|
request.abi ?? [],
|
|
@@ -811,6 +976,7 @@ export class SequencerPublisher {
|
|
|
811
976
|
gasUsed,
|
|
812
977
|
checkpointNumber,
|
|
813
978
|
forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
|
|
979
|
+
lastArchive: validationResult.checkpoint.lastArchive,
|
|
814
980
|
reason
|
|
815
981
|
};
|
|
816
982
|
} catch (err) {
|
|
@@ -823,8 +989,8 @@ export class SequencerPublisher {
|
|
|
823
989
|
request,
|
|
824
990
|
error: viemError.message
|
|
825
991
|
});
|
|
826
|
-
const
|
|
827
|
-
if (
|
|
992
|
+
const latestProposedCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
993
|
+
if (latestProposedCheckpointNumber < checkpointNumber) {
|
|
828
994
|
this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, {
|
|
829
995
|
...logData
|
|
830
996
|
});
|
|
@@ -838,6 +1004,27 @@ export class SequencerPublisher {
|
|
|
838
1004
|
}
|
|
839
1005
|
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
840
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
|
+
});
|
|
841
1028
|
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, {
|
|
842
1029
|
cause: viemError
|
|
843
1030
|
});
|
|
@@ -863,7 +1050,7 @@ export class SequencerPublisher {
|
|
|
863
1050
|
throw new Error(`Unknown reason for invalidation`);
|
|
864
1051
|
}
|
|
865
1052
|
}
|
|
866
|
-
/** Simulates `propose` to make sure that the checkpoint is valid for submission */ async validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature,
|
|
1053
|
+
/** Simulates `propose` to make sure that the checkpoint is valid for submission */ async validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, simulationOverridesPlan) {
|
|
867
1054
|
const blobFields = checkpoint.toBlobFields();
|
|
868
1055
|
const blobs = await getBlobsPerL1Block(blobFields);
|
|
869
1056
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
@@ -880,7 +1067,7 @@ export class SequencerPublisher {
|
|
|
880
1067
|
attestationsAndSignersSignature.toViemSignature(),
|
|
881
1068
|
blobInput
|
|
882
1069
|
];
|
|
883
|
-
await this.simulateProposeTx(args,
|
|
1070
|
+
await this.simulateProposeTx(args, simulationOverridesPlan);
|
|
884
1071
|
}
|
|
885
1072
|
async enqueueCastSignalHelper(slotNumber, signalType, payload, base, signerAddress, signer) {
|
|
886
1073
|
if (this.lastActions[signalType] && this.lastActions[signalType] === slotNumber) {
|
|
@@ -938,6 +1125,7 @@ export class SequencerPublisher {
|
|
|
938
1125
|
signer: this.l1TxUtils.client.account?.address,
|
|
939
1126
|
lastValidL2Slot: slotNumber
|
|
940
1127
|
});
|
|
1128
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
941
1129
|
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
942
1130
|
try {
|
|
943
1131
|
await this.l1TxUtils.simulate(request, {
|
|
@@ -952,7 +1140,29 @@ export class SequencerPublisher {
|
|
|
952
1140
|
} catch (err) {
|
|
953
1141
|
const viemError = formatViemError(err);
|
|
954
1142
|
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError, {
|
|
955
|
-
simulationTimestamp: timestamp
|
|
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
|
+
}
|
|
956
1166
|
});
|
|
957
1167
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
958
1168
|
}
|
|
@@ -1008,43 +1218,6 @@ export class SequencerPublisher {
|
|
|
1008
1218
|
}
|
|
1009
1219
|
for (const action of actions){
|
|
1010
1220
|
switch(action.type){
|
|
1011
|
-
case 'vote-empire-payload':
|
|
1012
|
-
{
|
|
1013
|
-
if (this.slashingProposerContract?.type !== 'empire') {
|
|
1014
|
-
this.log.error('Cannot vote for empire payload on non-empire slashing contract');
|
|
1015
|
-
break;
|
|
1016
|
-
}
|
|
1017
|
-
this.log.debug(`Enqueuing slashing vote for payload ${action.payload} at slot ${slotNumber}`, {
|
|
1018
|
-
signerAddress
|
|
1019
|
-
});
|
|
1020
|
-
await this.enqueueCastSignalHelper(slotNumber, 'empire-slashing-signal', action.payload, this.slashingProposerContract, signerAddress, signer);
|
|
1021
|
-
break;
|
|
1022
|
-
}
|
|
1023
|
-
case 'create-empire-payload':
|
|
1024
|
-
{
|
|
1025
|
-
this.log.debug(`Enqueuing slashing create payload at slot ${slotNumber}`, {
|
|
1026
|
-
slotNumber,
|
|
1027
|
-
signerAddress
|
|
1028
|
-
});
|
|
1029
|
-
const request = this.slashFactoryContract.buildCreatePayloadRequest(action.data);
|
|
1030
|
-
await this.simulateAndEnqueueRequest('create-empire-payload', request, (receipt)=>!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs), slotNumber);
|
|
1031
|
-
break;
|
|
1032
|
-
}
|
|
1033
|
-
case 'execute-empire-payload':
|
|
1034
|
-
{
|
|
1035
|
-
this.log.debug(`Enqueuing slashing execute payload at slot ${slotNumber}`, {
|
|
1036
|
-
slotNumber,
|
|
1037
|
-
signerAddress
|
|
1038
|
-
});
|
|
1039
|
-
if (this.slashingProposerContract?.type !== 'empire') {
|
|
1040
|
-
this.log.error('Cannot execute slashing payload on non-empire slashing contract');
|
|
1041
|
-
return false;
|
|
1042
|
-
}
|
|
1043
|
-
const empireSlashingProposer = this.slashingProposerContract;
|
|
1044
|
-
const request = empireSlashingProposer.buildExecuteRoundRequest(action.round);
|
|
1045
|
-
await this.simulateAndEnqueueRequest('execute-empire-payload', request, (receipt)=>!!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs), slotNumber);
|
|
1046
|
-
break;
|
|
1047
|
-
}
|
|
1048
1221
|
case 'vote-offenses':
|
|
1049
1222
|
{
|
|
1050
1223
|
this.log.debug(`Enqueuing slashing vote for ${action.votes.length} votes at slot ${slotNumber}`, {
|
|
@@ -1053,14 +1226,13 @@ export class SequencerPublisher {
|
|
|
1053
1226
|
votesCount: action.votes.length,
|
|
1054
1227
|
signerAddress
|
|
1055
1228
|
});
|
|
1056
|
-
if (this.slashingProposerContract
|
|
1057
|
-
this.log.error('
|
|
1229
|
+
if (!this.slashingProposerContract) {
|
|
1230
|
+
this.log.error('No slashing proposer contract available');
|
|
1058
1231
|
return false;
|
|
1059
1232
|
}
|
|
1060
|
-
const tallySlashingProposer = this.slashingProposerContract;
|
|
1061
1233
|
const votes = bufferToHex(encodeSlashConsensusVotes(action.votes));
|
|
1062
|
-
const request = await
|
|
1063
|
-
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);
|
|
1064
1236
|
break;
|
|
1065
1237
|
}
|
|
1066
1238
|
case 'execute-slash':
|
|
@@ -1070,13 +1242,12 @@ export class SequencerPublisher {
|
|
|
1070
1242
|
round: action.round,
|
|
1071
1243
|
signerAddress
|
|
1072
1244
|
});
|
|
1073
|
-
if (this.slashingProposerContract
|
|
1074
|
-
this.log.error('
|
|
1245
|
+
if (!this.slashingProposerContract) {
|
|
1246
|
+
this.log.error('No slashing proposer contract available');
|
|
1075
1247
|
return false;
|
|
1076
1248
|
}
|
|
1077
|
-
const
|
|
1078
|
-
|
|
1079
|
-
await this.simulateAndEnqueueRequest('execute-slash', request, (receipt)=>!!tallySlashingProposer.tryExtractRoundExecutedEvent(receipt.logs), slotNumber);
|
|
1249
|
+
const executeRequest = this.slashingProposerContract.buildExecuteRoundRequest(action.round, action.committees);
|
|
1250
|
+
await this.simulateAndEnqueueRequest('execute-slash', executeRequest, (receipt)=>!!this.slashingProposerContract.tryExtractRoundExecutedEvent(receipt.logs), slotNumber);
|
|
1080
1251
|
break;
|
|
1081
1252
|
}
|
|
1082
1253
|
default:
|
|
@@ -1100,26 +1271,40 @@ export class SequencerPublisher {
|
|
|
1100
1271
|
attestationsAndSignersSignature,
|
|
1101
1272
|
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier
|
|
1102
1273
|
};
|
|
1274
|
+
const simulationOverridesPlan = SimulationOverridesBuilder.from(opts.simulationOverridesPlan).withoutBlobCheck().build();
|
|
1103
1275
|
try {
|
|
1104
1276
|
// @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
|
|
1105
1277
|
// This means that we can avoid the simulation issues in later checks.
|
|
1106
1278
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
1107
1279
|
// make time consistency checks break.
|
|
1108
1280
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
1109
|
-
await this.validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature,
|
|
1281
|
+
await this.validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, simulationOverridesPlan);
|
|
1110
1282
|
} catch (err) {
|
|
1111
1283
|
this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
1112
1284
|
...checkpoint.getStats(),
|
|
1113
1285
|
slotNumber: checkpoint.header.slotNumber,
|
|
1114
|
-
|
|
1286
|
+
simulationOverridesPlan
|
|
1115
1287
|
});
|
|
1116
1288
|
throw err;
|
|
1117
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
|
+
}
|
|
1118
1299
|
this.log.verbose(`Enqueuing checkpoint propose transaction`, {
|
|
1119
1300
|
...checkpoint.toCheckpointInfo(),
|
|
1120
|
-
|
|
1301
|
+
txTimeoutAt: opts.txTimeoutAt,
|
|
1302
|
+
simulationOverridesPlan
|
|
1121
1303
|
});
|
|
1122
|
-
await this.addProposeTx(checkpoint, proposeTxArgs,
|
|
1304
|
+
await this.addProposeTx(checkpoint, proposeTxArgs, {
|
|
1305
|
+
txTimeoutAt: opts.txTimeoutAt,
|
|
1306
|
+
simulationOverridesPlan
|
|
1307
|
+
}, preCheck);
|
|
1123
1308
|
}
|
|
1124
1309
|
enqueueInvalidateCheckpoint(request, opts = {}) {
|
|
1125
1310
|
if (!request) {
|
|
@@ -1174,6 +1359,7 @@ export class SequencerPublisher {
|
|
|
1174
1359
|
const cachedLastActionSlot = this.lastActions[action];
|
|
1175
1360
|
this.lastActions[action] = slotNumber;
|
|
1176
1361
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
1362
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1177
1363
|
let gasUsed;
|
|
1178
1364
|
const simulateAbi = mergeAbis([
|
|
1179
1365
|
request.abi ?? [],
|
|
@@ -1191,6 +1377,27 @@ export class SequencerPublisher {
|
|
|
1191
1377
|
} catch (err) {
|
|
1192
1378
|
const viemError = formatViemError(err, simulateAbi);
|
|
1193
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
|
+
});
|
|
1194
1401
|
return false;
|
|
1195
1402
|
}
|
|
1196
1403
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
@@ -1236,13 +1443,14 @@ export class SequencerPublisher {
|
|
|
1236
1443
|
* A call to `restart` is required before you can continue publishing.
|
|
1237
1444
|
*/ interrupt() {
|
|
1238
1445
|
this.interrupted = true;
|
|
1446
|
+
this.interruptibleSleep.interrupt();
|
|
1239
1447
|
this.l1TxUtils.interrupt();
|
|
1240
1448
|
}
|
|
1241
1449
|
/** Restarts the publisher after calling `interrupt`. */ restart() {
|
|
1242
1450
|
this.interrupted = false;
|
|
1243
1451
|
this.l1TxUtils.restart();
|
|
1244
1452
|
}
|
|
1245
|
-
async prepareProposeTx(encodedData,
|
|
1453
|
+
async prepareProposeTx(encodedData, simulationOverridesPlan) {
|
|
1246
1454
|
const kzg = Blob.getViemKzgInstance();
|
|
1247
1455
|
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
1248
1456
|
this.log.debug('Validating blob input', {
|
|
@@ -1269,10 +1477,38 @@ export class SequencerPublisher {
|
|
|
1269
1477
|
}, {}, {
|
|
1270
1478
|
blobs: encodedData.blobs.map((b)=>b.data),
|
|
1271
1479
|
kzg
|
|
1272
|
-
}).catch((err)=>{
|
|
1273
|
-
const
|
|
1274
|
-
this.log.error(`Failed to validate blobs`, message, {
|
|
1275
|
-
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
|
+
}
|
|
1276
1512
|
});
|
|
1277
1513
|
throw new Error('Failed to validate blobs');
|
|
1278
1514
|
});
|
|
@@ -1291,7 +1527,7 @@ export class SequencerPublisher {
|
|
|
1291
1527
|
encodedData.attestationsAndSignersSignature.toViemSignature(),
|
|
1292
1528
|
blobInput
|
|
1293
1529
|
];
|
|
1294
|
-
const { rollupData, simulationResult } = await this.simulateProposeTx(args,
|
|
1530
|
+
const { rollupData, simulationResult } = await this.simulateProposeTx(args, simulationOverridesPlan);
|
|
1295
1531
|
return {
|
|
1296
1532
|
args,
|
|
1297
1533
|
blobEvaluationGas,
|
|
@@ -1303,27 +1539,13 @@ export class SequencerPublisher {
|
|
|
1303
1539
|
* Simulates the propose tx with eth_simulateV1
|
|
1304
1540
|
* @param args - The propose tx args
|
|
1305
1541
|
* @returns The simulation result
|
|
1306
|
-
*/ async simulateProposeTx(args,
|
|
1542
|
+
*/ async simulateProposeTx(args, simulationOverridesPlan) {
|
|
1307
1543
|
const rollupData = encodeFunctionData({
|
|
1308
1544
|
abi: RollupAbi,
|
|
1309
1545
|
functionName: 'propose',
|
|
1310
1546
|
args
|
|
1311
1547
|
});
|
|
1312
|
-
|
|
1313
|
-
const forcePendingCheckpointNumberStateDiff = (options.forcePendingCheckpointNumber !== undefined ? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber) : []).flatMap((override)=>override.stateDiff ?? []);
|
|
1314
|
-
const stateOverrides = [
|
|
1315
|
-
{
|
|
1316
|
-
address: this.rollupContract.address,
|
|
1317
|
-
// @note we override checkBlob to false since blobs are not part simulate()
|
|
1318
|
-
stateDiff: [
|
|
1319
|
-
{
|
|
1320
|
-
slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true),
|
|
1321
|
-
value: toPaddedHex(0n, true)
|
|
1322
|
-
},
|
|
1323
|
-
...forcePendingCheckpointNumberStateDiff
|
|
1324
|
-
]
|
|
1325
|
-
}
|
|
1326
|
-
];
|
|
1548
|
+
const stateOverrides = await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan);
|
|
1327
1549
|
// In fisherman mode, simulate as the proposer but with sufficient balance
|
|
1328
1550
|
if (this.proposerAddressForSimulation) {
|
|
1329
1551
|
stateOverrides.push({
|
|
@@ -1331,6 +1553,7 @@ export class SequencerPublisher {
|
|
|
1331
1553
|
balance: 10n * WEI_CONST * WEI_CONST
|
|
1332
1554
|
});
|
|
1333
1555
|
}
|
|
1556
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1334
1557
|
const simTs = this.getSimulationTimestamp(SlotNumber.fromBigInt(args[0].header.slotNumber));
|
|
1335
1558
|
const simulationResult = await this.l1TxUtils.simulate({
|
|
1336
1559
|
to: this.rollupContract.address,
|
|
@@ -1360,6 +1583,26 @@ export class SequencerPublisher {
|
|
|
1360
1583
|
this.log.error(`Failed to simulate propose tx`, viemError, {
|
|
1361
1584
|
simulationTimestamp: simTs
|
|
1362
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
|
+
});
|
|
1363
1606
|
throw err;
|
|
1364
1607
|
});
|
|
1365
1608
|
return {
|
|
@@ -1367,11 +1610,11 @@ export class SequencerPublisher {
|
|
|
1367
1610
|
simulationResult
|
|
1368
1611
|
};
|
|
1369
1612
|
}
|
|
1370
|
-
async addProposeTx(checkpoint, encodedData, opts = {}) {
|
|
1613
|
+
async addProposeTx(checkpoint, encodedData, opts = {}, preCheck) {
|
|
1371
1614
|
const slot = checkpoint.header.slotNumber;
|
|
1372
1615
|
const timer = new Timer();
|
|
1373
1616
|
const kzg = Blob.getViemKzgInstance();
|
|
1374
|
-
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, opts);
|
|
1617
|
+
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, opts.simulationOverridesPlan);
|
|
1375
1618
|
const startBlock = await this.l1TxUtils.getBlockNumber();
|
|
1376
1619
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil(Number(simulationResult.gasUsed) * 64 / 63)) + blobEvaluationGas + SequencerPublisher.MULTICALL_OVERHEAD_GAS_GUESS);
|
|
1377
1620
|
// Send the blobs to the blob client preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
|
|
@@ -1387,9 +1630,10 @@ export class SequencerPublisher {
|
|
|
1387
1630
|
},
|
|
1388
1631
|
lastValidL2Slot: checkpoint.header.slotNumber,
|
|
1389
1632
|
gasConfig: {
|
|
1390
|
-
|
|
1633
|
+
txTimeoutAt: opts.txTimeoutAt,
|
|
1391
1634
|
gasLimit
|
|
1392
1635
|
},
|
|
1636
|
+
preCheck,
|
|
1393
1637
|
blobConfig: {
|
|
1394
1638
|
blobs: encodedData.blobs.map((b)=>b.data),
|
|
1395
1639
|
kzg
|