@aztec/sequencer-client 0.0.1-commit.f504929 → 0.0.1-commit.f650c0a5c
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 +4 -1
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +46 -27
- package/dest/config.d.ts +25 -5
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +21 -12
- package/dest/global_variable_builder/global_builder.d.ts +15 -9
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +29 -25
- package/dest/global_variable_builder/index.d.ts +2 -2
- package/dest/global_variable_builder/index.d.ts.map +1 -1
- 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 +58 -32
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +132 -89
- package/dest/sequencer/checkpoint_proposal_job.d.ts +36 -9
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +302 -180
- 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 +5 -1
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +11 -0
- package/dest/sequencer/sequencer.d.ts +23 -10
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +130 -71
- package/dest/sequencer/timetable.d.ts +7 -3
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +21 -12
- package/dest/sequencer/types.d.ts +2 -2
- package/dest/sequencer/types.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts +7 -9
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +39 -30
- package/package.json +27 -28
- package/src/client/sequencer-client.ts +56 -28
- package/src/config.ts +28 -14
- package/src/global_variable_builder/global_builder.ts +37 -26
- package/src/global_variable_builder/index.ts +1 -1
- package/src/publisher/config.ts +32 -0
- package/src/publisher/sequencer-publisher-factory.ts +3 -6
- package/src/publisher/sequencer-publisher.ts +205 -127
- package/src/sequencer/README.md +81 -12
- package/src/sequencer/checkpoint_proposal_job.ts +385 -199
- package/src/sequencer/checkpoint_voter.ts +1 -12
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/metrics.ts +14 -0
- package/src/sequencer/sequencer.ts +186 -80
- package/src/sequencer/timetable.ts +26 -15
- package/src/sequencer/types.ts +1 -1
- package/src/test/mock_checkpoint_builder.ts +51 -48
|
@@ -3,14 +3,14 @@ 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,
|
|
7
|
+
type FeeHeader,
|
|
8
8
|
type GovernanceProposerContract,
|
|
9
9
|
type IEmpireBase,
|
|
10
10
|
MULTI_CALL_3_ADDRESS,
|
|
11
11
|
Multicall3,
|
|
12
12
|
RollupContract,
|
|
13
|
-
type
|
|
13
|
+
type SlashingProposerContract,
|
|
14
14
|
type ViemCommitteeAttestations,
|
|
15
15
|
type ViemHeader,
|
|
16
16
|
} from '@aztec/ethereum/contracts';
|
|
@@ -28,6 +28,7 @@ import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from
|
|
|
28
28
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
29
29
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
30
30
|
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
31
|
+
import { trimmedBytesLength } from '@aztec/foundation/buffer';
|
|
31
32
|
import { pick } from '@aztec/foundation/collection';
|
|
32
33
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
33
34
|
import { TimeoutError } from '@aztec/foundation/error';
|
|
@@ -35,13 +36,14 @@ import { EthAddress } from '@aztec/foundation/eth-address';
|
|
|
35
36
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
36
37
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
37
38
|
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
39
|
+
import { InterruptibleSleep } from '@aztec/foundation/sleep';
|
|
38
40
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
39
|
-
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
41
|
+
import { type DateProvider, Timer } from '@aztec/foundation/timer';
|
|
40
42
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
41
43
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
42
44
|
import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
43
45
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
44
|
-
import {
|
|
46
|
+
import { getLastL1SlotTimestampForL2Slot, getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
45
47
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
46
48
|
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
47
49
|
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
@@ -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,8 @@ 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;
|
|
105
120
|
};
|
|
106
121
|
|
|
107
122
|
interface RequestWithExpiry {
|
|
@@ -110,6 +125,8 @@ interface RequestWithExpiry {
|
|
|
110
125
|
lastValidL2Slot: SlotNumber;
|
|
111
126
|
gasConfig?: Pick<L1TxConfig, 'txTimeoutAt' | 'gasLimit'>;
|
|
112
127
|
blobConfig?: L1BlobInputs;
|
|
128
|
+
/** Optional pre-send validation. If it rejects, the request is discarded. */
|
|
129
|
+
preCheck?: () => Promise<void>;
|
|
113
130
|
checkSuccess: (
|
|
114
131
|
request: L1TxRequest,
|
|
115
132
|
result?: { receipt: TransactionReceipt; stats?: TransactionStats; errorMsg?: string },
|
|
@@ -132,6 +149,10 @@ export class SequencerPublisher {
|
|
|
132
149
|
|
|
133
150
|
protected log: Logger;
|
|
134
151
|
protected ethereumSlotDuration: bigint;
|
|
152
|
+
protected aztecSlotDuration: bigint;
|
|
153
|
+
|
|
154
|
+
/** Date provider for wall-clock time. */
|
|
155
|
+
private readonly dateProvider: DateProvider;
|
|
135
156
|
|
|
136
157
|
private blobClient: BlobClientInterface;
|
|
137
158
|
|
|
@@ -147,6 +168,9 @@ export class SequencerPublisher {
|
|
|
147
168
|
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */
|
|
148
169
|
private feeAssetPriceOracle: FeeAssetPriceOracle;
|
|
149
170
|
|
|
171
|
+
/** Interruptible sleep used by sendRequestsAt to wait until a target timestamp. */
|
|
172
|
+
private readonly interruptibleSleep = new InterruptibleSleep();
|
|
173
|
+
|
|
150
174
|
// A CALL to a cold address is 2700 gas
|
|
151
175
|
public static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
152
176
|
|
|
@@ -156,8 +180,7 @@ export class SequencerPublisher {
|
|
|
156
180
|
public l1TxUtils: L1TxUtils;
|
|
157
181
|
public rollupContract: RollupContract;
|
|
158
182
|
public govProposerContract: GovernanceProposerContract;
|
|
159
|
-
public slashingProposerContract:
|
|
160
|
-
public slashFactoryContract: SlashFactoryContract;
|
|
183
|
+
public slashingProposerContract: SlashingProposerContract | undefined;
|
|
161
184
|
|
|
162
185
|
public readonly tracer: Tracer;
|
|
163
186
|
|
|
@@ -165,15 +188,14 @@ export class SequencerPublisher {
|
|
|
165
188
|
|
|
166
189
|
constructor(
|
|
167
190
|
private config: Pick<SequencerPublisherConfig, 'fishermanMode' | 'l1TxFailedStore'> &
|
|
168
|
-
Pick<L1ContractsConfig, 'ethereumSlotDuration'> & { l1ChainId: number },
|
|
191
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration'> & { l1ChainId: number },
|
|
169
192
|
deps: {
|
|
170
193
|
telemetry?: TelemetryClient;
|
|
171
194
|
blobClient: BlobClientInterface;
|
|
172
195
|
l1TxUtils: L1TxUtils;
|
|
173
196
|
rollupContract: RollupContract;
|
|
174
|
-
slashingProposerContract:
|
|
197
|
+
slashingProposerContract: SlashingProposerContract | undefined;
|
|
175
198
|
governanceProposerContract: GovernanceProposerContract;
|
|
176
|
-
slashFactoryContract: SlashFactoryContract;
|
|
177
199
|
epochCache: EpochCache;
|
|
178
200
|
dateProvider: DateProvider;
|
|
179
201
|
metrics: SequencerPublisherMetrics;
|
|
@@ -184,10 +206,13 @@ export class SequencerPublisher {
|
|
|
184
206
|
) {
|
|
185
207
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
186
208
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
209
|
+
this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
|
|
210
|
+
this.dateProvider = deps.dateProvider;
|
|
187
211
|
this.epochCache = deps.epochCache;
|
|
188
212
|
this.lastActions = deps.lastActions;
|
|
189
213
|
|
|
190
214
|
this.blobClient = deps.blobClient;
|
|
215
|
+
this.dateProvider = deps.dateProvider;
|
|
191
216
|
|
|
192
217
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
193
218
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
@@ -205,8 +230,6 @@ export class SequencerPublisher {
|
|
|
205
230
|
const newSlashingProposer = await this.rollupContract.getSlashingProposer();
|
|
206
231
|
this.slashingProposerContract = newSlashingProposer;
|
|
207
232
|
});
|
|
208
|
-
this.slashFactoryContract = deps.slashFactoryContract;
|
|
209
|
-
|
|
210
233
|
// Initialize L1 fee analyzer for fisherman mode
|
|
211
234
|
if (config.fishermanMode) {
|
|
212
235
|
this.l1FeeAnalyzer = new L1FeeAnalyzer(
|
|
@@ -285,7 +308,7 @@ export class SequencerPublisher {
|
|
|
285
308
|
}
|
|
286
309
|
|
|
287
310
|
public getCurrentL2Slot(): SlotNumber {
|
|
288
|
-
return this.epochCache.
|
|
311
|
+
return this.epochCache.getSlotNow();
|
|
289
312
|
}
|
|
290
313
|
|
|
291
314
|
/**
|
|
@@ -363,9 +386,10 @@ export class SequencerPublisher {
|
|
|
363
386
|
* - undefined if no valid requests are found OR the tx failed to send.
|
|
364
387
|
*/
|
|
365
388
|
@trackSpan('SequencerPublisher.sendRequests')
|
|
366
|
-
public async sendRequests() {
|
|
389
|
+
public async sendRequests(): Promise<SendRequestsResult | undefined> {
|
|
367
390
|
const requestsToProcess = [...this.requests];
|
|
368
391
|
this.requests = [];
|
|
392
|
+
|
|
369
393
|
if (this.interrupted || requestsToProcess.length === 0) {
|
|
370
394
|
return undefined;
|
|
371
395
|
}
|
|
@@ -398,8 +422,8 @@ export class SequencerPublisher {
|
|
|
398
422
|
// @note - we can only have one blob config per bundle
|
|
399
423
|
// find requests with gas and blob configs
|
|
400
424
|
// See https://github.com/AztecProtocol/aztec-packages/issues/11513
|
|
401
|
-
const gasConfigs =
|
|
402
|
-
const blobConfigs =
|
|
425
|
+
const gasConfigs = validRequests.filter(request => request.gasConfig).map(request => request.gasConfig);
|
|
426
|
+
const blobConfigs = validRequests.filter(request => request.blobConfig).map(request => request.blobConfig);
|
|
403
427
|
|
|
404
428
|
if (blobConfigs.length > 1) {
|
|
405
429
|
throw new Error('Multiple blob configs found');
|
|
@@ -524,6 +548,45 @@ export class SequencerPublisher {
|
|
|
524
548
|
}
|
|
525
549
|
}
|
|
526
550
|
|
|
551
|
+
/*
|
|
552
|
+
* Schedules sending all enqueued requests at (or after) the given timestamp.
|
|
553
|
+
* Uses InterruptibleSleep so it can be cancelled via interrupt().
|
|
554
|
+
* Returns the promise for the L1 response (caller should NOT await this in the work loop).
|
|
555
|
+
*/
|
|
556
|
+
public async sendRequestsAt(submitAfter: Date): Promise<SendRequestsResult | undefined> {
|
|
557
|
+
const ms = submitAfter.getTime() - this.dateProvider.now();
|
|
558
|
+
if (ms > 0) {
|
|
559
|
+
this.log.debug(`Sleeping ${ms}ms before sending requests`, { submitAfter });
|
|
560
|
+
await this.interruptibleSleep.sleep(ms);
|
|
561
|
+
}
|
|
562
|
+
if (this.interrupted) {
|
|
563
|
+
return undefined;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Re-validate enqueued requests after the sleep (state may have changed, e.g. prune or L1 reorg)
|
|
567
|
+
const validRequests: RequestWithExpiry[] = [];
|
|
568
|
+
for (const request of this.requests) {
|
|
569
|
+
if (!request.preCheck) {
|
|
570
|
+
validRequests.push(request);
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
try {
|
|
575
|
+
await request.preCheck();
|
|
576
|
+
validRequests.push(request);
|
|
577
|
+
} catch (err) {
|
|
578
|
+
this.log.warn(`Pre-send validation failed for ${request.action}, discarding request`, err);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
this.requests = validRequests;
|
|
583
|
+
if (this.requests.length === 0) {
|
|
584
|
+
return undefined;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return this.sendRequests();
|
|
588
|
+
}
|
|
589
|
+
|
|
527
590
|
private callbackBundledTransactions(
|
|
528
591
|
requests: RequestWithExpiry[],
|
|
529
592
|
result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError | undefined,
|
|
@@ -547,7 +610,16 @@ export class SequencerPublisher {
|
|
|
547
610
|
});
|
|
548
611
|
return { failedActions: requests.map(r => r.action) };
|
|
549
612
|
} else {
|
|
550
|
-
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
613
|
+
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
614
|
+
result,
|
|
615
|
+
requests: requests.map(r => ({
|
|
616
|
+
...r,
|
|
617
|
+
// Avoid logging large blob data
|
|
618
|
+
blobConfig: r.blobConfig
|
|
619
|
+
? { ...r.blobConfig, blobs: r.blobConfig.blobs.map(b => ({ size: trimmedBytesLength(b) })) }
|
|
620
|
+
: undefined,
|
|
621
|
+
})),
|
|
622
|
+
});
|
|
551
623
|
const successfulActions: Action[] = [];
|
|
552
624
|
const failedActions: Action[] = [];
|
|
553
625
|
for (const request of requests) {
|
|
@@ -586,21 +658,30 @@ export class SequencerPublisher {
|
|
|
586
658
|
}
|
|
587
659
|
|
|
588
660
|
/**
|
|
589
|
-
* @notice Will call `
|
|
661
|
+
* @notice Will call `canProposeAt` to make sure that it is possible to propose
|
|
590
662
|
* @param tipArchive - The archive to check
|
|
591
663
|
* @returns The slot and block number if it is possible to propose, undefined otherwise
|
|
592
664
|
*/
|
|
593
|
-
public
|
|
665
|
+
public canProposeAt(
|
|
594
666
|
tipArchive: Fr,
|
|
595
667
|
msgSender: EthAddress,
|
|
596
|
-
opts: {
|
|
668
|
+
opts: {
|
|
669
|
+
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
670
|
+
forceArchive?: { checkpointNumber: CheckpointNumber; archive: Fr };
|
|
671
|
+
pipelined?: boolean;
|
|
672
|
+
} = {},
|
|
597
673
|
) {
|
|
598
674
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
599
675
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
600
676
|
|
|
677
|
+
const pipelined = opts.pipelined ?? this.epochCache.isProposerPipeliningEnabled();
|
|
678
|
+
const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
|
|
679
|
+
const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
|
|
680
|
+
|
|
601
681
|
return this.rollupContract
|
|
602
|
-
.
|
|
682
|
+
.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
|
|
603
683
|
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
684
|
+
forceArchive: opts.forceArchive,
|
|
604
685
|
})
|
|
605
686
|
.catch(err => {
|
|
606
687
|
if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
|
|
@@ -613,6 +694,7 @@ export class SequencerPublisher {
|
|
|
613
694
|
return undefined;
|
|
614
695
|
});
|
|
615
696
|
}
|
|
697
|
+
|
|
616
698
|
/**
|
|
617
699
|
* @notice Will simulate `validateHeader` to make sure that the block header is valid
|
|
618
700
|
* @dev This is a convenience function that can be used by the sequencer to validate a "partial" header.
|
|
@@ -636,7 +718,7 @@ export class SequencerPublisher {
|
|
|
636
718
|
flags,
|
|
637
719
|
] as const;
|
|
638
720
|
|
|
639
|
-
const ts =
|
|
721
|
+
const ts = this.getSimulationTimestamp(header.slotNumber);
|
|
640
722
|
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
641
723
|
opts?.forcePendingCheckpointNumber,
|
|
642
724
|
);
|
|
@@ -659,7 +741,7 @@ export class SequencerPublisher {
|
|
|
659
741
|
data: encodeFunctionData({ abi: RollupAbi, functionName: 'validateHeaderWithAttestations', args }),
|
|
660
742
|
from: MULTI_CALL_3_ADDRESS,
|
|
661
743
|
},
|
|
662
|
-
{ time: ts
|
|
744
|
+
{ time: ts },
|
|
663
745
|
stateOverrides,
|
|
664
746
|
);
|
|
665
747
|
this.log.debug(`Simulated validateHeader`);
|
|
@@ -712,6 +794,7 @@ export class SequencerPublisher {
|
|
|
712
794
|
gasUsed,
|
|
713
795
|
checkpointNumber,
|
|
714
796
|
forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
|
|
797
|
+
lastArchive: validationResult.checkpoint.lastArchive,
|
|
715
798
|
reason,
|
|
716
799
|
};
|
|
717
800
|
} catch (err) {
|
|
@@ -724,8 +807,8 @@ export class SequencerPublisher {
|
|
|
724
807
|
`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
|
|
725
808
|
{ ...logData, request, error: viemError.message },
|
|
726
809
|
);
|
|
727
|
-
const
|
|
728
|
-
if (
|
|
810
|
+
const latestProposedCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
811
|
+
if (latestProposedCheckpointNumber < checkpointNumber) {
|
|
729
812
|
this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, { ...logData });
|
|
730
813
|
return undefined;
|
|
731
814
|
} else {
|
|
@@ -799,9 +882,11 @@ export class SequencerPublisher {
|
|
|
799
882
|
checkpoint: Checkpoint,
|
|
800
883
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
801
884
|
attestationsAndSignersSignature: Signature,
|
|
802
|
-
options: {
|
|
803
|
-
|
|
804
|
-
|
|
885
|
+
options: {
|
|
886
|
+
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
887
|
+
forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
|
|
888
|
+
},
|
|
889
|
+
): Promise<void> {
|
|
805
890
|
const blobFields = checkpoint.toBlobFields();
|
|
806
891
|
const blobs = await getBlobsPerL1Block(blobFields);
|
|
807
892
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
@@ -820,13 +905,11 @@ export class SequencerPublisher {
|
|
|
820
905
|
blobInput,
|
|
821
906
|
] as const;
|
|
822
907
|
|
|
823
|
-
await this.simulateProposeTx(args,
|
|
824
|
-
return ts;
|
|
908
|
+
await this.simulateProposeTx(args, options);
|
|
825
909
|
}
|
|
826
910
|
|
|
827
911
|
private async enqueueCastSignalHelper(
|
|
828
912
|
slotNumber: SlotNumber,
|
|
829
|
-
timestamp: bigint,
|
|
830
913
|
signalType: GovernanceSignalAction,
|
|
831
914
|
payload: EthAddress,
|
|
832
915
|
base: IEmpireBase,
|
|
@@ -905,13 +988,17 @@ export class SequencerPublisher {
|
|
|
905
988
|
});
|
|
906
989
|
|
|
907
990
|
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
991
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
908
992
|
|
|
909
993
|
try {
|
|
910
994
|
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
911
995
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
912
996
|
} catch (err) {
|
|
913
997
|
const viemError = formatViemError(err);
|
|
914
|
-
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError
|
|
998
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError, {
|
|
999
|
+
simulationTimestamp: timestamp,
|
|
1000
|
+
l1BlockNumber,
|
|
1001
|
+
});
|
|
915
1002
|
this.backupFailedTx({
|
|
916
1003
|
id: keccak256(request.data!),
|
|
917
1004
|
failureType: 'simulation',
|
|
@@ -974,19 +1061,16 @@ export class SequencerPublisher {
|
|
|
974
1061
|
/**
|
|
975
1062
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
976
1063
|
* @param slotNumber - The slot number to cast a signal for.
|
|
977
|
-
* @param timestamp - The timestamp of the slot to cast a signal for.
|
|
978
1064
|
* @returns True if the signal was successfully enqueued, false otherwise.
|
|
979
1065
|
*/
|
|
980
1066
|
public enqueueGovernanceCastSignal(
|
|
981
1067
|
governancePayload: EthAddress,
|
|
982
1068
|
slotNumber: SlotNumber,
|
|
983
|
-
timestamp: bigint,
|
|
984
1069
|
signerAddress: EthAddress,
|
|
985
1070
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
986
1071
|
): Promise<boolean> {
|
|
987
1072
|
return this.enqueueCastSignalHelper(
|
|
988
1073
|
slotNumber,
|
|
989
|
-
timestamp,
|
|
990
1074
|
'governance-signal',
|
|
991
1075
|
governancePayload,
|
|
992
1076
|
this.govProposerContract,
|
|
@@ -999,7 +1083,6 @@ export class SequencerPublisher {
|
|
|
999
1083
|
public async enqueueSlashingActions(
|
|
1000
1084
|
actions: ProposerSlashAction[],
|
|
1001
1085
|
slotNumber: SlotNumber,
|
|
1002
|
-
timestamp: bigint,
|
|
1003
1086
|
signerAddress: EthAddress,
|
|
1004
1087
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
1005
1088
|
): Promise<boolean> {
|
|
@@ -1010,58 +1093,6 @@ export class SequencerPublisher {
|
|
|
1010
1093
|
|
|
1011
1094
|
for (const action of actions) {
|
|
1012
1095
|
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
1096
|
case 'vote-offenses': {
|
|
1066
1097
|
this.log.debug(`Enqueuing slashing vote for ${action.votes.length} votes at slot ${slotNumber}`, {
|
|
1067
1098
|
slotNumber,
|
|
@@ -1069,19 +1100,17 @@ export class SequencerPublisher {
|
|
|
1069
1100
|
votesCount: action.votes.length,
|
|
1070
1101
|
signerAddress,
|
|
1071
1102
|
});
|
|
1072
|
-
if (this.slashingProposerContract
|
|
1073
|
-
this.log.error('
|
|
1103
|
+
if (!this.slashingProposerContract) {
|
|
1104
|
+
this.log.error('No slashing proposer contract available');
|
|
1074
1105
|
return false;
|
|
1075
1106
|
}
|
|
1076
|
-
const tallySlashingProposer = this.slashingProposerContract as TallySlashingProposerContract;
|
|
1077
1107
|
const votes = bufferToHex(encodeSlashConsensusVotes(action.votes));
|
|
1078
|
-
const request = await
|
|
1108
|
+
const request = await this.slashingProposerContract.buildVoteRequestFromSigner(votes, slotNumber, signer);
|
|
1079
1109
|
await this.simulateAndEnqueueRequest(
|
|
1080
1110
|
'vote-offenses',
|
|
1081
1111
|
request,
|
|
1082
|
-
(receipt: TransactionReceipt) => !!
|
|
1112
|
+
(receipt: TransactionReceipt) => !!this.slashingProposerContract!.tryExtractVoteCastEvent(receipt.logs),
|
|
1083
1113
|
slotNumber,
|
|
1084
|
-
timestamp,
|
|
1085
1114
|
);
|
|
1086
1115
|
break;
|
|
1087
1116
|
}
|
|
@@ -1092,18 +1121,20 @@ export class SequencerPublisher {
|
|
|
1092
1121
|
round: action.round,
|
|
1093
1122
|
signerAddress,
|
|
1094
1123
|
});
|
|
1095
|
-
if (this.slashingProposerContract
|
|
1096
|
-
this.log.error('
|
|
1124
|
+
if (!this.slashingProposerContract) {
|
|
1125
|
+
this.log.error('No slashing proposer contract available');
|
|
1097
1126
|
return false;
|
|
1098
1127
|
}
|
|
1099
|
-
const
|
|
1100
|
-
|
|
1128
|
+
const executeRequest = this.slashingProposerContract.buildExecuteRoundRequest(
|
|
1129
|
+
action.round,
|
|
1130
|
+
action.committees,
|
|
1131
|
+
);
|
|
1101
1132
|
await this.simulateAndEnqueueRequest(
|
|
1102
1133
|
'execute-slash',
|
|
1103
|
-
|
|
1104
|
-
(receipt: TransactionReceipt) =>
|
|
1134
|
+
executeRequest,
|
|
1135
|
+
(receipt: TransactionReceipt) =>
|
|
1136
|
+
!!this.slashingProposerContract!.tryExtractRoundExecutedEvent(receipt.logs),
|
|
1105
1137
|
slotNumber,
|
|
1106
|
-
timestamp,
|
|
1107
1138
|
);
|
|
1108
1139
|
break;
|
|
1109
1140
|
}
|
|
@@ -1123,7 +1154,11 @@ export class SequencerPublisher {
|
|
|
1123
1154
|
checkpoint: Checkpoint,
|
|
1124
1155
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
1125
1156
|
attestationsAndSignersSignature: Signature,
|
|
1126
|
-
opts: {
|
|
1157
|
+
opts: {
|
|
1158
|
+
txTimeoutAt?: Date;
|
|
1159
|
+
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
1160
|
+
forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
|
|
1161
|
+
} = {},
|
|
1127
1162
|
): Promise<void> {
|
|
1128
1163
|
const checkpointHeader = checkpoint.header;
|
|
1129
1164
|
|
|
@@ -1139,15 +1174,13 @@ export class SequencerPublisher {
|
|
|
1139
1174
|
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
1140
1175
|
};
|
|
1141
1176
|
|
|
1142
|
-
let ts: bigint;
|
|
1143
|
-
|
|
1144
1177
|
try {
|
|
1145
1178
|
// @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
|
|
1146
1179
|
// This means that we can avoid the simulation issues in later checks.
|
|
1147
1180
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
1148
1181
|
// make time consistency checks break.
|
|
1149
1182
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
1150
|
-
|
|
1183
|
+
await this.validateCheckpointForSubmission(
|
|
1151
1184
|
checkpoint,
|
|
1152
1185
|
attestationsAndSigners,
|
|
1153
1186
|
attestationsAndSignersSignature,
|
|
@@ -1162,8 +1195,26 @@ export class SequencerPublisher {
|
|
|
1162
1195
|
throw err;
|
|
1163
1196
|
}
|
|
1164
1197
|
|
|
1198
|
+
// Build a pre-check callback that re-validates the checkpoint before L1 submission.
|
|
1199
|
+
// During pipelining this catches stale proposals due to prunes or L1 reorgs that occur during the pipeline sleep.
|
|
1200
|
+
let preCheck = undefined;
|
|
1201
|
+
if (this.epochCache.isProposerPipeliningEnabled()) {
|
|
1202
|
+
preCheck = async () => {
|
|
1203
|
+
this.log.debug(`Re-validating checkpoint ${checkpoint.number} before L1 submission`);
|
|
1204
|
+
await this.validateCheckpointForSubmission(
|
|
1205
|
+
checkpoint,
|
|
1206
|
+
attestationsAndSigners,
|
|
1207
|
+
attestationsAndSignersSignature,
|
|
1208
|
+
{
|
|
1209
|
+
// Forcing pending checkpoint number is included its required if an invalidation request is included
|
|
1210
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
1211
|
+
},
|
|
1212
|
+
);
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1165
1216
|
this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
|
|
1166
|
-
await this.addProposeTx(checkpoint, proposeTxArgs, opts,
|
|
1217
|
+
await this.addProposeTx(checkpoint, proposeTxArgs, opts, preCheck);
|
|
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
|
|
|
@@ -1293,7 +1346,6 @@ export class SequencerPublisher {
|
|
|
1293
1346
|
|
|
1294
1347
|
private async prepareProposeTx(
|
|
1295
1348
|
encodedData: L1ProcessArgs,
|
|
1296
|
-
timestamp: bigint,
|
|
1297
1349
|
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1298
1350
|
) {
|
|
1299
1351
|
const kzg = Blob.getViemKzgInstance();
|
|
@@ -1366,7 +1418,7 @@ export class SequencerPublisher {
|
|
|
1366
1418
|
blobInput,
|
|
1367
1419
|
] as const;
|
|
1368
1420
|
|
|
1369
|
-
const { rollupData, simulationResult } = await this.simulateProposeTx(args,
|
|
1421
|
+
const { rollupData, simulationResult } = await this.simulateProposeTx(args, options);
|
|
1370
1422
|
|
|
1371
1423
|
return { args, blobEvaluationGas, rollupData, simulationResult };
|
|
1372
1424
|
}
|
|
@@ -1374,7 +1426,6 @@ export class SequencerPublisher {
|
|
|
1374
1426
|
/**
|
|
1375
1427
|
* Simulates the propose tx with eth_simulateV1
|
|
1376
1428
|
* @param args - The propose tx args
|
|
1377
|
-
* @param timestamp - The timestamp to simulate proposal at
|
|
1378
1429
|
* @returns The simulation result
|
|
1379
1430
|
*/
|
|
1380
1431
|
private async simulateProposeTx(
|
|
@@ -1391,8 +1442,10 @@ export class SequencerPublisher {
|
|
|
1391
1442
|
ViemSignature,
|
|
1392
1443
|
`0x${string}`,
|
|
1393
1444
|
],
|
|
1394
|
-
|
|
1395
|
-
|
|
1445
|
+
options: {
|
|
1446
|
+
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
1447
|
+
forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
|
|
1448
|
+
},
|
|
1396
1449
|
) {
|
|
1397
1450
|
const rollupData = encodeFunctionData({
|
|
1398
1451
|
abi: RollupAbi,
|
|
@@ -1400,13 +1453,23 @@ export class SequencerPublisher {
|
|
|
1400
1453
|
args,
|
|
1401
1454
|
});
|
|
1402
1455
|
|
|
1403
|
-
// override the
|
|
1456
|
+
// override the proposed checkpoint number if requested
|
|
1404
1457
|
const forcePendingCheckpointNumberStateDiff = (
|
|
1405
1458
|
options.forcePendingCheckpointNumber !== undefined
|
|
1406
1459
|
? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber)
|
|
1407
1460
|
: []
|
|
1408
1461
|
).flatMap(override => override.stateDiff ?? []);
|
|
1409
1462
|
|
|
1463
|
+
// override the fee header for a specific checkpoint number if requested (used when pipelining)
|
|
1464
|
+
const forceProposedFeeHeaderStateDiff = (
|
|
1465
|
+
options.forceProposedFeeHeader !== undefined
|
|
1466
|
+
? await this.rollupContract.makeFeeHeaderOverride(
|
|
1467
|
+
options.forceProposedFeeHeader.checkpointNumber,
|
|
1468
|
+
options.forceProposedFeeHeader.feeHeader,
|
|
1469
|
+
)
|
|
1470
|
+
: []
|
|
1471
|
+
).flatMap(override => override.stateDiff ?? []);
|
|
1472
|
+
|
|
1410
1473
|
const stateOverrides: StateOverride = [
|
|
1411
1474
|
{
|
|
1412
1475
|
address: this.rollupContract.address,
|
|
@@ -1414,6 +1477,7 @@ export class SequencerPublisher {
|
|
|
1414
1477
|
stateDiff: [
|
|
1415
1478
|
{ slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
|
|
1416
1479
|
...forcePendingCheckpointNumberStateDiff,
|
|
1480
|
+
...forceProposedFeeHeaderStateDiff,
|
|
1417
1481
|
],
|
|
1418
1482
|
},
|
|
1419
1483
|
];
|
|
@@ -1426,6 +1490,7 @@ export class SequencerPublisher {
|
|
|
1426
1490
|
}
|
|
1427
1491
|
|
|
1428
1492
|
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1493
|
+
const simTs = this.getSimulationTimestamp(SlotNumber.fromBigInt(args[0].header.slotNumber));
|
|
1429
1494
|
|
|
1430
1495
|
const simulationResult = await this.l1TxUtils
|
|
1431
1496
|
.simulate(
|
|
@@ -1436,8 +1501,7 @@ export class SequencerPublisher {
|
|
|
1436
1501
|
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1437
1502
|
},
|
|
1438
1503
|
{
|
|
1439
|
-
|
|
1440
|
-
time: timestamp + 1n,
|
|
1504
|
+
time: simTs,
|
|
1441
1505
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1442
1506
|
gasLimit: MAX_L1_TX_LIMIT * 2n,
|
|
1443
1507
|
},
|
|
@@ -1459,7 +1523,7 @@ export class SequencerPublisher {
|
|
|
1459
1523
|
logs: [],
|
|
1460
1524
|
};
|
|
1461
1525
|
}
|
|
1462
|
-
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1526
|
+
this.log.error(`Failed to simulate propose tx`, viemError, { simulationTimestamp: simTs });
|
|
1463
1527
|
this.backupFailedTx({
|
|
1464
1528
|
id: keccak256(rollupData),
|
|
1465
1529
|
failureType: 'simulation',
|
|
@@ -1481,17 +1545,17 @@ export class SequencerPublisher {
|
|
|
1481
1545
|
private async addProposeTx(
|
|
1482
1546
|
checkpoint: Checkpoint,
|
|
1483
1547
|
encodedData: L1ProcessArgs,
|
|
1484
|
-
opts: {
|
|
1485
|
-
|
|
1548
|
+
opts: {
|
|
1549
|
+
txTimeoutAt?: Date;
|
|
1550
|
+
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
1551
|
+
forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
|
|
1552
|
+
} = {},
|
|
1553
|
+
preCheck?: () => Promise<void>,
|
|
1486
1554
|
): Promise<void> {
|
|
1487
1555
|
const slot = checkpoint.header.slotNumber;
|
|
1488
1556
|
const timer = new Timer();
|
|
1489
1557
|
const kzg = Blob.getViemKzgInstance();
|
|
1490
|
-
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
1491
|
-
encodedData,
|
|
1492
|
-
timestamp,
|
|
1493
|
-
opts,
|
|
1494
|
-
);
|
|
1558
|
+
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, opts);
|
|
1495
1559
|
const startBlock = await this.l1TxUtils.getBlockNumber();
|
|
1496
1560
|
const gasLimit = this.l1TxUtils.bumpGasLimit(
|
|
1497
1561
|
BigInt(Math.ceil((Number(simulationResult.gasUsed) * 64) / 63)) +
|
|
@@ -1515,6 +1579,7 @@ export class SequencerPublisher {
|
|
|
1515
1579
|
},
|
|
1516
1580
|
lastValidL2Slot: checkpoint.header.slotNumber,
|
|
1517
1581
|
gasConfig: { ...opts, gasLimit },
|
|
1582
|
+
preCheck,
|
|
1518
1583
|
blobConfig: {
|
|
1519
1584
|
blobs: encodedData.blobs.map(b => b.data),
|
|
1520
1585
|
kzg,
|
|
@@ -1567,4 +1632,17 @@ export class SequencerPublisher {
|
|
|
1567
1632
|
},
|
|
1568
1633
|
});
|
|
1569
1634
|
}
|
|
1635
|
+
|
|
1636
|
+
/** Returns the timestamp of the last L1 slot within a given L2 slot. Used as the simulation timestamp
|
|
1637
|
+
* for eth_simulateV1 calls, since it's guaranteed to be greater than any L1 block produced during the slot. */
|
|
1638
|
+
private getSimulationTimestamp(slot: SlotNumber): bigint {
|
|
1639
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1640
|
+
return getLastL1SlotTimestampForL2Slot(slot, l1Constants);
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
/** Returns the timestamp of the next L1 slot boundary after now. */
|
|
1644
|
+
private getNextL1SlotTimestamp(): bigint {
|
|
1645
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1646
|
+
return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
|
|
1647
|
+
}
|
|
1570
1648
|
}
|