@aztec/sequencer-client 0.0.1-commit.3e3d0c9cd → 0.0.1-commit.3f5453c7b
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 +6 -13
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +32 -84
- package/dest/config.d.ts +3 -3
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +2 -9
- 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 -14
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +16 -51
- 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 +13 -1
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +17 -2
- 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 +2 -3
- package/dest/publisher/sequencer-publisher.d.ts +51 -43
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +124 -110
- 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 +26 -7
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +274 -159
- 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 +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 +20 -7
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +125 -60
- 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 +4 -8
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/package.json +27 -27
- package/src/client/sequencer-client.ts +44 -110
- package/src/config.ts +4 -8
- 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 +26 -63
- package/src/global_variable_builder/index.ts +3 -1
- package/src/publisher/config.ts +32 -0
- package/src/publisher/sequencer-publisher-factory.ts +3 -6
- package/src/publisher/sequencer-publisher.ts +191 -157
- package/src/sequencer/README.md +83 -13
- package/src/sequencer/chain_state_overrides.ts +87 -0
- package/src/sequencer/checkpoint_proposal_job.ts +353 -186
- package/src/sequencer/checkpoint_proposal_job_metrics.ts +128 -0
- package/src/sequencer/checkpoint_voter.ts +1 -12
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/metrics.ts +29 -25
- package/src/sequencer/sequencer.ts +185 -66
- package/src/sequencer/timetable.ts +57 -45
- package/src/test/mock_checkpoint_builder.ts +3 -3
|
@@ -3,16 +3,18 @@ import { Blob, getBlobsPerL1Block, getPrefixedEthBlobCommitments } from '@aztec/
|
|
|
3
3
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
4
4
|
import type { L1ContractsConfig } from '@aztec/ethereum/config';
|
|
5
5
|
import {
|
|
6
|
-
type EmpireSlashingProposerContract,
|
|
7
6
|
FeeAssetPriceOracle,
|
|
8
7
|
type GovernanceProposerContract,
|
|
9
8
|
type IEmpireBase,
|
|
10
9
|
MULTI_CALL_3_ADDRESS,
|
|
11
10
|
Multicall3,
|
|
12
11
|
RollupContract,
|
|
13
|
-
|
|
12
|
+
SimulationOverridesBuilder,
|
|
13
|
+
type SimulationOverridesPlan,
|
|
14
|
+
type SlashingProposerContract,
|
|
14
15
|
type ViemCommitteeAttestations,
|
|
15
16
|
type ViemHeader,
|
|
17
|
+
buildSimulationOverridesStateOverride,
|
|
16
18
|
} from '@aztec/ethereum/contracts';
|
|
17
19
|
import { type L1FeeAnalysisResult, L1FeeAnalyzer } from '@aztec/ethereum/l1-fee-analysis';
|
|
18
20
|
import {
|
|
@@ -26,7 +28,6 @@ import {
|
|
|
26
28
|
} from '@aztec/ethereum/l1-tx-utils';
|
|
27
29
|
import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
28
30
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
29
|
-
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
30
31
|
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
31
32
|
import { trimmedBytesLength } from '@aztec/foundation/buffer';
|
|
32
33
|
import { pick } from '@aztec/foundation/collection';
|
|
@@ -36,20 +37,20 @@ import { EthAddress } from '@aztec/foundation/eth-address';
|
|
|
36
37
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
37
38
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
38
39
|
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
40
|
+
import { InterruptibleSleep } from '@aztec/foundation/sleep';
|
|
39
41
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
40
|
-
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
42
|
+
import { type DateProvider, Timer } from '@aztec/foundation/timer';
|
|
41
43
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
42
44
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
43
45
|
import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
44
46
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
45
|
-
import {
|
|
47
|
+
import { getLastL1SlotTimestampForL2Slot, getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
46
48
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
47
49
|
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
48
50
|
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
49
51
|
|
|
50
52
|
import {
|
|
51
53
|
type Hex,
|
|
52
|
-
type StateOverride,
|
|
53
54
|
type TransactionReceipt,
|
|
54
55
|
type TypedDataDefinition,
|
|
55
56
|
encodeFunctionData,
|
|
@@ -62,6 +63,20 @@ import type { SequencerPublisherConfig } from './config.js';
|
|
|
62
63
|
import { type FailedL1Tx, type L1TxFailedStore, createL1TxFailedStore } from './l1_tx_failed_store/index.js';
|
|
63
64
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
64
65
|
|
|
66
|
+
/** Result of a sendRequests call, returned by both sendRequests() and sendRequestsAt(). */
|
|
67
|
+
export type SendRequestsResult = {
|
|
68
|
+
/** The L1 transaction receipt or error from the bundled multicall. */
|
|
69
|
+
result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError;
|
|
70
|
+
/** Actions that expired (past their deadline) before the request was sent. */
|
|
71
|
+
expiredActions: Action[];
|
|
72
|
+
/** Actions that were included in the sent L1 transaction. */
|
|
73
|
+
sentActions: Action[];
|
|
74
|
+
/** Actions whose L1 simulation succeeded (subset of sentActions). */
|
|
75
|
+
successfulActions: Action[];
|
|
76
|
+
/** Actions whose L1 simulation failed (subset of sentActions). */
|
|
77
|
+
failedActions: Action[];
|
|
78
|
+
};
|
|
79
|
+
|
|
65
80
|
/** Arguments to the process method of the rollup contract */
|
|
66
81
|
type L1ProcessArgs = {
|
|
67
82
|
/** The L2 block header. */
|
|
@@ -83,16 +98,13 @@ export const Actions = [
|
|
|
83
98
|
'invalidate-by-insufficient-attestations',
|
|
84
99
|
'propose',
|
|
85
100
|
'governance-signal',
|
|
86
|
-
'empire-slashing-signal',
|
|
87
|
-
'create-empire-payload',
|
|
88
|
-
'execute-empire-payload',
|
|
89
101
|
'vote-offenses',
|
|
90
102
|
'execute-slash',
|
|
91
103
|
] as const;
|
|
92
104
|
|
|
93
105
|
export type Action = (typeof Actions)[number];
|
|
94
106
|
|
|
95
|
-
type GovernanceSignalAction = Extract<Action, 'governance-signal'
|
|
107
|
+
type GovernanceSignalAction = Extract<Action, 'governance-signal'>;
|
|
96
108
|
|
|
97
109
|
// Sorting for actions such that invalidations go before proposals, and proposals go before votes
|
|
98
110
|
export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
|
|
@@ -103,6 +115,13 @@ export type InvalidateCheckpointRequest = {
|
|
|
103
115
|
gasUsed: bigint;
|
|
104
116
|
checkpointNumber: CheckpointNumber;
|
|
105
117
|
forcePendingCheckpointNumber: CheckpointNumber;
|
|
118
|
+
/** Archive at the rollback target checkpoint (checkpoint N-1). */
|
|
119
|
+
lastArchive: Fr;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
type EnqueueProposeCheckpointOpts = {
|
|
123
|
+
txTimeoutAt?: Date;
|
|
124
|
+
simulationOverridesPlan?: SimulationOverridesPlan;
|
|
106
125
|
};
|
|
107
126
|
|
|
108
127
|
interface RequestWithExpiry {
|
|
@@ -111,6 +130,8 @@ interface RequestWithExpiry {
|
|
|
111
130
|
lastValidL2Slot: SlotNumber;
|
|
112
131
|
gasConfig?: Pick<L1TxConfig, 'txTimeoutAt' | 'gasLimit'>;
|
|
113
132
|
blobConfig?: L1BlobInputs;
|
|
133
|
+
/** Optional pre-send validation. If it rejects, the request is discarded. */
|
|
134
|
+
preCheck?: () => Promise<void>;
|
|
114
135
|
checkSuccess: (
|
|
115
136
|
request: L1TxRequest,
|
|
116
137
|
result?: { receipt: TransactionReceipt; stats?: TransactionStats; errorMsg?: string },
|
|
@@ -133,6 +154,10 @@ export class SequencerPublisher {
|
|
|
133
154
|
|
|
134
155
|
protected log: Logger;
|
|
135
156
|
protected ethereumSlotDuration: bigint;
|
|
157
|
+
protected aztecSlotDuration: bigint;
|
|
158
|
+
|
|
159
|
+
/** Date provider for wall-clock time. */
|
|
160
|
+
private readonly dateProvider: DateProvider;
|
|
136
161
|
|
|
137
162
|
private blobClient: BlobClientInterface;
|
|
138
163
|
|
|
@@ -148,6 +173,9 @@ export class SequencerPublisher {
|
|
|
148
173
|
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */
|
|
149
174
|
private feeAssetPriceOracle: FeeAssetPriceOracle;
|
|
150
175
|
|
|
176
|
+
/** Interruptible sleep used by sendRequestsAt to wait until a target timestamp. */
|
|
177
|
+
private readonly interruptibleSleep = new InterruptibleSleep();
|
|
178
|
+
|
|
151
179
|
// A CALL to a cold address is 2700 gas
|
|
152
180
|
public static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
153
181
|
|
|
@@ -157,8 +185,7 @@ export class SequencerPublisher {
|
|
|
157
185
|
public l1TxUtils: L1TxUtils;
|
|
158
186
|
public rollupContract: RollupContract;
|
|
159
187
|
public govProposerContract: GovernanceProposerContract;
|
|
160
|
-
public slashingProposerContract:
|
|
161
|
-
public slashFactoryContract: SlashFactoryContract;
|
|
188
|
+
public slashingProposerContract: SlashingProposerContract | undefined;
|
|
162
189
|
|
|
163
190
|
public readonly tracer: Tracer;
|
|
164
191
|
|
|
@@ -166,15 +193,14 @@ export class SequencerPublisher {
|
|
|
166
193
|
|
|
167
194
|
constructor(
|
|
168
195
|
private config: Pick<SequencerPublisherConfig, 'fishermanMode' | 'l1TxFailedStore'> &
|
|
169
|
-
Pick<L1ContractsConfig, 'ethereumSlotDuration'> & { l1ChainId: number },
|
|
196
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration'> & { l1ChainId: number },
|
|
170
197
|
deps: {
|
|
171
198
|
telemetry?: TelemetryClient;
|
|
172
199
|
blobClient: BlobClientInterface;
|
|
173
200
|
l1TxUtils: L1TxUtils;
|
|
174
201
|
rollupContract: RollupContract;
|
|
175
|
-
slashingProposerContract:
|
|
202
|
+
slashingProposerContract: SlashingProposerContract | undefined;
|
|
176
203
|
governanceProposerContract: GovernanceProposerContract;
|
|
177
|
-
slashFactoryContract: SlashFactoryContract;
|
|
178
204
|
epochCache: EpochCache;
|
|
179
205
|
dateProvider: DateProvider;
|
|
180
206
|
metrics: SequencerPublisherMetrics;
|
|
@@ -185,10 +211,13 @@ export class SequencerPublisher {
|
|
|
185
211
|
) {
|
|
186
212
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
187
213
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
214
|
+
this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
|
|
215
|
+
this.dateProvider = deps.dateProvider;
|
|
188
216
|
this.epochCache = deps.epochCache;
|
|
189
217
|
this.lastActions = deps.lastActions;
|
|
190
218
|
|
|
191
219
|
this.blobClient = deps.blobClient;
|
|
220
|
+
this.dateProvider = deps.dateProvider;
|
|
192
221
|
|
|
193
222
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
194
223
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
@@ -206,8 +235,6 @@ export class SequencerPublisher {
|
|
|
206
235
|
const newSlashingProposer = await this.rollupContract.getSlashingProposer();
|
|
207
236
|
this.slashingProposerContract = newSlashingProposer;
|
|
208
237
|
});
|
|
209
|
-
this.slashFactoryContract = deps.slashFactoryContract;
|
|
210
|
-
|
|
211
238
|
// Initialize L1 fee analyzer for fisherman mode
|
|
212
239
|
if (config.fishermanMode) {
|
|
213
240
|
this.l1FeeAnalyzer = new L1FeeAnalyzer(
|
|
@@ -286,7 +313,7 @@ export class SequencerPublisher {
|
|
|
286
313
|
}
|
|
287
314
|
|
|
288
315
|
public getCurrentL2Slot(): SlotNumber {
|
|
289
|
-
return this.epochCache.
|
|
316
|
+
return this.epochCache.getSlotNow();
|
|
290
317
|
}
|
|
291
318
|
|
|
292
319
|
/**
|
|
@@ -364,9 +391,10 @@ export class SequencerPublisher {
|
|
|
364
391
|
* - undefined if no valid requests are found OR the tx failed to send.
|
|
365
392
|
*/
|
|
366
393
|
@trackSpan('SequencerPublisher.sendRequests')
|
|
367
|
-
public async sendRequests() {
|
|
394
|
+
public async sendRequests(): Promise<SendRequestsResult | undefined> {
|
|
368
395
|
const requestsToProcess = [...this.requests];
|
|
369
396
|
this.requests = [];
|
|
397
|
+
|
|
370
398
|
if (this.interrupted || requestsToProcess.length === 0) {
|
|
371
399
|
return undefined;
|
|
372
400
|
}
|
|
@@ -399,8 +427,8 @@ export class SequencerPublisher {
|
|
|
399
427
|
// @note - we can only have one blob config per bundle
|
|
400
428
|
// find requests with gas and blob configs
|
|
401
429
|
// See https://github.com/AztecProtocol/aztec-packages/issues/11513
|
|
402
|
-
const gasConfigs =
|
|
403
|
-
const blobConfigs =
|
|
430
|
+
const gasConfigs = validRequests.filter(request => request.gasConfig).map(request => request.gasConfig);
|
|
431
|
+
const blobConfigs = validRequests.filter(request => request.blobConfig).map(request => request.blobConfig);
|
|
404
432
|
|
|
405
433
|
if (blobConfigs.length > 1) {
|
|
406
434
|
throw new Error('Multiple blob configs found');
|
|
@@ -525,6 +553,45 @@ export class SequencerPublisher {
|
|
|
525
553
|
}
|
|
526
554
|
}
|
|
527
555
|
|
|
556
|
+
/*
|
|
557
|
+
* Schedules sending all enqueued requests at (or after) the given timestamp.
|
|
558
|
+
* Uses InterruptibleSleep so it can be cancelled via interrupt().
|
|
559
|
+
* Returns the promise for the L1 response (caller should NOT await this in the work loop).
|
|
560
|
+
*/
|
|
561
|
+
public async sendRequestsAt(submitAfter: Date): Promise<SendRequestsResult | undefined> {
|
|
562
|
+
const ms = submitAfter.getTime() - this.dateProvider.now();
|
|
563
|
+
if (ms > 0) {
|
|
564
|
+
this.log.debug(`Sleeping ${ms}ms before sending requests`, { submitAfter });
|
|
565
|
+
await this.interruptibleSleep.sleep(ms);
|
|
566
|
+
}
|
|
567
|
+
if (this.interrupted) {
|
|
568
|
+
return undefined;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Re-validate enqueued requests after the sleep (state may have changed, e.g. prune or L1 reorg)
|
|
572
|
+
const validRequests: RequestWithExpiry[] = [];
|
|
573
|
+
for (const request of this.requests) {
|
|
574
|
+
if (!request.preCheck) {
|
|
575
|
+
validRequests.push(request);
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
try {
|
|
580
|
+
await request.preCheck();
|
|
581
|
+
validRequests.push(request);
|
|
582
|
+
} catch (err) {
|
|
583
|
+
this.log.warn(`Pre-send validation failed for ${request.action}, discarding request`, err);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
this.requests = validRequests;
|
|
588
|
+
if (this.requests.length === 0) {
|
|
589
|
+
return undefined;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
return this.sendRequests();
|
|
593
|
+
}
|
|
594
|
+
|
|
528
595
|
private callbackBundledTransactions(
|
|
529
596
|
requests: RequestWithExpiry[],
|
|
530
597
|
result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError | undefined,
|
|
@@ -596,22 +663,25 @@ export class SequencerPublisher {
|
|
|
596
663
|
}
|
|
597
664
|
|
|
598
665
|
/**
|
|
599
|
-
* @notice Will call `
|
|
666
|
+
* @notice Will call `canProposeAt` to make sure that it is possible to propose
|
|
600
667
|
* @param tipArchive - The archive to check
|
|
601
668
|
* @returns The slot and block number if it is possible to propose, undefined otherwise
|
|
602
669
|
*/
|
|
603
|
-
public
|
|
604
|
-
tipArchive: Fr,
|
|
605
|
-
msgSender: EthAddress,
|
|
606
|
-
opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
607
|
-
) {
|
|
670
|
+
public async canProposeAt(tipArchive: Fr, msgSender: EthAddress, simulationOverridesPlan?: SimulationOverridesPlan) {
|
|
608
671
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
609
672
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
610
673
|
|
|
674
|
+
const pipelined = this.epochCache.isProposerPipeliningEnabled();
|
|
675
|
+
const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
|
|
676
|
+
const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
|
|
677
|
+
|
|
611
678
|
return this.rollupContract
|
|
612
|
-
.
|
|
613
|
-
|
|
614
|
-
|
|
679
|
+
.canProposeAt(
|
|
680
|
+
tipArchive.toBuffer(),
|
|
681
|
+
msgSender.toString(),
|
|
682
|
+
nextL1SlotTs,
|
|
683
|
+
await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan),
|
|
684
|
+
)
|
|
615
685
|
.catch(err => {
|
|
616
686
|
if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
|
|
617
687
|
this.log.warn(`Failed canProposeAtTime check with ${ignoredErrors.find(e => err.message.includes(e))}`, {
|
|
@@ -623,6 +693,7 @@ export class SequencerPublisher {
|
|
|
623
693
|
return undefined;
|
|
624
694
|
});
|
|
625
695
|
}
|
|
696
|
+
|
|
626
697
|
/**
|
|
627
698
|
* @notice Will simulate `validateHeader` to make sure that the block header is valid
|
|
628
699
|
* @dev This is a convenience function that can be used by the sequencer to validate a "partial" header.
|
|
@@ -632,7 +703,7 @@ export class SequencerPublisher {
|
|
|
632
703
|
@trackSpan('SequencerPublisher.validateBlockHeader')
|
|
633
704
|
public async validateBlockHeader(
|
|
634
705
|
header: CheckpointHeader,
|
|
635
|
-
|
|
706
|
+
simulationOverridesPlan?: SimulationOverridesPlan,
|
|
636
707
|
): Promise<void> {
|
|
637
708
|
const flags = { ignoreDA: true, ignoreSignatures: true };
|
|
638
709
|
|
|
@@ -646,10 +717,8 @@ export class SequencerPublisher {
|
|
|
646
717
|
flags,
|
|
647
718
|
] as const;
|
|
648
719
|
|
|
649
|
-
const ts =
|
|
650
|
-
const stateOverrides = await this.rollupContract
|
|
651
|
-
opts?.forcePendingCheckpointNumber,
|
|
652
|
-
);
|
|
720
|
+
const ts = this.getSimulationTimestamp(header.slotNumber);
|
|
721
|
+
const stateOverrides = await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan);
|
|
653
722
|
let balance = 0n;
|
|
654
723
|
if (this.config.fishermanMode) {
|
|
655
724
|
// In fisherman mode, we can't know where the proposer is publishing from
|
|
@@ -669,7 +738,7 @@ export class SequencerPublisher {
|
|
|
669
738
|
data: encodeFunctionData({ abi: RollupAbi, functionName: 'validateHeaderWithAttestations', args }),
|
|
670
739
|
from: MULTI_CALL_3_ADDRESS,
|
|
671
740
|
},
|
|
672
|
-
{ time: ts
|
|
741
|
+
{ time: ts },
|
|
673
742
|
stateOverrides,
|
|
674
743
|
);
|
|
675
744
|
this.log.debug(`Simulated validateHeader`);
|
|
@@ -722,6 +791,7 @@ export class SequencerPublisher {
|
|
|
722
791
|
gasUsed,
|
|
723
792
|
checkpointNumber,
|
|
724
793
|
forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
|
|
794
|
+
lastArchive: validationResult.checkpoint.lastArchive,
|
|
725
795
|
reason,
|
|
726
796
|
};
|
|
727
797
|
} catch (err) {
|
|
@@ -734,8 +804,8 @@ export class SequencerPublisher {
|
|
|
734
804
|
`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
|
|
735
805
|
{ ...logData, request, error: viemError.message },
|
|
736
806
|
);
|
|
737
|
-
const
|
|
738
|
-
if (
|
|
807
|
+
const latestProposedCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
808
|
+
if (latestProposedCheckpointNumber < checkpointNumber) {
|
|
739
809
|
this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, { ...logData });
|
|
740
810
|
return undefined;
|
|
741
811
|
} else {
|
|
@@ -809,9 +879,8 @@ export class SequencerPublisher {
|
|
|
809
879
|
checkpoint: Checkpoint,
|
|
810
880
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
811
881
|
attestationsAndSignersSignature: Signature,
|
|
812
|
-
|
|
813
|
-
): Promise<
|
|
814
|
-
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
882
|
+
simulationOverridesPlan?: SimulationOverridesPlan,
|
|
883
|
+
): Promise<void> {
|
|
815
884
|
const blobFields = checkpoint.toBlobFields();
|
|
816
885
|
const blobs = await getBlobsPerL1Block(blobFields);
|
|
817
886
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
@@ -830,13 +899,11 @@ export class SequencerPublisher {
|
|
|
830
899
|
blobInput,
|
|
831
900
|
] as const;
|
|
832
901
|
|
|
833
|
-
await this.simulateProposeTx(args,
|
|
834
|
-
return ts;
|
|
902
|
+
await this.simulateProposeTx(args, simulationOverridesPlan);
|
|
835
903
|
}
|
|
836
904
|
|
|
837
905
|
private async enqueueCastSignalHelper(
|
|
838
906
|
slotNumber: SlotNumber,
|
|
839
|
-
timestamp: bigint,
|
|
840
907
|
signalType: GovernanceSignalAction,
|
|
841
908
|
payload: EthAddress,
|
|
842
909
|
base: IEmpireBase,
|
|
@@ -915,13 +982,17 @@ export class SequencerPublisher {
|
|
|
915
982
|
});
|
|
916
983
|
|
|
917
984
|
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
985
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
918
986
|
|
|
919
987
|
try {
|
|
920
988
|
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
921
989
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
922
990
|
} catch (err) {
|
|
923
991
|
const viemError = formatViemError(err);
|
|
924
|
-
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError
|
|
992
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError, {
|
|
993
|
+
simulationTimestamp: timestamp,
|
|
994
|
+
l1BlockNumber,
|
|
995
|
+
});
|
|
925
996
|
this.backupFailedTx({
|
|
926
997
|
id: keccak256(request.data!),
|
|
927
998
|
failureType: 'simulation',
|
|
@@ -984,19 +1055,16 @@ export class SequencerPublisher {
|
|
|
984
1055
|
/**
|
|
985
1056
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
986
1057
|
* @param slotNumber - The slot number to cast a signal for.
|
|
987
|
-
* @param timestamp - The timestamp of the slot to cast a signal for.
|
|
988
1058
|
* @returns True if the signal was successfully enqueued, false otherwise.
|
|
989
1059
|
*/
|
|
990
1060
|
public enqueueGovernanceCastSignal(
|
|
991
1061
|
governancePayload: EthAddress,
|
|
992
1062
|
slotNumber: SlotNumber,
|
|
993
|
-
timestamp: bigint,
|
|
994
1063
|
signerAddress: EthAddress,
|
|
995
1064
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
996
1065
|
): Promise<boolean> {
|
|
997
1066
|
return this.enqueueCastSignalHelper(
|
|
998
1067
|
slotNumber,
|
|
999
|
-
timestamp,
|
|
1000
1068
|
'governance-signal',
|
|
1001
1069
|
governancePayload,
|
|
1002
1070
|
this.govProposerContract,
|
|
@@ -1009,7 +1077,6 @@ export class SequencerPublisher {
|
|
|
1009
1077
|
public async enqueueSlashingActions(
|
|
1010
1078
|
actions: ProposerSlashAction[],
|
|
1011
1079
|
slotNumber: SlotNumber,
|
|
1012
|
-
timestamp: bigint,
|
|
1013
1080
|
signerAddress: EthAddress,
|
|
1014
1081
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
1015
1082
|
): Promise<boolean> {
|
|
@@ -1020,58 +1087,6 @@ export class SequencerPublisher {
|
|
|
1020
1087
|
|
|
1021
1088
|
for (const action of actions) {
|
|
1022
1089
|
switch (action.type) {
|
|
1023
|
-
case 'vote-empire-payload': {
|
|
1024
|
-
if (this.slashingProposerContract?.type !== 'empire') {
|
|
1025
|
-
this.log.error('Cannot vote for empire payload on non-empire slashing contract');
|
|
1026
|
-
break;
|
|
1027
|
-
}
|
|
1028
|
-
this.log.debug(`Enqueuing slashing vote for payload ${action.payload} at slot ${slotNumber}`, {
|
|
1029
|
-
signerAddress,
|
|
1030
|
-
});
|
|
1031
|
-
await this.enqueueCastSignalHelper(
|
|
1032
|
-
slotNumber,
|
|
1033
|
-
timestamp,
|
|
1034
|
-
'empire-slashing-signal',
|
|
1035
|
-
action.payload,
|
|
1036
|
-
this.slashingProposerContract,
|
|
1037
|
-
signerAddress,
|
|
1038
|
-
signer,
|
|
1039
|
-
);
|
|
1040
|
-
break;
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
case 'create-empire-payload': {
|
|
1044
|
-
this.log.debug(`Enqueuing slashing create payload at slot ${slotNumber}`, { slotNumber, signerAddress });
|
|
1045
|
-
const request = this.slashFactoryContract.buildCreatePayloadRequest(action.data);
|
|
1046
|
-
await this.simulateAndEnqueueRequest(
|
|
1047
|
-
'create-empire-payload',
|
|
1048
|
-
request,
|
|
1049
|
-
(receipt: TransactionReceipt) =>
|
|
1050
|
-
!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs),
|
|
1051
|
-
slotNumber,
|
|
1052
|
-
timestamp,
|
|
1053
|
-
);
|
|
1054
|
-
break;
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
case 'execute-empire-payload': {
|
|
1058
|
-
this.log.debug(`Enqueuing slashing execute payload at slot ${slotNumber}`, { slotNumber, signerAddress });
|
|
1059
|
-
if (this.slashingProposerContract?.type !== 'empire') {
|
|
1060
|
-
this.log.error('Cannot execute slashing payload on non-empire slashing contract');
|
|
1061
|
-
return false;
|
|
1062
|
-
}
|
|
1063
|
-
const empireSlashingProposer = this.slashingProposerContract as EmpireSlashingProposerContract;
|
|
1064
|
-
const request = empireSlashingProposer.buildExecuteRoundRequest(action.round);
|
|
1065
|
-
await this.simulateAndEnqueueRequest(
|
|
1066
|
-
'execute-empire-payload',
|
|
1067
|
-
request,
|
|
1068
|
-
(receipt: TransactionReceipt) => !!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs),
|
|
1069
|
-
slotNumber,
|
|
1070
|
-
timestamp,
|
|
1071
|
-
);
|
|
1072
|
-
break;
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
1090
|
case 'vote-offenses': {
|
|
1076
1091
|
this.log.debug(`Enqueuing slashing vote for ${action.votes.length} votes at slot ${slotNumber}`, {
|
|
1077
1092
|
slotNumber,
|
|
@@ -1079,19 +1094,17 @@ export class SequencerPublisher {
|
|
|
1079
1094
|
votesCount: action.votes.length,
|
|
1080
1095
|
signerAddress,
|
|
1081
1096
|
});
|
|
1082
|
-
if (this.slashingProposerContract
|
|
1083
|
-
this.log.error('
|
|
1097
|
+
if (!this.slashingProposerContract) {
|
|
1098
|
+
this.log.error('No slashing proposer contract available');
|
|
1084
1099
|
return false;
|
|
1085
1100
|
}
|
|
1086
|
-
const tallySlashingProposer = this.slashingProposerContract as TallySlashingProposerContract;
|
|
1087
1101
|
const votes = bufferToHex(encodeSlashConsensusVotes(action.votes));
|
|
1088
|
-
const request = await
|
|
1102
|
+
const request = await this.slashingProposerContract.buildVoteRequestFromSigner(votes, slotNumber, signer);
|
|
1089
1103
|
await this.simulateAndEnqueueRequest(
|
|
1090
1104
|
'vote-offenses',
|
|
1091
1105
|
request,
|
|
1092
|
-
(receipt: TransactionReceipt) => !!
|
|
1106
|
+
(receipt: TransactionReceipt) => !!this.slashingProposerContract!.tryExtractVoteCastEvent(receipt.logs),
|
|
1093
1107
|
slotNumber,
|
|
1094
|
-
timestamp,
|
|
1095
1108
|
);
|
|
1096
1109
|
break;
|
|
1097
1110
|
}
|
|
@@ -1102,18 +1115,20 @@ export class SequencerPublisher {
|
|
|
1102
1115
|
round: action.round,
|
|
1103
1116
|
signerAddress,
|
|
1104
1117
|
});
|
|
1105
|
-
if (this.slashingProposerContract
|
|
1106
|
-
this.log.error('
|
|
1118
|
+
if (!this.slashingProposerContract) {
|
|
1119
|
+
this.log.error('No slashing proposer contract available');
|
|
1107
1120
|
return false;
|
|
1108
1121
|
}
|
|
1109
|
-
const
|
|
1110
|
-
|
|
1122
|
+
const executeRequest = this.slashingProposerContract.buildExecuteRoundRequest(
|
|
1123
|
+
action.round,
|
|
1124
|
+
action.committees,
|
|
1125
|
+
);
|
|
1111
1126
|
await this.simulateAndEnqueueRequest(
|
|
1112
1127
|
'execute-slash',
|
|
1113
|
-
|
|
1114
|
-
(receipt: TransactionReceipt) =>
|
|
1128
|
+
executeRequest,
|
|
1129
|
+
(receipt: TransactionReceipt) =>
|
|
1130
|
+
!!this.slashingProposerContract!.tryExtractRoundExecutedEvent(receipt.logs),
|
|
1115
1131
|
slotNumber,
|
|
1116
|
-
timestamp,
|
|
1117
1132
|
);
|
|
1118
1133
|
break;
|
|
1119
1134
|
}
|
|
@@ -1133,7 +1148,7 @@ export class SequencerPublisher {
|
|
|
1133
1148
|
checkpoint: Checkpoint,
|
|
1134
1149
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
1135
1150
|
attestationsAndSignersSignature: Signature,
|
|
1136
|
-
opts:
|
|
1151
|
+
opts: EnqueueProposeCheckpointOpts = {},
|
|
1137
1152
|
): Promise<void> {
|
|
1138
1153
|
const checkpointHeader = checkpoint.header;
|
|
1139
1154
|
|
|
@@ -1149,7 +1164,9 @@ export class SequencerPublisher {
|
|
|
1149
1164
|
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
1150
1165
|
};
|
|
1151
1166
|
|
|
1152
|
-
|
|
1167
|
+
const simulationOverridesPlan = SimulationOverridesBuilder.from(opts.simulationOverridesPlan)
|
|
1168
|
+
.withoutBlobCheck()
|
|
1169
|
+
.build();
|
|
1153
1170
|
|
|
1154
1171
|
try {
|
|
1155
1172
|
// @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
|
|
@@ -1157,23 +1174,47 @@ export class SequencerPublisher {
|
|
|
1157
1174
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
1158
1175
|
// make time consistency checks break.
|
|
1159
1176
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
1160
|
-
|
|
1177
|
+
await this.validateCheckpointForSubmission(
|
|
1161
1178
|
checkpoint,
|
|
1162
1179
|
attestationsAndSigners,
|
|
1163
1180
|
attestationsAndSignersSignature,
|
|
1164
|
-
|
|
1181
|
+
simulationOverridesPlan,
|
|
1165
1182
|
);
|
|
1166
1183
|
} catch (err: any) {
|
|
1167
1184
|
this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
1168
1185
|
...checkpoint.getStats(),
|
|
1169
1186
|
slotNumber: checkpoint.header.slotNumber,
|
|
1170
|
-
|
|
1187
|
+
simulationOverridesPlan,
|
|
1171
1188
|
});
|
|
1172
1189
|
throw err;
|
|
1173
1190
|
}
|
|
1174
1191
|
|
|
1175
|
-
|
|
1176
|
-
|
|
1192
|
+
// Build a pre-check callback that re-validates the checkpoint before L1 submission.
|
|
1193
|
+
// During pipelining this catches stale proposals due to prunes or L1 reorgs that occur during the pipeline sleep.
|
|
1194
|
+
let preCheck = undefined;
|
|
1195
|
+
if (this.epochCache.isProposerPipeliningEnabled()) {
|
|
1196
|
+
preCheck = async () => {
|
|
1197
|
+
this.log.debug(`Re-validating checkpoint ${checkpoint.number} before L1 submission`);
|
|
1198
|
+
await this.validateCheckpointForSubmission(
|
|
1199
|
+
checkpoint,
|
|
1200
|
+
attestationsAndSigners,
|
|
1201
|
+
attestationsAndSignersSignature,
|
|
1202
|
+
simulationOverridesPlan,
|
|
1203
|
+
);
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
this.log.verbose(`Enqueuing checkpoint propose transaction`, {
|
|
1208
|
+
...checkpoint.toCheckpointInfo(),
|
|
1209
|
+
txTimeoutAt: opts.txTimeoutAt,
|
|
1210
|
+
simulationOverridesPlan,
|
|
1211
|
+
});
|
|
1212
|
+
await this.addProposeTx(
|
|
1213
|
+
checkpoint,
|
|
1214
|
+
proposeTxArgs,
|
|
1215
|
+
{ txTimeoutAt: opts.txTimeoutAt, simulationOverridesPlan },
|
|
1216
|
+
preCheck,
|
|
1217
|
+
);
|
|
1177
1218
|
}
|
|
1178
1219
|
|
|
1179
1220
|
public enqueueInvalidateCheckpoint(
|
|
@@ -1216,8 +1257,8 @@ export class SequencerPublisher {
|
|
|
1216
1257
|
request: L1TxRequest,
|
|
1217
1258
|
checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
|
|
1218
1259
|
slotNumber: SlotNumber,
|
|
1219
|
-
timestamp: bigint,
|
|
1220
1260
|
) {
|
|
1261
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
1221
1262
|
const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
|
|
1222
1263
|
if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
|
|
1223
1264
|
this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
|
|
@@ -1233,8 +1274,9 @@ export class SequencerPublisher {
|
|
|
1233
1274
|
|
|
1234
1275
|
let gasUsed: bigint;
|
|
1235
1276
|
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
1277
|
+
|
|
1236
1278
|
try {
|
|
1237
|
-
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi));
|
|
1279
|
+
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi));
|
|
1238
1280
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
1239
1281
|
} catch (err) {
|
|
1240
1282
|
const viemError = formatViemError(err, simulateAbi);
|
|
@@ -1292,6 +1334,7 @@ export class SequencerPublisher {
|
|
|
1292
1334
|
*/
|
|
1293
1335
|
public interrupt() {
|
|
1294
1336
|
this.interrupted = true;
|
|
1337
|
+
this.interruptibleSleep.interrupt();
|
|
1295
1338
|
this.l1TxUtils.interrupt();
|
|
1296
1339
|
}
|
|
1297
1340
|
|
|
@@ -1301,11 +1344,7 @@ export class SequencerPublisher {
|
|
|
1301
1344
|
this.l1TxUtils.restart();
|
|
1302
1345
|
}
|
|
1303
1346
|
|
|
1304
|
-
private async prepareProposeTx(
|
|
1305
|
-
encodedData: L1ProcessArgs,
|
|
1306
|
-
timestamp: bigint,
|
|
1307
|
-
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1308
|
-
) {
|
|
1347
|
+
private async prepareProposeTx(encodedData: L1ProcessArgs, simulationOverridesPlan?: SimulationOverridesPlan) {
|
|
1309
1348
|
const kzg = Blob.getViemKzgInstance();
|
|
1310
1349
|
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
1311
1350
|
this.log.debug('Validating blob input', { blobInput });
|
|
@@ -1376,7 +1415,7 @@ export class SequencerPublisher {
|
|
|
1376
1415
|
blobInput,
|
|
1377
1416
|
] as const;
|
|
1378
1417
|
|
|
1379
|
-
const { rollupData, simulationResult } = await this.simulateProposeTx(args,
|
|
1418
|
+
const { rollupData, simulationResult } = await this.simulateProposeTx(args, simulationOverridesPlan);
|
|
1380
1419
|
|
|
1381
1420
|
return { args, blobEvaluationGas, rollupData, simulationResult };
|
|
1382
1421
|
}
|
|
@@ -1384,7 +1423,6 @@ export class SequencerPublisher {
|
|
|
1384
1423
|
/**
|
|
1385
1424
|
* Simulates the propose tx with eth_simulateV1
|
|
1386
1425
|
* @param args - The propose tx args
|
|
1387
|
-
* @param timestamp - The timestamp to simulate proposal at
|
|
1388
1426
|
* @returns The simulation result
|
|
1389
1427
|
*/
|
|
1390
1428
|
private async simulateProposeTx(
|
|
@@ -1401,8 +1439,7 @@ export class SequencerPublisher {
|
|
|
1401
1439
|
ViemSignature,
|
|
1402
1440
|
`0x${string}`,
|
|
1403
1441
|
],
|
|
1404
|
-
|
|
1405
|
-
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1442
|
+
simulationOverridesPlan?: SimulationOverridesPlan,
|
|
1406
1443
|
) {
|
|
1407
1444
|
const rollupData = encodeFunctionData({
|
|
1408
1445
|
abi: RollupAbi,
|
|
@@ -1410,23 +1447,7 @@ export class SequencerPublisher {
|
|
|
1410
1447
|
args,
|
|
1411
1448
|
});
|
|
1412
1449
|
|
|
1413
|
-
|
|
1414
|
-
const forcePendingCheckpointNumberStateDiff = (
|
|
1415
|
-
options.forcePendingCheckpointNumber !== undefined
|
|
1416
|
-
? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber)
|
|
1417
|
-
: []
|
|
1418
|
-
).flatMap(override => override.stateDiff ?? []);
|
|
1419
|
-
|
|
1420
|
-
const stateOverrides: StateOverride = [
|
|
1421
|
-
{
|
|
1422
|
-
address: this.rollupContract.address,
|
|
1423
|
-
// @note we override checkBlob to false since blobs are not part simulate()
|
|
1424
|
-
stateDiff: [
|
|
1425
|
-
{ slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
|
|
1426
|
-
...forcePendingCheckpointNumberStateDiff,
|
|
1427
|
-
],
|
|
1428
|
-
},
|
|
1429
|
-
];
|
|
1450
|
+
const stateOverrides = await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan);
|
|
1430
1451
|
// In fisherman mode, simulate as the proposer but with sufficient balance
|
|
1431
1452
|
if (this.proposerAddressForSimulation) {
|
|
1432
1453
|
stateOverrides.push({
|
|
@@ -1436,6 +1457,7 @@ export class SequencerPublisher {
|
|
|
1436
1457
|
}
|
|
1437
1458
|
|
|
1438
1459
|
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1460
|
+
const simTs = this.getSimulationTimestamp(SlotNumber.fromBigInt(args[0].header.slotNumber));
|
|
1439
1461
|
|
|
1440
1462
|
const simulationResult = await this.l1TxUtils
|
|
1441
1463
|
.simulate(
|
|
@@ -1446,8 +1468,7 @@ export class SequencerPublisher {
|
|
|
1446
1468
|
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1447
1469
|
},
|
|
1448
1470
|
{
|
|
1449
|
-
|
|
1450
|
-
time: timestamp + 1n,
|
|
1471
|
+
time: simTs,
|
|
1451
1472
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1452
1473
|
gasLimit: MAX_L1_TX_LIMIT * 2n,
|
|
1453
1474
|
},
|
|
@@ -1469,7 +1490,7 @@ export class SequencerPublisher {
|
|
|
1469
1490
|
logs: [],
|
|
1470
1491
|
};
|
|
1471
1492
|
}
|
|
1472
|
-
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1493
|
+
this.log.error(`Failed to simulate propose tx`, viemError, { simulationTimestamp: simTs });
|
|
1473
1494
|
this.backupFailedTx({
|
|
1474
1495
|
id: keccak256(rollupData),
|
|
1475
1496
|
failureType: 'simulation',
|
|
@@ -1491,16 +1512,15 @@ export class SequencerPublisher {
|
|
|
1491
1512
|
private async addProposeTx(
|
|
1492
1513
|
checkpoint: Checkpoint,
|
|
1493
1514
|
encodedData: L1ProcessArgs,
|
|
1494
|
-
opts:
|
|
1495
|
-
|
|
1515
|
+
opts: EnqueueProposeCheckpointOpts = {},
|
|
1516
|
+
preCheck?: () => Promise<void>,
|
|
1496
1517
|
): Promise<void> {
|
|
1497
1518
|
const slot = checkpoint.header.slotNumber;
|
|
1498
1519
|
const timer = new Timer();
|
|
1499
1520
|
const kzg = Blob.getViemKzgInstance();
|
|
1500
1521
|
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
1501
1522
|
encodedData,
|
|
1502
|
-
|
|
1503
|
-
opts,
|
|
1523
|
+
opts.simulationOverridesPlan,
|
|
1504
1524
|
);
|
|
1505
1525
|
const startBlock = await this.l1TxUtils.getBlockNumber();
|
|
1506
1526
|
const gasLimit = this.l1TxUtils.bumpGasLimit(
|
|
@@ -1524,7 +1544,8 @@ export class SequencerPublisher {
|
|
|
1524
1544
|
data: rollupData,
|
|
1525
1545
|
},
|
|
1526
1546
|
lastValidL2Slot: checkpoint.header.slotNumber,
|
|
1527
|
-
gasConfig: {
|
|
1547
|
+
gasConfig: { txTimeoutAt: opts.txTimeoutAt, gasLimit },
|
|
1548
|
+
preCheck,
|
|
1528
1549
|
blobConfig: {
|
|
1529
1550
|
blobs: encodedData.blobs.map(b => b.data),
|
|
1530
1551
|
kzg,
|
|
@@ -1577,4 +1598,17 @@ export class SequencerPublisher {
|
|
|
1577
1598
|
},
|
|
1578
1599
|
});
|
|
1579
1600
|
}
|
|
1601
|
+
|
|
1602
|
+
/** Returns the timestamp of the last L1 slot within a given L2 slot. Used as the simulation timestamp
|
|
1603
|
+
* for eth_simulateV1 calls, since it's guaranteed to be greater than any L1 block produced during the slot. */
|
|
1604
|
+
private getSimulationTimestamp(slot: SlotNumber): bigint {
|
|
1605
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1606
|
+
return getLastL1SlotTimestampForL2Slot(slot, l1Constants);
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
/** Returns the timestamp of the next L1 slot boundary after now. */
|
|
1610
|
+
private getNextL1SlotTimestamp(): bigint {
|
|
1611
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1612
|
+
return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
|
|
1613
|
+
}
|
|
1580
1614
|
}
|