@aztec/sequencer-client 0.0.1-commit.10bd49492 → 0.0.1-commit.11bf3dd6e
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 -12
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +30 -80
- package/dest/config.d.ts +4 -3
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +17 -13
- 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 -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 +13 -1
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +19 -4
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +3 -4
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -1
- 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 +135 -111
- 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 +291 -181
- 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 +21 -6
- 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 -4
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/package.json +27 -28
- package/src/client/sequencer-client.ts +42 -108
- package/src/config.ts +20 -12
- 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 -62
- package/src/global_variable_builder/index.ts +3 -1
- package/src/publisher/config.ts +38 -4
- package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +3 -1
- package/src/publisher/sequencer-publisher-factory.ts +3 -6
- package/src/publisher/sequencer-publisher.ts +202 -158
- package/src/sequencer/README.md +83 -13
- package/src/sequencer/chain_state_overrides.ts +87 -0
- package/src/sequencer/checkpoint_proposal_job.ts +379 -201
- 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,8 +28,8 @@ 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';
|
|
32
|
+
import { trimmedBytesLength } from '@aztec/foundation/buffer';
|
|
31
33
|
import { pick } from '@aztec/foundation/collection';
|
|
32
34
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
33
35
|
import { TimeoutError } from '@aztec/foundation/error';
|
|
@@ -35,20 +37,20 @@ import { EthAddress } from '@aztec/foundation/eth-address';
|
|
|
35
37
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
36
38
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
37
39
|
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
40
|
+
import { InterruptibleSleep } from '@aztec/foundation/sleep';
|
|
38
41
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
39
|
-
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
42
|
+
import { type DateProvider, Timer } from '@aztec/foundation/timer';
|
|
40
43
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
41
44
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
42
45
|
import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
43
46
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
44
|
-
import {
|
|
47
|
+
import { getLastL1SlotTimestampForL2Slot, getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
45
48
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
46
49
|
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
47
50
|
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
48
51
|
|
|
49
52
|
import {
|
|
50
53
|
type Hex,
|
|
51
|
-
type StateOverride,
|
|
52
54
|
type TransactionReceipt,
|
|
53
55
|
type TypedDataDefinition,
|
|
54
56
|
encodeFunctionData,
|
|
@@ -61,6 +63,20 @@ import type { SequencerPublisherConfig } from './config.js';
|
|
|
61
63
|
import { type FailedL1Tx, type L1TxFailedStore, createL1TxFailedStore } from './l1_tx_failed_store/index.js';
|
|
62
64
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
63
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
|
+
|
|
64
80
|
/** Arguments to the process method of the rollup contract */
|
|
65
81
|
type L1ProcessArgs = {
|
|
66
82
|
/** The L2 block header. */
|
|
@@ -82,16 +98,13 @@ export const Actions = [
|
|
|
82
98
|
'invalidate-by-insufficient-attestations',
|
|
83
99
|
'propose',
|
|
84
100
|
'governance-signal',
|
|
85
|
-
'empire-slashing-signal',
|
|
86
|
-
'create-empire-payload',
|
|
87
|
-
'execute-empire-payload',
|
|
88
101
|
'vote-offenses',
|
|
89
102
|
'execute-slash',
|
|
90
103
|
] as const;
|
|
91
104
|
|
|
92
105
|
export type Action = (typeof Actions)[number];
|
|
93
106
|
|
|
94
|
-
type GovernanceSignalAction = Extract<Action, 'governance-signal'
|
|
107
|
+
type GovernanceSignalAction = Extract<Action, 'governance-signal'>;
|
|
95
108
|
|
|
96
109
|
// Sorting for actions such that invalidations go before proposals, and proposals go before votes
|
|
97
110
|
export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
|
|
@@ -102,6 +115,13 @@ export type InvalidateCheckpointRequest = {
|
|
|
102
115
|
gasUsed: bigint;
|
|
103
116
|
checkpointNumber: CheckpointNumber;
|
|
104
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;
|
|
105
125
|
};
|
|
106
126
|
|
|
107
127
|
interface RequestWithExpiry {
|
|
@@ -110,6 +130,8 @@ interface RequestWithExpiry {
|
|
|
110
130
|
lastValidL2Slot: SlotNumber;
|
|
111
131
|
gasConfig?: Pick<L1TxConfig, 'txTimeoutAt' | 'gasLimit'>;
|
|
112
132
|
blobConfig?: L1BlobInputs;
|
|
133
|
+
/** Optional pre-send validation. If it rejects, the request is discarded. */
|
|
134
|
+
preCheck?: () => Promise<void>;
|
|
113
135
|
checkSuccess: (
|
|
114
136
|
request: L1TxRequest,
|
|
115
137
|
result?: { receipt: TransactionReceipt; stats?: TransactionStats; errorMsg?: string },
|
|
@@ -132,6 +154,10 @@ export class SequencerPublisher {
|
|
|
132
154
|
|
|
133
155
|
protected log: Logger;
|
|
134
156
|
protected ethereumSlotDuration: bigint;
|
|
157
|
+
protected aztecSlotDuration: bigint;
|
|
158
|
+
|
|
159
|
+
/** Date provider for wall-clock time. */
|
|
160
|
+
private readonly dateProvider: DateProvider;
|
|
135
161
|
|
|
136
162
|
private blobClient: BlobClientInterface;
|
|
137
163
|
|
|
@@ -147,6 +173,9 @@ export class SequencerPublisher {
|
|
|
147
173
|
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */
|
|
148
174
|
private feeAssetPriceOracle: FeeAssetPriceOracle;
|
|
149
175
|
|
|
176
|
+
/** Interruptible sleep used by sendRequestsAt to wait until a target timestamp. */
|
|
177
|
+
private readonly interruptibleSleep = new InterruptibleSleep();
|
|
178
|
+
|
|
150
179
|
// A CALL to a cold address is 2700 gas
|
|
151
180
|
public static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
152
181
|
|
|
@@ -156,8 +185,7 @@ export class SequencerPublisher {
|
|
|
156
185
|
public l1TxUtils: L1TxUtils;
|
|
157
186
|
public rollupContract: RollupContract;
|
|
158
187
|
public govProposerContract: GovernanceProposerContract;
|
|
159
|
-
public slashingProposerContract:
|
|
160
|
-
public slashFactoryContract: SlashFactoryContract;
|
|
188
|
+
public slashingProposerContract: SlashingProposerContract | undefined;
|
|
161
189
|
|
|
162
190
|
public readonly tracer: Tracer;
|
|
163
191
|
|
|
@@ -165,15 +193,14 @@ export class SequencerPublisher {
|
|
|
165
193
|
|
|
166
194
|
constructor(
|
|
167
195
|
private config: Pick<SequencerPublisherConfig, 'fishermanMode' | 'l1TxFailedStore'> &
|
|
168
|
-
Pick<L1ContractsConfig, 'ethereumSlotDuration'> & { l1ChainId: number },
|
|
196
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration'> & { l1ChainId: number },
|
|
169
197
|
deps: {
|
|
170
198
|
telemetry?: TelemetryClient;
|
|
171
199
|
blobClient: BlobClientInterface;
|
|
172
200
|
l1TxUtils: L1TxUtils;
|
|
173
201
|
rollupContract: RollupContract;
|
|
174
|
-
slashingProposerContract:
|
|
202
|
+
slashingProposerContract: SlashingProposerContract | undefined;
|
|
175
203
|
governanceProposerContract: GovernanceProposerContract;
|
|
176
|
-
slashFactoryContract: SlashFactoryContract;
|
|
177
204
|
epochCache: EpochCache;
|
|
178
205
|
dateProvider: DateProvider;
|
|
179
206
|
metrics: SequencerPublisherMetrics;
|
|
@@ -184,10 +211,13 @@ export class SequencerPublisher {
|
|
|
184
211
|
) {
|
|
185
212
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
186
213
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
214
|
+
this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
|
|
215
|
+
this.dateProvider = deps.dateProvider;
|
|
187
216
|
this.epochCache = deps.epochCache;
|
|
188
217
|
this.lastActions = deps.lastActions;
|
|
189
218
|
|
|
190
219
|
this.blobClient = deps.blobClient;
|
|
220
|
+
this.dateProvider = deps.dateProvider;
|
|
191
221
|
|
|
192
222
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
193
223
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
@@ -205,8 +235,6 @@ export class SequencerPublisher {
|
|
|
205
235
|
const newSlashingProposer = await this.rollupContract.getSlashingProposer();
|
|
206
236
|
this.slashingProposerContract = newSlashingProposer;
|
|
207
237
|
});
|
|
208
|
-
this.slashFactoryContract = deps.slashFactoryContract;
|
|
209
|
-
|
|
210
238
|
// Initialize L1 fee analyzer for fisherman mode
|
|
211
239
|
if (config.fishermanMode) {
|
|
212
240
|
this.l1FeeAnalyzer = new L1FeeAnalyzer(
|
|
@@ -285,7 +313,7 @@ export class SequencerPublisher {
|
|
|
285
313
|
}
|
|
286
314
|
|
|
287
315
|
public getCurrentL2Slot(): SlotNumber {
|
|
288
|
-
return this.epochCache.
|
|
316
|
+
return this.epochCache.getSlotNow();
|
|
289
317
|
}
|
|
290
318
|
|
|
291
319
|
/**
|
|
@@ -363,9 +391,10 @@ export class SequencerPublisher {
|
|
|
363
391
|
* - undefined if no valid requests are found OR the tx failed to send.
|
|
364
392
|
*/
|
|
365
393
|
@trackSpan('SequencerPublisher.sendRequests')
|
|
366
|
-
public async sendRequests() {
|
|
394
|
+
public async sendRequests(): Promise<SendRequestsResult | undefined> {
|
|
367
395
|
const requestsToProcess = [...this.requests];
|
|
368
396
|
this.requests = [];
|
|
397
|
+
|
|
369
398
|
if (this.interrupted || requestsToProcess.length === 0) {
|
|
370
399
|
return undefined;
|
|
371
400
|
}
|
|
@@ -398,8 +427,8 @@ export class SequencerPublisher {
|
|
|
398
427
|
// @note - we can only have one blob config per bundle
|
|
399
428
|
// find requests with gas and blob configs
|
|
400
429
|
// See https://github.com/AztecProtocol/aztec-packages/issues/11513
|
|
401
|
-
const gasConfigs =
|
|
402
|
-
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);
|
|
403
432
|
|
|
404
433
|
if (blobConfigs.length > 1) {
|
|
405
434
|
throw new Error('Multiple blob configs found');
|
|
@@ -524,6 +553,45 @@ export class SequencerPublisher {
|
|
|
524
553
|
}
|
|
525
554
|
}
|
|
526
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
|
+
|
|
527
595
|
private callbackBundledTransactions(
|
|
528
596
|
requests: RequestWithExpiry[],
|
|
529
597
|
result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError | undefined,
|
|
@@ -547,7 +615,16 @@ export class SequencerPublisher {
|
|
|
547
615
|
});
|
|
548
616
|
return { failedActions: requests.map(r => r.action) };
|
|
549
617
|
} else {
|
|
550
|
-
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
618
|
+
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
619
|
+
result,
|
|
620
|
+
requests: requests.map(r => ({
|
|
621
|
+
...r,
|
|
622
|
+
// Avoid logging large blob data
|
|
623
|
+
blobConfig: r.blobConfig
|
|
624
|
+
? { ...r.blobConfig, blobs: r.blobConfig.blobs.map(b => ({ size: trimmedBytesLength(b) })) }
|
|
625
|
+
: undefined,
|
|
626
|
+
})),
|
|
627
|
+
});
|
|
551
628
|
const successfulActions: Action[] = [];
|
|
552
629
|
const failedActions: Action[] = [];
|
|
553
630
|
for (const request of requests) {
|
|
@@ -586,22 +663,25 @@ export class SequencerPublisher {
|
|
|
586
663
|
}
|
|
587
664
|
|
|
588
665
|
/**
|
|
589
|
-
* @notice Will call `
|
|
666
|
+
* @notice Will call `canProposeAt` to make sure that it is possible to propose
|
|
590
667
|
* @param tipArchive - The archive to check
|
|
591
668
|
* @returns The slot and block number if it is possible to propose, undefined otherwise
|
|
592
669
|
*/
|
|
593
|
-
public
|
|
594
|
-
tipArchive: Fr,
|
|
595
|
-
msgSender: EthAddress,
|
|
596
|
-
opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
597
|
-
) {
|
|
670
|
+
public async canProposeAt(tipArchive: Fr, msgSender: EthAddress, simulationOverridesPlan?: SimulationOverridesPlan) {
|
|
598
671
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
599
672
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
600
673
|
|
|
674
|
+
const pipelined = this.epochCache.isProposerPipeliningEnabled();
|
|
675
|
+
const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
|
|
676
|
+
const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
|
|
677
|
+
|
|
601
678
|
return this.rollupContract
|
|
602
|
-
.
|
|
603
|
-
|
|
604
|
-
|
|
679
|
+
.canProposeAt(
|
|
680
|
+
tipArchive.toBuffer(),
|
|
681
|
+
msgSender.toString(),
|
|
682
|
+
nextL1SlotTs,
|
|
683
|
+
await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan),
|
|
684
|
+
)
|
|
605
685
|
.catch(err => {
|
|
606
686
|
if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
|
|
607
687
|
this.log.warn(`Failed canProposeAtTime check with ${ignoredErrors.find(e => err.message.includes(e))}`, {
|
|
@@ -613,6 +693,7 @@ export class SequencerPublisher {
|
|
|
613
693
|
return undefined;
|
|
614
694
|
});
|
|
615
695
|
}
|
|
696
|
+
|
|
616
697
|
/**
|
|
617
698
|
* @notice Will simulate `validateHeader` to make sure that the block header is valid
|
|
618
699
|
* @dev This is a convenience function that can be used by the sequencer to validate a "partial" header.
|
|
@@ -622,7 +703,7 @@ export class SequencerPublisher {
|
|
|
622
703
|
@trackSpan('SequencerPublisher.validateBlockHeader')
|
|
623
704
|
public async validateBlockHeader(
|
|
624
705
|
header: CheckpointHeader,
|
|
625
|
-
|
|
706
|
+
simulationOverridesPlan?: SimulationOverridesPlan,
|
|
626
707
|
): Promise<void> {
|
|
627
708
|
const flags = { ignoreDA: true, ignoreSignatures: true };
|
|
628
709
|
|
|
@@ -636,10 +717,8 @@ export class SequencerPublisher {
|
|
|
636
717
|
flags,
|
|
637
718
|
] as const;
|
|
638
719
|
|
|
639
|
-
const ts =
|
|
640
|
-
const stateOverrides = await this.rollupContract
|
|
641
|
-
opts?.forcePendingCheckpointNumber,
|
|
642
|
-
);
|
|
720
|
+
const ts = this.getSimulationTimestamp(header.slotNumber);
|
|
721
|
+
const stateOverrides = await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan);
|
|
643
722
|
let balance = 0n;
|
|
644
723
|
if (this.config.fishermanMode) {
|
|
645
724
|
// In fisherman mode, we can't know where the proposer is publishing from
|
|
@@ -659,7 +738,7 @@ export class SequencerPublisher {
|
|
|
659
738
|
data: encodeFunctionData({ abi: RollupAbi, functionName: 'validateHeaderWithAttestations', args }),
|
|
660
739
|
from: MULTI_CALL_3_ADDRESS,
|
|
661
740
|
},
|
|
662
|
-
{ time: ts
|
|
741
|
+
{ time: ts },
|
|
663
742
|
stateOverrides,
|
|
664
743
|
);
|
|
665
744
|
this.log.debug(`Simulated validateHeader`);
|
|
@@ -712,6 +791,7 @@ export class SequencerPublisher {
|
|
|
712
791
|
gasUsed,
|
|
713
792
|
checkpointNumber,
|
|
714
793
|
forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
|
|
794
|
+
lastArchive: validationResult.checkpoint.lastArchive,
|
|
715
795
|
reason,
|
|
716
796
|
};
|
|
717
797
|
} catch (err) {
|
|
@@ -724,8 +804,8 @@ export class SequencerPublisher {
|
|
|
724
804
|
`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
|
|
725
805
|
{ ...logData, request, error: viemError.message },
|
|
726
806
|
);
|
|
727
|
-
const
|
|
728
|
-
if (
|
|
807
|
+
const latestProposedCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
808
|
+
if (latestProposedCheckpointNumber < checkpointNumber) {
|
|
729
809
|
this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, { ...logData });
|
|
730
810
|
return undefined;
|
|
731
811
|
} else {
|
|
@@ -799,9 +879,8 @@ export class SequencerPublisher {
|
|
|
799
879
|
checkpoint: Checkpoint,
|
|
800
880
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
801
881
|
attestationsAndSignersSignature: Signature,
|
|
802
|
-
|
|
803
|
-
): Promise<
|
|
804
|
-
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
882
|
+
simulationOverridesPlan?: SimulationOverridesPlan,
|
|
883
|
+
): Promise<void> {
|
|
805
884
|
const blobFields = checkpoint.toBlobFields();
|
|
806
885
|
const blobs = await getBlobsPerL1Block(blobFields);
|
|
807
886
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
@@ -820,13 +899,11 @@ export class SequencerPublisher {
|
|
|
820
899
|
blobInput,
|
|
821
900
|
] as const;
|
|
822
901
|
|
|
823
|
-
await this.simulateProposeTx(args,
|
|
824
|
-
return ts;
|
|
902
|
+
await this.simulateProposeTx(args, simulationOverridesPlan);
|
|
825
903
|
}
|
|
826
904
|
|
|
827
905
|
private async enqueueCastSignalHelper(
|
|
828
906
|
slotNumber: SlotNumber,
|
|
829
|
-
timestamp: bigint,
|
|
830
907
|
signalType: GovernanceSignalAction,
|
|
831
908
|
payload: EthAddress,
|
|
832
909
|
base: IEmpireBase,
|
|
@@ -905,13 +982,17 @@ export class SequencerPublisher {
|
|
|
905
982
|
});
|
|
906
983
|
|
|
907
984
|
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
985
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
908
986
|
|
|
909
987
|
try {
|
|
910
988
|
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
911
989
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
912
990
|
} catch (err) {
|
|
913
991
|
const viemError = formatViemError(err);
|
|
914
|
-
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
|
+
});
|
|
915
996
|
this.backupFailedTx({
|
|
916
997
|
id: keccak256(request.data!),
|
|
917
998
|
failureType: 'simulation',
|
|
@@ -974,19 +1055,16 @@ export class SequencerPublisher {
|
|
|
974
1055
|
/**
|
|
975
1056
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
976
1057
|
* @param slotNumber - The slot number to cast a signal for.
|
|
977
|
-
* @param timestamp - The timestamp of the slot to cast a signal for.
|
|
978
1058
|
* @returns True if the signal was successfully enqueued, false otherwise.
|
|
979
1059
|
*/
|
|
980
1060
|
public enqueueGovernanceCastSignal(
|
|
981
1061
|
governancePayload: EthAddress,
|
|
982
1062
|
slotNumber: SlotNumber,
|
|
983
|
-
timestamp: bigint,
|
|
984
1063
|
signerAddress: EthAddress,
|
|
985
1064
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
986
1065
|
): Promise<boolean> {
|
|
987
1066
|
return this.enqueueCastSignalHelper(
|
|
988
1067
|
slotNumber,
|
|
989
|
-
timestamp,
|
|
990
1068
|
'governance-signal',
|
|
991
1069
|
governancePayload,
|
|
992
1070
|
this.govProposerContract,
|
|
@@ -999,7 +1077,6 @@ export class SequencerPublisher {
|
|
|
999
1077
|
public async enqueueSlashingActions(
|
|
1000
1078
|
actions: ProposerSlashAction[],
|
|
1001
1079
|
slotNumber: SlotNumber,
|
|
1002
|
-
timestamp: bigint,
|
|
1003
1080
|
signerAddress: EthAddress,
|
|
1004
1081
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
1005
1082
|
): Promise<boolean> {
|
|
@@ -1010,58 +1087,6 @@ export class SequencerPublisher {
|
|
|
1010
1087
|
|
|
1011
1088
|
for (const action of actions) {
|
|
1012
1089
|
switch (action.type) {
|
|
1013
|
-
case 'vote-empire-payload': {
|
|
1014
|
-
if (this.slashingProposerContract?.type !== 'empire') {
|
|
1015
|
-
this.log.error('Cannot vote for empire payload on non-empire slashing contract');
|
|
1016
|
-
break;
|
|
1017
|
-
}
|
|
1018
|
-
this.log.debug(`Enqueuing slashing vote for payload ${action.payload} at slot ${slotNumber}`, {
|
|
1019
|
-
signerAddress,
|
|
1020
|
-
});
|
|
1021
|
-
await this.enqueueCastSignalHelper(
|
|
1022
|
-
slotNumber,
|
|
1023
|
-
timestamp,
|
|
1024
|
-
'empire-slashing-signal',
|
|
1025
|
-
action.payload,
|
|
1026
|
-
this.slashingProposerContract,
|
|
1027
|
-
signerAddress,
|
|
1028
|
-
signer,
|
|
1029
|
-
);
|
|
1030
|
-
break;
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
case 'create-empire-payload': {
|
|
1034
|
-
this.log.debug(`Enqueuing slashing create payload at slot ${slotNumber}`, { slotNumber, signerAddress });
|
|
1035
|
-
const request = this.slashFactoryContract.buildCreatePayloadRequest(action.data);
|
|
1036
|
-
await this.simulateAndEnqueueRequest(
|
|
1037
|
-
'create-empire-payload',
|
|
1038
|
-
request,
|
|
1039
|
-
(receipt: TransactionReceipt) =>
|
|
1040
|
-
!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs),
|
|
1041
|
-
slotNumber,
|
|
1042
|
-
timestamp,
|
|
1043
|
-
);
|
|
1044
|
-
break;
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
case 'execute-empire-payload': {
|
|
1048
|
-
this.log.debug(`Enqueuing slashing execute payload at slot ${slotNumber}`, { slotNumber, signerAddress });
|
|
1049
|
-
if (this.slashingProposerContract?.type !== 'empire') {
|
|
1050
|
-
this.log.error('Cannot execute slashing payload on non-empire slashing contract');
|
|
1051
|
-
return false;
|
|
1052
|
-
}
|
|
1053
|
-
const empireSlashingProposer = this.slashingProposerContract as EmpireSlashingProposerContract;
|
|
1054
|
-
const request = empireSlashingProposer.buildExecuteRoundRequest(action.round);
|
|
1055
|
-
await this.simulateAndEnqueueRequest(
|
|
1056
|
-
'execute-empire-payload',
|
|
1057
|
-
request,
|
|
1058
|
-
(receipt: TransactionReceipt) => !!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs),
|
|
1059
|
-
slotNumber,
|
|
1060
|
-
timestamp,
|
|
1061
|
-
);
|
|
1062
|
-
break;
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
1090
|
case 'vote-offenses': {
|
|
1066
1091
|
this.log.debug(`Enqueuing slashing vote for ${action.votes.length} votes at slot ${slotNumber}`, {
|
|
1067
1092
|
slotNumber,
|
|
@@ -1069,19 +1094,17 @@ export class SequencerPublisher {
|
|
|
1069
1094
|
votesCount: action.votes.length,
|
|
1070
1095
|
signerAddress,
|
|
1071
1096
|
});
|
|
1072
|
-
if (this.slashingProposerContract
|
|
1073
|
-
this.log.error('
|
|
1097
|
+
if (!this.slashingProposerContract) {
|
|
1098
|
+
this.log.error('No slashing proposer contract available');
|
|
1074
1099
|
return false;
|
|
1075
1100
|
}
|
|
1076
|
-
const tallySlashingProposer = this.slashingProposerContract as TallySlashingProposerContract;
|
|
1077
1101
|
const votes = bufferToHex(encodeSlashConsensusVotes(action.votes));
|
|
1078
|
-
const request = await
|
|
1102
|
+
const request = await this.slashingProposerContract.buildVoteRequestFromSigner(votes, slotNumber, signer);
|
|
1079
1103
|
await this.simulateAndEnqueueRequest(
|
|
1080
1104
|
'vote-offenses',
|
|
1081
1105
|
request,
|
|
1082
|
-
(receipt: TransactionReceipt) => !!
|
|
1106
|
+
(receipt: TransactionReceipt) => !!this.slashingProposerContract!.tryExtractVoteCastEvent(receipt.logs),
|
|
1083
1107
|
slotNumber,
|
|
1084
|
-
timestamp,
|
|
1085
1108
|
);
|
|
1086
1109
|
break;
|
|
1087
1110
|
}
|
|
@@ -1092,18 +1115,20 @@ export class SequencerPublisher {
|
|
|
1092
1115
|
round: action.round,
|
|
1093
1116
|
signerAddress,
|
|
1094
1117
|
});
|
|
1095
|
-
if (this.slashingProposerContract
|
|
1096
|
-
this.log.error('
|
|
1118
|
+
if (!this.slashingProposerContract) {
|
|
1119
|
+
this.log.error('No slashing proposer contract available');
|
|
1097
1120
|
return false;
|
|
1098
1121
|
}
|
|
1099
|
-
const
|
|
1100
|
-
|
|
1122
|
+
const executeRequest = this.slashingProposerContract.buildExecuteRoundRequest(
|
|
1123
|
+
action.round,
|
|
1124
|
+
action.committees,
|
|
1125
|
+
);
|
|
1101
1126
|
await this.simulateAndEnqueueRequest(
|
|
1102
1127
|
'execute-slash',
|
|
1103
|
-
|
|
1104
|
-
(receipt: TransactionReceipt) =>
|
|
1128
|
+
executeRequest,
|
|
1129
|
+
(receipt: TransactionReceipt) =>
|
|
1130
|
+
!!this.slashingProposerContract!.tryExtractRoundExecutedEvent(receipt.logs),
|
|
1105
1131
|
slotNumber,
|
|
1106
|
-
timestamp,
|
|
1107
1132
|
);
|
|
1108
1133
|
break;
|
|
1109
1134
|
}
|
|
@@ -1123,7 +1148,7 @@ export class SequencerPublisher {
|
|
|
1123
1148
|
checkpoint: Checkpoint,
|
|
1124
1149
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
1125
1150
|
attestationsAndSignersSignature: Signature,
|
|
1126
|
-
opts:
|
|
1151
|
+
opts: EnqueueProposeCheckpointOpts = {},
|
|
1127
1152
|
): Promise<void> {
|
|
1128
1153
|
const checkpointHeader = checkpoint.header;
|
|
1129
1154
|
|
|
@@ -1139,7 +1164,9 @@ export class SequencerPublisher {
|
|
|
1139
1164
|
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
1140
1165
|
};
|
|
1141
1166
|
|
|
1142
|
-
|
|
1167
|
+
const simulationOverridesPlan = SimulationOverridesBuilder.from(opts.simulationOverridesPlan)
|
|
1168
|
+
.withoutBlobCheck()
|
|
1169
|
+
.build();
|
|
1143
1170
|
|
|
1144
1171
|
try {
|
|
1145
1172
|
// @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
|
|
@@ -1147,23 +1174,47 @@ export class SequencerPublisher {
|
|
|
1147
1174
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
1148
1175
|
// make time consistency checks break.
|
|
1149
1176
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
1150
|
-
|
|
1177
|
+
await this.validateCheckpointForSubmission(
|
|
1151
1178
|
checkpoint,
|
|
1152
1179
|
attestationsAndSigners,
|
|
1153
1180
|
attestationsAndSignersSignature,
|
|
1154
|
-
|
|
1181
|
+
simulationOverridesPlan,
|
|
1155
1182
|
);
|
|
1156
1183
|
} catch (err: any) {
|
|
1157
1184
|
this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
1158
1185
|
...checkpoint.getStats(),
|
|
1159
1186
|
slotNumber: checkpoint.header.slotNumber,
|
|
1160
|
-
|
|
1187
|
+
simulationOverridesPlan,
|
|
1161
1188
|
});
|
|
1162
1189
|
throw err;
|
|
1163
1190
|
}
|
|
1164
1191
|
|
|
1165
|
-
|
|
1166
|
-
|
|
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
|
+
);
|
|
1167
1218
|
}
|
|
1168
1219
|
|
|
1169
1220
|
public enqueueInvalidateCheckpoint(
|
|
@@ -1206,8 +1257,8 @@ export class SequencerPublisher {
|
|
|
1206
1257
|
request: L1TxRequest,
|
|
1207
1258
|
checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
|
|
1208
1259
|
slotNumber: SlotNumber,
|
|
1209
|
-
timestamp: bigint,
|
|
1210
1260
|
) {
|
|
1261
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
1211
1262
|
const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
|
|
1212
1263
|
if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
|
|
1213
1264
|
this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
|
|
@@ -1223,8 +1274,9 @@ export class SequencerPublisher {
|
|
|
1223
1274
|
|
|
1224
1275
|
let gasUsed: bigint;
|
|
1225
1276
|
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
1277
|
+
|
|
1226
1278
|
try {
|
|
1227
|
-
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi));
|
|
1279
|
+
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi));
|
|
1228
1280
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
1229
1281
|
} catch (err) {
|
|
1230
1282
|
const viemError = formatViemError(err, simulateAbi);
|
|
@@ -1282,6 +1334,7 @@ export class SequencerPublisher {
|
|
|
1282
1334
|
*/
|
|
1283
1335
|
public interrupt() {
|
|
1284
1336
|
this.interrupted = true;
|
|
1337
|
+
this.interruptibleSleep.interrupt();
|
|
1285
1338
|
this.l1TxUtils.interrupt();
|
|
1286
1339
|
}
|
|
1287
1340
|
|
|
@@ -1291,11 +1344,7 @@ export class SequencerPublisher {
|
|
|
1291
1344
|
this.l1TxUtils.restart();
|
|
1292
1345
|
}
|
|
1293
1346
|
|
|
1294
|
-
private async prepareProposeTx(
|
|
1295
|
-
encodedData: L1ProcessArgs,
|
|
1296
|
-
timestamp: bigint,
|
|
1297
|
-
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1298
|
-
) {
|
|
1347
|
+
private async prepareProposeTx(encodedData: L1ProcessArgs, simulationOverridesPlan?: SimulationOverridesPlan) {
|
|
1299
1348
|
const kzg = Blob.getViemKzgInstance();
|
|
1300
1349
|
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
1301
1350
|
this.log.debug('Validating blob input', { blobInput });
|
|
@@ -1366,7 +1415,7 @@ export class SequencerPublisher {
|
|
|
1366
1415
|
blobInput,
|
|
1367
1416
|
] as const;
|
|
1368
1417
|
|
|
1369
|
-
const { rollupData, simulationResult } = await this.simulateProposeTx(args,
|
|
1418
|
+
const { rollupData, simulationResult } = await this.simulateProposeTx(args, simulationOverridesPlan);
|
|
1370
1419
|
|
|
1371
1420
|
return { args, blobEvaluationGas, rollupData, simulationResult };
|
|
1372
1421
|
}
|
|
@@ -1374,7 +1423,6 @@ export class SequencerPublisher {
|
|
|
1374
1423
|
/**
|
|
1375
1424
|
* Simulates the propose tx with eth_simulateV1
|
|
1376
1425
|
* @param args - The propose tx args
|
|
1377
|
-
* @param timestamp - The timestamp to simulate proposal at
|
|
1378
1426
|
* @returns The simulation result
|
|
1379
1427
|
*/
|
|
1380
1428
|
private async simulateProposeTx(
|
|
@@ -1391,8 +1439,7 @@ export class SequencerPublisher {
|
|
|
1391
1439
|
ViemSignature,
|
|
1392
1440
|
`0x${string}`,
|
|
1393
1441
|
],
|
|
1394
|
-
|
|
1395
|
-
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1442
|
+
simulationOverridesPlan?: SimulationOverridesPlan,
|
|
1396
1443
|
) {
|
|
1397
1444
|
const rollupData = encodeFunctionData({
|
|
1398
1445
|
abi: RollupAbi,
|
|
@@ -1400,23 +1447,7 @@ export class SequencerPublisher {
|
|
|
1400
1447
|
args,
|
|
1401
1448
|
});
|
|
1402
1449
|
|
|
1403
|
-
|
|
1404
|
-
const forcePendingCheckpointNumberStateDiff = (
|
|
1405
|
-
options.forcePendingCheckpointNumber !== undefined
|
|
1406
|
-
? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber)
|
|
1407
|
-
: []
|
|
1408
|
-
).flatMap(override => override.stateDiff ?? []);
|
|
1409
|
-
|
|
1410
|
-
const stateOverrides: StateOverride = [
|
|
1411
|
-
{
|
|
1412
|
-
address: this.rollupContract.address,
|
|
1413
|
-
// @note we override checkBlob to false since blobs are not part simulate()
|
|
1414
|
-
stateDiff: [
|
|
1415
|
-
{ slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
|
|
1416
|
-
...forcePendingCheckpointNumberStateDiff,
|
|
1417
|
-
],
|
|
1418
|
-
},
|
|
1419
|
-
];
|
|
1450
|
+
const stateOverrides = await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan);
|
|
1420
1451
|
// In fisherman mode, simulate as the proposer but with sufficient balance
|
|
1421
1452
|
if (this.proposerAddressForSimulation) {
|
|
1422
1453
|
stateOverrides.push({
|
|
@@ -1426,6 +1457,7 @@ export class SequencerPublisher {
|
|
|
1426
1457
|
}
|
|
1427
1458
|
|
|
1428
1459
|
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1460
|
+
const simTs = this.getSimulationTimestamp(SlotNumber.fromBigInt(args[0].header.slotNumber));
|
|
1429
1461
|
|
|
1430
1462
|
const simulationResult = await this.l1TxUtils
|
|
1431
1463
|
.simulate(
|
|
@@ -1436,8 +1468,7 @@ export class SequencerPublisher {
|
|
|
1436
1468
|
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1437
1469
|
},
|
|
1438
1470
|
{
|
|
1439
|
-
|
|
1440
|
-
time: timestamp + 1n,
|
|
1471
|
+
time: simTs,
|
|
1441
1472
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1442
1473
|
gasLimit: MAX_L1_TX_LIMIT * 2n,
|
|
1443
1474
|
},
|
|
@@ -1459,7 +1490,7 @@ export class SequencerPublisher {
|
|
|
1459
1490
|
logs: [],
|
|
1460
1491
|
};
|
|
1461
1492
|
}
|
|
1462
|
-
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1493
|
+
this.log.error(`Failed to simulate propose tx`, viemError, { simulationTimestamp: simTs });
|
|
1463
1494
|
this.backupFailedTx({
|
|
1464
1495
|
id: keccak256(rollupData),
|
|
1465
1496
|
failureType: 'simulation',
|
|
@@ -1481,16 +1512,15 @@ export class SequencerPublisher {
|
|
|
1481
1512
|
private async addProposeTx(
|
|
1482
1513
|
checkpoint: Checkpoint,
|
|
1483
1514
|
encodedData: L1ProcessArgs,
|
|
1484
|
-
opts:
|
|
1485
|
-
|
|
1515
|
+
opts: EnqueueProposeCheckpointOpts = {},
|
|
1516
|
+
preCheck?: () => Promise<void>,
|
|
1486
1517
|
): Promise<void> {
|
|
1487
1518
|
const slot = checkpoint.header.slotNumber;
|
|
1488
1519
|
const timer = new Timer();
|
|
1489
1520
|
const kzg = Blob.getViemKzgInstance();
|
|
1490
1521
|
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
1491
1522
|
encodedData,
|
|
1492
|
-
|
|
1493
|
-
opts,
|
|
1523
|
+
opts.simulationOverridesPlan,
|
|
1494
1524
|
);
|
|
1495
1525
|
const startBlock = await this.l1TxUtils.getBlockNumber();
|
|
1496
1526
|
const gasLimit = this.l1TxUtils.bumpGasLimit(
|
|
@@ -1514,7 +1544,8 @@ export class SequencerPublisher {
|
|
|
1514
1544
|
data: rollupData,
|
|
1515
1545
|
},
|
|
1516
1546
|
lastValidL2Slot: checkpoint.header.slotNumber,
|
|
1517
|
-
gasConfig: {
|
|
1547
|
+
gasConfig: { txTimeoutAt: opts.txTimeoutAt, gasLimit },
|
|
1548
|
+
preCheck,
|
|
1518
1549
|
blobConfig: {
|
|
1519
1550
|
blobs: encodedData.blobs.map(b => b.data),
|
|
1520
1551
|
kzg,
|
|
@@ -1567,4 +1598,17 @@ export class SequencerPublisher {
|
|
|
1567
1598
|
},
|
|
1568
1599
|
});
|
|
1569
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
|
+
}
|
|
1570
1614
|
}
|