@aztec/sequencer-client 0.0.1-commit.29c6b1a3 → 0.0.1-commit.2d9cb6034
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 +15 -7
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +60 -26
- package/dest/config.d.ts +26 -7
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +47 -28
- package/dest/global_variable_builder/global_builder.d.ts +15 -11
- 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 +47 -17
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +121 -42
- package/dest/publisher/index.d.ts +2 -1
- package/dest/publisher/index.d.ts.map +1 -1
- package/dest/publisher/l1_tx_failed_store/factory.d.ts +11 -0
- package/dest/publisher/l1_tx_failed_store/factory.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/factory.js +22 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +59 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +1 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +15 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +34 -0
- package/dest/publisher/l1_tx_failed_store/index.d.ts +4 -0
- package/dest/publisher/l1_tx_failed_store/index.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/index.js +2 -0
- package/dest/publisher/sequencer-publisher-factory.d.ts +11 -3
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +27 -2
- package/dest/publisher/sequencer-publisher-metrics.d.ts +1 -1
- package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-metrics.js +12 -4
- package/dest/publisher/sequencer-publisher.d.ts +76 -31
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +434 -88
- package/dest/sequencer/checkpoint_proposal_job.d.ts +61 -13
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +400 -201
- 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 +21 -5
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +122 -30
- package/dest/sequencer/sequencer.d.ts +42 -17
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +147 -89
- package/dest/sequencer/timetable.d.ts +4 -6
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +7 -11
- package/dest/sequencer/types.d.ts +2 -2
- package/dest/sequencer/types.d.ts.map +1 -1
- package/dest/test/index.d.ts +3 -5
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts +12 -12
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +45 -36
- package/dest/test/utils.d.ts +3 -3
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +5 -4
- package/package.json +27 -28
- package/src/client/sequencer-client.ts +76 -23
- package/src/config.ts +65 -38
- package/src/global_variable_builder/global_builder.ts +38 -27
- package/src/global_variable_builder/index.ts +1 -1
- package/src/publisher/config.ts +153 -43
- package/src/publisher/index.ts +3 -0
- package/src/publisher/l1_tx_failed_store/factory.ts +32 -0
- package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +55 -0
- package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +46 -0
- package/src/publisher/l1_tx_failed_store/index.ts +3 -0
- package/src/publisher/sequencer-publisher-factory.ts +38 -6
- package/src/publisher/sequencer-publisher-metrics.ts +7 -3
- package/src/publisher/sequencer-publisher.ts +474 -111
- package/src/sequencer/checkpoint_proposal_job.ts +532 -224
- package/src/sequencer/checkpoint_voter.ts +1 -12
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/metrics.ts +138 -32
- package/src/sequencer/sequencer.ts +213 -106
- package/src/sequencer/timetable.ts +13 -12
- package/src/sequencer/types.ts +1 -1
- package/src/test/index.ts +2 -4
- package/src/test/mock_checkpoint_builder.ts +65 -53
- package/src/test/utils.ts +5 -2
|
@@ -4,6 +4,8 @@ import type { EpochCache } from '@aztec/epoch-cache';
|
|
|
4
4
|
import type { L1ContractsConfig } from '@aztec/ethereum/config';
|
|
5
5
|
import {
|
|
6
6
|
type EmpireSlashingProposerContract,
|
|
7
|
+
FeeAssetPriceOracle,
|
|
8
|
+
type FeeHeader,
|
|
7
9
|
type GovernanceProposerContract,
|
|
8
10
|
type IEmpireBase,
|
|
9
11
|
MULTI_CALL_3_ADDRESS,
|
|
@@ -18,35 +20,65 @@ import {
|
|
|
18
20
|
type L1BlobInputs,
|
|
19
21
|
type L1TxConfig,
|
|
20
22
|
type L1TxRequest,
|
|
23
|
+
type L1TxUtils,
|
|
24
|
+
MAX_L1_TX_LIMIT,
|
|
21
25
|
type TransactionStats,
|
|
22
26
|
WEI_CONST,
|
|
23
27
|
} from '@aztec/ethereum/l1-tx-utils';
|
|
24
|
-
import
|
|
25
|
-
import { FormattedViemError, formatViemError, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
28
|
+
import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
26
29
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
27
30
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
28
31
|
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
32
|
+
import { trimmedBytesLength } from '@aztec/foundation/buffer';
|
|
29
33
|
import { pick } from '@aztec/foundation/collection';
|
|
30
34
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
35
|
+
import { TimeoutError } from '@aztec/foundation/error';
|
|
31
36
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
32
37
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
33
38
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
39
|
+
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
40
|
+
import { InterruptibleSleep } from '@aztec/foundation/sleep';
|
|
34
41
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
35
|
-
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
42
|
+
import { type DateProvider, Timer } from '@aztec/foundation/timer';
|
|
36
43
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
37
44
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
38
45
|
import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
39
46
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
47
|
+
import { getLastL1SlotTimestampForL2Slot, getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
40
48
|
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
41
49
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
42
50
|
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
43
51
|
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
44
52
|
|
|
45
|
-
import {
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
import {
|
|
54
|
+
type Hex,
|
|
55
|
+
type StateOverride,
|
|
56
|
+
type TransactionReceipt,
|
|
57
|
+
type TypedDataDefinition,
|
|
58
|
+
encodeFunctionData,
|
|
59
|
+
keccak256,
|
|
60
|
+
multicall3Abi,
|
|
61
|
+
toHex,
|
|
62
|
+
} from 'viem';
|
|
63
|
+
|
|
64
|
+
import type { SequencerPublisherConfig } from './config.js';
|
|
65
|
+
import { type FailedL1Tx, type L1TxFailedStore, createL1TxFailedStore } from './l1_tx_failed_store/index.js';
|
|
48
66
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
49
67
|
|
|
68
|
+
/** Result of a sendRequests call, returned by both sendRequests() and sendRequestsAt(). */
|
|
69
|
+
export type SendRequestsResult = {
|
|
70
|
+
/** The L1 transaction receipt or error from the bundled multicall. */
|
|
71
|
+
result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError;
|
|
72
|
+
/** Actions that expired (past their deadline) before the request was sent. */
|
|
73
|
+
expiredActions: Action[];
|
|
74
|
+
/** Actions that were included in the sent L1 transaction. */
|
|
75
|
+
sentActions: Action[];
|
|
76
|
+
/** Actions whose L1 simulation succeeded (subset of sentActions). */
|
|
77
|
+
successfulActions: Action[];
|
|
78
|
+
/** Actions whose L1 simulation failed (subset of sentActions). */
|
|
79
|
+
failedActions: Action[];
|
|
80
|
+
};
|
|
81
|
+
|
|
50
82
|
/** Arguments to the process method of the rollup contract */
|
|
51
83
|
type L1ProcessArgs = {
|
|
52
84
|
/** The L2 block header. */
|
|
@@ -59,6 +91,8 @@ type L1ProcessArgs = {
|
|
|
59
91
|
attestationsAndSigners: CommitteeAttestationsAndSigners;
|
|
60
92
|
/** Attestations and signers signature */
|
|
61
93
|
attestationsAndSignersSignature: Signature;
|
|
94
|
+
/** The fee asset price modifier in basis points (from oracle) */
|
|
95
|
+
feeAssetPriceModifier: bigint;
|
|
62
96
|
};
|
|
63
97
|
|
|
64
98
|
export const Actions = [
|
|
@@ -86,6 +120,8 @@ export type InvalidateCheckpointRequest = {
|
|
|
86
120
|
gasUsed: bigint;
|
|
87
121
|
checkpointNumber: CheckpointNumber;
|
|
88
122
|
forcePendingCheckpointNumber: CheckpointNumber;
|
|
123
|
+
/** Archive at the rollback target checkpoint (checkpoint N-1). */
|
|
124
|
+
lastArchive: Fr;
|
|
89
125
|
};
|
|
90
126
|
|
|
91
127
|
interface RequestWithExpiry {
|
|
@@ -104,6 +140,7 @@ export class SequencerPublisher {
|
|
|
104
140
|
private interrupted = false;
|
|
105
141
|
private metrics: SequencerPublisherMetrics;
|
|
106
142
|
public epochCache: EpochCache;
|
|
143
|
+
private failedTxStore?: Promise<L1TxFailedStore | undefined>;
|
|
107
144
|
|
|
108
145
|
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
109
146
|
protected slashingLog = createLogger('sequencer:publisher:slashing');
|
|
@@ -111,21 +148,31 @@ export class SequencerPublisher {
|
|
|
111
148
|
protected lastActions: Partial<Record<Action, SlotNumber>> = {};
|
|
112
149
|
|
|
113
150
|
private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
|
|
151
|
+
private payloadProposedCache: Set<string> = new Set<string>();
|
|
114
152
|
|
|
115
153
|
protected log: Logger;
|
|
116
154
|
protected ethereumSlotDuration: bigint;
|
|
155
|
+
protected aztecSlotDuration: bigint;
|
|
156
|
+
|
|
157
|
+
/** Date provider for wall-clock time. */
|
|
158
|
+
private readonly dateProvider: DateProvider;
|
|
117
159
|
|
|
118
160
|
private blobClient: BlobClientInterface;
|
|
119
161
|
|
|
120
162
|
/** Address to use for simulations in fisherman mode (actual proposer's address) */
|
|
121
163
|
private proposerAddressForSimulation?: EthAddress;
|
|
122
164
|
|
|
165
|
+
/** Optional callback to obtain a replacement publisher when the current one fails to send. */
|
|
166
|
+
private getNextPublisher?: (excludeAddresses: EthAddress[]) => Promise<L1TxUtils | undefined>;
|
|
167
|
+
|
|
123
168
|
/** L1 fee analyzer for fisherman mode */
|
|
124
169
|
private l1FeeAnalyzer?: L1FeeAnalyzer;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
170
|
+
|
|
171
|
+
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */
|
|
172
|
+
private feeAssetPriceOracle: FeeAssetPriceOracle;
|
|
173
|
+
|
|
174
|
+
/** Interruptible sleep used by sendRequestsAt to wait until a target timestamp. */
|
|
175
|
+
private readonly interruptibleSleep = new InterruptibleSleep();
|
|
129
176
|
|
|
130
177
|
// A CALL to a cold address is 2700 gas
|
|
131
178
|
public static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
@@ -133,7 +180,7 @@ export class SequencerPublisher {
|
|
|
133
180
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
134
181
|
public static VOTE_GAS_GUESS: bigint = 800_000n;
|
|
135
182
|
|
|
136
|
-
public l1TxUtils:
|
|
183
|
+
public l1TxUtils: L1TxUtils;
|
|
137
184
|
public rollupContract: RollupContract;
|
|
138
185
|
public govProposerContract: GovernanceProposerContract;
|
|
139
186
|
public slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
@@ -144,11 +191,12 @@ export class SequencerPublisher {
|
|
|
144
191
|
protected requests: RequestWithExpiry[] = [];
|
|
145
192
|
|
|
146
193
|
constructor(
|
|
147
|
-
private config:
|
|
194
|
+
private config: Pick<SequencerPublisherConfig, 'fishermanMode' | 'l1TxFailedStore'> &
|
|
195
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration'> & { l1ChainId: number },
|
|
148
196
|
deps: {
|
|
149
197
|
telemetry?: TelemetryClient;
|
|
150
198
|
blobClient: BlobClientInterface;
|
|
151
|
-
l1TxUtils:
|
|
199
|
+
l1TxUtils: L1TxUtils;
|
|
152
200
|
rollupContract: RollupContract;
|
|
153
201
|
slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
154
202
|
governanceProposerContract: GovernanceProposerContract;
|
|
@@ -158,19 +206,24 @@ export class SequencerPublisher {
|
|
|
158
206
|
metrics: SequencerPublisherMetrics;
|
|
159
207
|
lastActions: Partial<Record<Action, SlotNumber>>;
|
|
160
208
|
log?: Logger;
|
|
209
|
+
getNextPublisher?: (excludeAddresses: EthAddress[]) => Promise<L1TxUtils | undefined>;
|
|
161
210
|
},
|
|
162
211
|
) {
|
|
163
212
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
164
213
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
214
|
+
this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
|
|
215
|
+
this.dateProvider = deps.dateProvider;
|
|
165
216
|
this.epochCache = deps.epochCache;
|
|
166
217
|
this.lastActions = deps.lastActions;
|
|
167
218
|
|
|
168
219
|
this.blobClient = deps.blobClient;
|
|
220
|
+
this.dateProvider = deps.dateProvider;
|
|
169
221
|
|
|
170
222
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
171
223
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
172
224
|
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
173
225
|
this.l1TxUtils = deps.l1TxUtils;
|
|
226
|
+
this.getNextPublisher = deps.getNextPublisher;
|
|
174
227
|
|
|
175
228
|
this.rollupContract = deps.rollupContract;
|
|
176
229
|
|
|
@@ -192,12 +245,52 @@ export class SequencerPublisher {
|
|
|
192
245
|
createLogger('sequencer:publisher:fee-analyzer'),
|
|
193
246
|
);
|
|
194
247
|
}
|
|
248
|
+
|
|
249
|
+
// Initialize fee asset price oracle
|
|
250
|
+
this.feeAssetPriceOracle = new FeeAssetPriceOracle(
|
|
251
|
+
this.l1TxUtils.client,
|
|
252
|
+
this.rollupContract,
|
|
253
|
+
createLogger('sequencer:publisher:price-oracle'),
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
// Initialize failed L1 tx store (optional, for test networks)
|
|
257
|
+
this.failedTxStore = createL1TxFailedStore(config.l1TxFailedStore, this.log);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Backs up a failed L1 transaction to the configured store for debugging.
|
|
262
|
+
* Does nothing if no store is configured.
|
|
263
|
+
*/
|
|
264
|
+
private backupFailedTx(failedTx: Omit<FailedL1Tx, 'timestamp'>): void {
|
|
265
|
+
if (!this.failedTxStore) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const tx: FailedL1Tx = {
|
|
270
|
+
...failedTx,
|
|
271
|
+
timestamp: Date.now(),
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// Fire and forget - don't block on backup
|
|
275
|
+
void this.failedTxStore
|
|
276
|
+
.then(store => store?.saveFailedTx(tx))
|
|
277
|
+
.catch(err => {
|
|
278
|
+
this.log.warn(`Failed to backup failed L1 tx to store`, err);
|
|
279
|
+
});
|
|
195
280
|
}
|
|
196
281
|
|
|
197
282
|
public getRollupContract(): RollupContract {
|
|
198
283
|
return this.rollupContract;
|
|
199
284
|
}
|
|
200
285
|
|
|
286
|
+
/**
|
|
287
|
+
* Gets the fee asset price modifier from the oracle.
|
|
288
|
+
* Returns 0n if the oracle query fails.
|
|
289
|
+
*/
|
|
290
|
+
public getFeeAssetPriceModifier(): Promise<bigint> {
|
|
291
|
+
return this.feeAssetPriceOracle.computePriceModifier();
|
|
292
|
+
}
|
|
293
|
+
|
|
201
294
|
public getSenderAddress() {
|
|
202
295
|
return this.l1TxUtils.getSenderAddress();
|
|
203
296
|
}
|
|
@@ -222,7 +315,7 @@ export class SequencerPublisher {
|
|
|
222
315
|
}
|
|
223
316
|
|
|
224
317
|
public getCurrentL2Slot(): SlotNumber {
|
|
225
|
-
return this.epochCache.
|
|
318
|
+
return this.epochCache.getSlotNow();
|
|
226
319
|
}
|
|
227
320
|
|
|
228
321
|
/**
|
|
@@ -273,7 +366,7 @@ export class SequencerPublisher {
|
|
|
273
366
|
// Start the analysis
|
|
274
367
|
const analysisId = await this.l1FeeAnalyzer.startAnalysis(
|
|
275
368
|
l2SlotNumber,
|
|
276
|
-
gasLimit > 0n ? gasLimit :
|
|
369
|
+
gasLimit > 0n ? gasLimit : MAX_L1_TX_LIMIT,
|
|
277
370
|
l1Requests,
|
|
278
371
|
blobConfig,
|
|
279
372
|
onComplete,
|
|
@@ -300,9 +393,10 @@ export class SequencerPublisher {
|
|
|
300
393
|
* - undefined if no valid requests are found OR the tx failed to send.
|
|
301
394
|
*/
|
|
302
395
|
@trackSpan('SequencerPublisher.sendRequests')
|
|
303
|
-
public async sendRequests() {
|
|
396
|
+
public async sendRequests(): Promise<SendRequestsResult | undefined> {
|
|
304
397
|
const requestsToProcess = [...this.requests];
|
|
305
398
|
this.requests = [];
|
|
399
|
+
|
|
306
400
|
if (this.interrupted || requestsToProcess.length === 0) {
|
|
307
401
|
return undefined;
|
|
308
402
|
}
|
|
@@ -335,8 +429,8 @@ export class SequencerPublisher {
|
|
|
335
429
|
// @note - we can only have one blob config per bundle
|
|
336
430
|
// find requests with gas and blob configs
|
|
337
431
|
// See https://github.com/AztecProtocol/aztec-packages/issues/11513
|
|
338
|
-
const gasConfigs =
|
|
339
|
-
const blobConfigs =
|
|
432
|
+
const gasConfigs = validRequests.filter(request => request.gasConfig).map(request => request.gasConfig);
|
|
433
|
+
const blobConfigs = validRequests.filter(request => request.blobConfig).map(request => request.blobConfig);
|
|
340
434
|
|
|
341
435
|
if (blobConfigs.length > 1) {
|
|
342
436
|
throw new Error('Multiple blob configs found');
|
|
@@ -346,7 +440,16 @@ export class SequencerPublisher {
|
|
|
346
440
|
|
|
347
441
|
// Merge gasConfigs. Yields the sum of gasLimits, and the earliest txTimeoutAt, or undefined if no gasConfig sets them.
|
|
348
442
|
const gasLimits = gasConfigs.map(g => g?.gasLimit).filter((g): g is bigint => g !== undefined);
|
|
349
|
-
|
|
443
|
+
let gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
|
|
444
|
+
// Cap at L1 block gas limit so the node accepts the tx ("gas limit too high" otherwise).
|
|
445
|
+
const maxGas = MAX_L1_TX_LIMIT;
|
|
446
|
+
if (gasLimit !== undefined && gasLimit > maxGas) {
|
|
447
|
+
this.log.debug('Capping bundled tx gas limit to L1 max', {
|
|
448
|
+
requested: gasLimit,
|
|
449
|
+
capped: maxGas,
|
|
450
|
+
});
|
|
451
|
+
gasLimit = maxGas;
|
|
452
|
+
}
|
|
350
453
|
const txTimeoutAts = gasConfigs.map(g => g?.txTimeoutAt).filter((g): g is Date => g !== undefined);
|
|
351
454
|
const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map(g => g.getTime()))) : undefined; // earliest
|
|
352
455
|
const txConfig: RequestWithExpiry['gasConfig'] = { gasLimit, txTimeoutAt };
|
|
@@ -356,19 +459,36 @@ export class SequencerPublisher {
|
|
|
356
459
|
validRequests.sort((a, b) => compareActions(a.action, b.action));
|
|
357
460
|
|
|
358
461
|
try {
|
|
462
|
+
// Capture context for failed tx backup before sending
|
|
463
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
464
|
+
const multicallData = encodeFunctionData({
|
|
465
|
+
abi: multicall3Abi,
|
|
466
|
+
functionName: 'aggregate3',
|
|
467
|
+
args: [
|
|
468
|
+
validRequests.map(r => ({
|
|
469
|
+
target: r.request.to!,
|
|
470
|
+
callData: r.request.data!,
|
|
471
|
+
allowFailure: true,
|
|
472
|
+
})),
|
|
473
|
+
],
|
|
474
|
+
});
|
|
475
|
+
const blobDataHex = blobConfig?.blobs?.map(b => toHex(b)) as Hex[] | undefined;
|
|
476
|
+
|
|
477
|
+
const txContext = { multicallData, blobData: blobDataHex, l1BlockNumber };
|
|
478
|
+
|
|
359
479
|
this.log.debug('Forwarding transactions', {
|
|
360
480
|
validRequests: validRequests.map(request => request.action),
|
|
361
481
|
txConfig,
|
|
362
482
|
});
|
|
363
|
-
const result = await
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
483
|
+
const result = await this.forwardWithPublisherRotation(validRequests, txConfig, blobConfig);
|
|
484
|
+
if (result === undefined) {
|
|
485
|
+
return undefined;
|
|
486
|
+
}
|
|
487
|
+
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(
|
|
488
|
+
validRequests,
|
|
489
|
+
result,
|
|
490
|
+
txContext,
|
|
370
491
|
);
|
|
371
|
-
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(validRequests, result);
|
|
372
492
|
return { result, expiredActions, sentActions: validActions, successfulActions, failedActions };
|
|
373
493
|
} catch (err) {
|
|
374
494
|
const viemError = formatViemError(err);
|
|
@@ -386,16 +506,105 @@ export class SequencerPublisher {
|
|
|
386
506
|
}
|
|
387
507
|
}
|
|
388
508
|
|
|
509
|
+
/**
|
|
510
|
+
* Forwards transactions via Multicall3, rotating to the next available publisher if a send
|
|
511
|
+
* failure occurs (i.e. the tx never reached the chain).
|
|
512
|
+
* On-chain reverts and simulation errors are returned as-is without rotation.
|
|
513
|
+
*/
|
|
514
|
+
private async forwardWithPublisherRotation(
|
|
515
|
+
validRequests: RequestWithExpiry[],
|
|
516
|
+
txConfig: RequestWithExpiry['gasConfig'],
|
|
517
|
+
blobConfig: L1BlobInputs | undefined,
|
|
518
|
+
) {
|
|
519
|
+
const triedAddresses: EthAddress[] = [];
|
|
520
|
+
let currentPublisher = this.l1TxUtils;
|
|
521
|
+
|
|
522
|
+
while (true) {
|
|
523
|
+
triedAddresses.push(currentPublisher.getSenderAddress());
|
|
524
|
+
try {
|
|
525
|
+
const result = await Multicall3.forward(
|
|
526
|
+
validRequests.map(r => r.request),
|
|
527
|
+
currentPublisher,
|
|
528
|
+
txConfig,
|
|
529
|
+
blobConfig,
|
|
530
|
+
this.rollupContract.address,
|
|
531
|
+
this.log,
|
|
532
|
+
);
|
|
533
|
+
this.l1TxUtils = currentPublisher;
|
|
534
|
+
return result;
|
|
535
|
+
} catch (err) {
|
|
536
|
+
if (err instanceof TimeoutError) {
|
|
537
|
+
throw err;
|
|
538
|
+
}
|
|
539
|
+
const viemError = formatViemError(err);
|
|
540
|
+
if (!this.getNextPublisher) {
|
|
541
|
+
this.log.error('Failed to publish bundled transactions', viemError);
|
|
542
|
+
return undefined;
|
|
543
|
+
}
|
|
544
|
+
this.log.warn(
|
|
545
|
+
`Publisher ${currentPublisher.getSenderAddress()} failed to send, rotating to next publisher`,
|
|
546
|
+
viemError,
|
|
547
|
+
);
|
|
548
|
+
const nextPublisher = await this.getNextPublisher([...triedAddresses]);
|
|
549
|
+
if (!nextPublisher) {
|
|
550
|
+
this.log.error('All available publishers exhausted, failed to publish bundled transactions');
|
|
551
|
+
return undefined;
|
|
552
|
+
}
|
|
553
|
+
currentPublisher = nextPublisher;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/*
|
|
559
|
+
* Schedules sending all enqueued requests at (or after) the given timestamp.
|
|
560
|
+
* Uses InterruptibleSleep so it can be cancelled via interrupt().
|
|
561
|
+
* Returns the promise for the L1 response (caller should NOT await this in the work loop).
|
|
562
|
+
*/
|
|
563
|
+
public async sendRequestsAt(submitAfter: Date): Promise<SendRequestsResult | undefined> {
|
|
564
|
+
const ms = submitAfter.getTime() - this.dateProvider.now();
|
|
565
|
+
if (ms > 0) {
|
|
566
|
+
this.log.debug(`Sleeping ${ms}ms before sending requests`, { submitAfter });
|
|
567
|
+
await this.interruptibleSleep.sleep(ms);
|
|
568
|
+
}
|
|
569
|
+
if (this.interrupted) {
|
|
570
|
+
return undefined;
|
|
571
|
+
}
|
|
572
|
+
return this.sendRequests();
|
|
573
|
+
}
|
|
574
|
+
|
|
389
575
|
private callbackBundledTransactions(
|
|
390
576
|
requests: RequestWithExpiry[],
|
|
391
|
-
result
|
|
577
|
+
result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError | undefined,
|
|
578
|
+
txContext: { multicallData: Hex; blobData?: Hex[]; l1BlockNumber: bigint },
|
|
392
579
|
) {
|
|
393
580
|
const actionsListStr = requests.map(r => r.action).join(', ');
|
|
394
581
|
if (result instanceof FormattedViemError) {
|
|
395
582
|
this.log.error(`Failed to publish bundled transactions (${actionsListStr})`, result);
|
|
583
|
+
this.backupFailedTx({
|
|
584
|
+
id: keccak256(txContext.multicallData),
|
|
585
|
+
failureType: 'send-error',
|
|
586
|
+
request: { to: MULTI_CALL_3_ADDRESS, data: txContext.multicallData },
|
|
587
|
+
blobData: txContext.blobData,
|
|
588
|
+
l1BlockNumber: txContext.l1BlockNumber.toString(),
|
|
589
|
+
error: { message: result.message, name: result.name },
|
|
590
|
+
context: {
|
|
591
|
+
actions: requests.map(r => r.action),
|
|
592
|
+
requests: requests.map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
|
|
593
|
+
sender: this.getSenderAddress().toString(),
|
|
594
|
+
},
|
|
595
|
+
});
|
|
396
596
|
return { failedActions: requests.map(r => r.action) };
|
|
397
597
|
} else {
|
|
398
|
-
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
598
|
+
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
599
|
+
result,
|
|
600
|
+
requests: requests.map(r => ({
|
|
601
|
+
...r,
|
|
602
|
+
// Avoid logging large blob data
|
|
603
|
+
blobConfig: r.blobConfig
|
|
604
|
+
? { ...r.blobConfig, blobs: r.blobConfig.blobs.map(b => ({ size: trimmedBytesLength(b) })) }
|
|
605
|
+
: undefined,
|
|
606
|
+
})),
|
|
607
|
+
});
|
|
399
608
|
const successfulActions: Action[] = [];
|
|
400
609
|
const failedActions: Action[] = [];
|
|
401
610
|
for (const request of requests) {
|
|
@@ -405,26 +614,59 @@ export class SequencerPublisher {
|
|
|
405
614
|
failedActions.push(request.action);
|
|
406
615
|
}
|
|
407
616
|
}
|
|
617
|
+
// Single backup for the whole reverted tx
|
|
618
|
+
if (failedActions.length > 0 && result?.receipt?.status === 'reverted') {
|
|
619
|
+
this.backupFailedTx({
|
|
620
|
+
id: result.receipt.transactionHash,
|
|
621
|
+
failureType: 'revert',
|
|
622
|
+
request: { to: MULTI_CALL_3_ADDRESS, data: txContext.multicallData },
|
|
623
|
+
blobData: txContext.blobData,
|
|
624
|
+
l1BlockNumber: result.receipt.blockNumber.toString(),
|
|
625
|
+
receipt: {
|
|
626
|
+
transactionHash: result.receipt.transactionHash,
|
|
627
|
+
blockNumber: result.receipt.blockNumber.toString(),
|
|
628
|
+
gasUsed: (result.receipt.gasUsed ?? 0n).toString(),
|
|
629
|
+
status: 'reverted',
|
|
630
|
+
},
|
|
631
|
+
error: { message: result.errorMsg ?? 'Transaction reverted' },
|
|
632
|
+
context: {
|
|
633
|
+
actions: failedActions,
|
|
634
|
+
requests: requests
|
|
635
|
+
.filter(r => failedActions.includes(r.action))
|
|
636
|
+
.map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
|
|
637
|
+
sender: this.getSenderAddress().toString(),
|
|
638
|
+
},
|
|
639
|
+
});
|
|
640
|
+
}
|
|
408
641
|
return { successfulActions, failedActions };
|
|
409
642
|
}
|
|
410
643
|
}
|
|
411
644
|
|
|
412
645
|
/**
|
|
413
|
-
* @notice Will call `
|
|
646
|
+
* @notice Will call `canProposeAt` to make sure that it is possible to propose
|
|
414
647
|
* @param tipArchive - The archive to check
|
|
415
648
|
* @returns The slot and block number if it is possible to propose, undefined otherwise
|
|
416
649
|
*/
|
|
417
|
-
public
|
|
650
|
+
public canProposeAt(
|
|
418
651
|
tipArchive: Fr,
|
|
419
652
|
msgSender: EthAddress,
|
|
420
|
-
opts: {
|
|
653
|
+
opts: {
|
|
654
|
+
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
655
|
+
forceArchive?: { checkpointNumber: CheckpointNumber; archive: Fr };
|
|
656
|
+
pipelined?: boolean;
|
|
657
|
+
} = {},
|
|
421
658
|
) {
|
|
422
659
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
423
660
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
424
661
|
|
|
662
|
+
const pipelined = opts.pipelined ?? this.epochCache.isProposerPipeliningEnabled();
|
|
663
|
+
const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
|
|
664
|
+
const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
|
|
665
|
+
|
|
425
666
|
return this.rollupContract
|
|
426
|
-
.
|
|
667
|
+
.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
|
|
427
668
|
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
669
|
+
forceArchive: opts.forceArchive,
|
|
428
670
|
})
|
|
429
671
|
.catch(err => {
|
|
430
672
|
if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
|
|
@@ -437,6 +679,7 @@ export class SequencerPublisher {
|
|
|
437
679
|
return undefined;
|
|
438
680
|
});
|
|
439
681
|
}
|
|
682
|
+
|
|
440
683
|
/**
|
|
441
684
|
* @notice Will simulate `validateHeader` to make sure that the block header is valid
|
|
442
685
|
* @dev This is a convenience function that can be used by the sequencer to validate a "partial" header.
|
|
@@ -460,7 +703,7 @@ export class SequencerPublisher {
|
|
|
460
703
|
flags,
|
|
461
704
|
] as const;
|
|
462
705
|
|
|
463
|
-
const ts =
|
|
706
|
+
const ts = this.getSimulationTimestamp(header.slotNumber);
|
|
464
707
|
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
465
708
|
opts?.forcePendingCheckpointNumber,
|
|
466
709
|
);
|
|
@@ -483,7 +726,7 @@ export class SequencerPublisher {
|
|
|
483
726
|
data: encodeFunctionData({ abi: RollupAbi, functionName: 'validateHeaderWithAttestations', args }),
|
|
484
727
|
from: MULTI_CALL_3_ADDRESS,
|
|
485
728
|
},
|
|
486
|
-
{ time: ts
|
|
729
|
+
{ time: ts },
|
|
487
730
|
stateOverrides,
|
|
488
731
|
);
|
|
489
732
|
this.log.debug(`Simulated validateHeader`);
|
|
@@ -516,8 +759,15 @@ export class SequencerPublisher {
|
|
|
516
759
|
const request = this.buildInvalidateCheckpointRequest(validationResult);
|
|
517
760
|
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
|
|
518
761
|
|
|
762
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
763
|
+
|
|
519
764
|
try {
|
|
520
|
-
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
765
|
+
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
766
|
+
request,
|
|
767
|
+
undefined,
|
|
768
|
+
undefined,
|
|
769
|
+
mergeAbis([request.abi ?? [], ErrorsAbi]),
|
|
770
|
+
);
|
|
521
771
|
this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} succeeded`, {
|
|
522
772
|
...logData,
|
|
523
773
|
request,
|
|
@@ -529,6 +779,7 @@ export class SequencerPublisher {
|
|
|
529
779
|
gasUsed,
|
|
530
780
|
checkpointNumber,
|
|
531
781
|
forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
|
|
782
|
+
lastArchive: validationResult.checkpoint.lastArchive,
|
|
532
783
|
reason,
|
|
533
784
|
};
|
|
534
785
|
} catch (err) {
|
|
@@ -536,13 +787,13 @@ export class SequencerPublisher {
|
|
|
536
787
|
|
|
537
788
|
// If the error is due to the checkpoint not being in the pending chain, and it was indeed removed by someone else,
|
|
538
789
|
// we can safely ignore it and return undefined so we go ahead with checkpoint building.
|
|
539
|
-
if (viemError.message?.includes('
|
|
790
|
+
if (viemError.message?.includes('Rollup__CheckpointNotInPendingChain')) {
|
|
540
791
|
this.log.verbose(
|
|
541
792
|
`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
|
|
542
793
|
{ ...logData, request, error: viemError.message },
|
|
543
794
|
);
|
|
544
|
-
const
|
|
545
|
-
if (
|
|
795
|
+
const latestProposedCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
796
|
+
if (latestProposedCheckpointNumber < checkpointNumber) {
|
|
546
797
|
this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, { ...logData });
|
|
547
798
|
return undefined;
|
|
548
799
|
} else {
|
|
@@ -562,6 +813,18 @@ export class SequencerPublisher {
|
|
|
562
813
|
|
|
563
814
|
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
564
815
|
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
|
|
816
|
+
this.backupFailedTx({
|
|
817
|
+
id: keccak256(request.data!),
|
|
818
|
+
failureType: 'simulation',
|
|
819
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
820
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
821
|
+
error: { message: viemError.message, name: viemError.name },
|
|
822
|
+
context: {
|
|
823
|
+
actions: [`invalidate-${reason}`],
|
|
824
|
+
checkpointNumber,
|
|
825
|
+
sender: this.getSenderAddress().toString(),
|
|
826
|
+
},
|
|
827
|
+
});
|
|
565
828
|
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
|
|
566
829
|
}
|
|
567
830
|
}
|
|
@@ -604,27 +867,13 @@ export class SequencerPublisher {
|
|
|
604
867
|
checkpoint: Checkpoint,
|
|
605
868
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
606
869
|
attestationsAndSignersSignature: Signature,
|
|
607
|
-
options: {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
// If we have no attestations, we still need to provide the empty attestations
|
|
613
|
-
// so that the committee is recalculated correctly
|
|
614
|
-
// const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
615
|
-
// if (ignoreSignatures) {
|
|
616
|
-
// const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
|
|
617
|
-
// if (!committee) {
|
|
618
|
-
// this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
619
|
-
// throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
620
|
-
// }
|
|
621
|
-
// attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
622
|
-
// CommitteeAttestation.fromAddress(committeeMember),
|
|
623
|
-
// );
|
|
624
|
-
// }
|
|
625
|
-
|
|
870
|
+
options: {
|
|
871
|
+
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
872
|
+
forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
|
|
873
|
+
},
|
|
874
|
+
): Promise<void> {
|
|
626
875
|
const blobFields = checkpoint.toBlobFields();
|
|
627
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
876
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
628
877
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
629
878
|
|
|
630
879
|
const args = [
|
|
@@ -632,7 +881,7 @@ export class SequencerPublisher {
|
|
|
632
881
|
header: checkpoint.header.toViem(),
|
|
633
882
|
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
634
883
|
oracleInput: {
|
|
635
|
-
feeAssetPriceModifier:
|
|
884
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
636
885
|
},
|
|
637
886
|
},
|
|
638
887
|
attestationsAndSigners.getPackedAttestations(),
|
|
@@ -641,13 +890,11 @@ export class SequencerPublisher {
|
|
|
641
890
|
blobInput,
|
|
642
891
|
] as const;
|
|
643
892
|
|
|
644
|
-
await this.simulateProposeTx(args,
|
|
645
|
-
return ts;
|
|
893
|
+
await this.simulateProposeTx(args, options);
|
|
646
894
|
}
|
|
647
895
|
|
|
648
896
|
private async enqueueCastSignalHelper(
|
|
649
897
|
slotNumber: SlotNumber,
|
|
650
|
-
timestamp: bigint,
|
|
651
898
|
signalType: GovernanceSignalAction,
|
|
652
899
|
payload: EthAddress,
|
|
653
900
|
base: IEmpireBase,
|
|
@@ -681,6 +928,32 @@ export class SequencerPublisher {
|
|
|
681
928
|
return false;
|
|
682
929
|
}
|
|
683
930
|
|
|
931
|
+
// Check if payload was already submitted to governance
|
|
932
|
+
const cacheKey = payload.toString();
|
|
933
|
+
if (!this.payloadProposedCache.has(cacheKey)) {
|
|
934
|
+
try {
|
|
935
|
+
const l1StartBlock = await this.rollupContract.getL1StartBlock();
|
|
936
|
+
const proposed = await retry(
|
|
937
|
+
() => base.hasPayloadBeenProposed(payload.toString(), l1StartBlock),
|
|
938
|
+
'Check if payload was proposed',
|
|
939
|
+
makeBackoff([0, 1, 2]),
|
|
940
|
+
this.log,
|
|
941
|
+
true,
|
|
942
|
+
);
|
|
943
|
+
if (proposed) {
|
|
944
|
+
this.payloadProposedCache.add(cacheKey);
|
|
945
|
+
}
|
|
946
|
+
} catch (err) {
|
|
947
|
+
this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
|
|
948
|
+
return false;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
if (this.payloadProposedCache.has(cacheKey)) {
|
|
953
|
+
this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
|
|
954
|
+
return false;
|
|
955
|
+
}
|
|
956
|
+
|
|
684
957
|
const cachedLastVote = this.lastActions[signalType];
|
|
685
958
|
this.lastActions[signalType] = slotNumber;
|
|
686
959
|
const action = signalType;
|
|
@@ -699,11 +972,30 @@ export class SequencerPublisher {
|
|
|
699
972
|
lastValidL2Slot: slotNumber,
|
|
700
973
|
});
|
|
701
974
|
|
|
975
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
976
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
977
|
+
|
|
702
978
|
try {
|
|
703
|
-
await this.l1TxUtils.simulate(request, { time: timestamp }, [], ErrorsAbi);
|
|
979
|
+
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
704
980
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
705
981
|
} catch (err) {
|
|
706
|
-
|
|
982
|
+
const viemError = formatViemError(err);
|
|
983
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError, {
|
|
984
|
+
simulationTimestamp: timestamp,
|
|
985
|
+
l1BlockNumber,
|
|
986
|
+
});
|
|
987
|
+
this.backupFailedTx({
|
|
988
|
+
id: keccak256(request.data!),
|
|
989
|
+
failureType: 'simulation',
|
|
990
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
991
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
992
|
+
error: { message: viemError.message, name: viemError.name },
|
|
993
|
+
context: {
|
|
994
|
+
actions: [action],
|
|
995
|
+
slot: slotNumber,
|
|
996
|
+
sender: this.getSenderAddress().toString(),
|
|
997
|
+
},
|
|
998
|
+
});
|
|
707
999
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
708
1000
|
}
|
|
709
1001
|
|
|
@@ -754,19 +1046,16 @@ export class SequencerPublisher {
|
|
|
754
1046
|
/**
|
|
755
1047
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
756
1048
|
* @param slotNumber - The slot number to cast a signal for.
|
|
757
|
-
* @param timestamp - The timestamp of the slot to cast a signal for.
|
|
758
1049
|
* @returns True if the signal was successfully enqueued, false otherwise.
|
|
759
1050
|
*/
|
|
760
1051
|
public enqueueGovernanceCastSignal(
|
|
761
1052
|
governancePayload: EthAddress,
|
|
762
1053
|
slotNumber: SlotNumber,
|
|
763
|
-
timestamp: bigint,
|
|
764
1054
|
signerAddress: EthAddress,
|
|
765
1055
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
766
1056
|
): Promise<boolean> {
|
|
767
1057
|
return this.enqueueCastSignalHelper(
|
|
768
1058
|
slotNumber,
|
|
769
|
-
timestamp,
|
|
770
1059
|
'governance-signal',
|
|
771
1060
|
governancePayload,
|
|
772
1061
|
this.govProposerContract,
|
|
@@ -779,7 +1068,6 @@ export class SequencerPublisher {
|
|
|
779
1068
|
public async enqueueSlashingActions(
|
|
780
1069
|
actions: ProposerSlashAction[],
|
|
781
1070
|
slotNumber: SlotNumber,
|
|
782
|
-
timestamp: bigint,
|
|
783
1071
|
signerAddress: EthAddress,
|
|
784
1072
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
785
1073
|
): Promise<boolean> {
|
|
@@ -800,7 +1088,6 @@ export class SequencerPublisher {
|
|
|
800
1088
|
});
|
|
801
1089
|
await this.enqueueCastSignalHelper(
|
|
802
1090
|
slotNumber,
|
|
803
|
-
timestamp,
|
|
804
1091
|
'empire-slashing-signal',
|
|
805
1092
|
action.payload,
|
|
806
1093
|
this.slashingProposerContract,
|
|
@@ -819,7 +1106,6 @@ export class SequencerPublisher {
|
|
|
819
1106
|
(receipt: TransactionReceipt) =>
|
|
820
1107
|
!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs),
|
|
821
1108
|
slotNumber,
|
|
822
|
-
timestamp,
|
|
823
1109
|
);
|
|
824
1110
|
break;
|
|
825
1111
|
}
|
|
@@ -837,7 +1123,6 @@ export class SequencerPublisher {
|
|
|
837
1123
|
request,
|
|
838
1124
|
(receipt: TransactionReceipt) => !!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs),
|
|
839
1125
|
slotNumber,
|
|
840
|
-
timestamp,
|
|
841
1126
|
);
|
|
842
1127
|
break;
|
|
843
1128
|
}
|
|
@@ -861,7 +1146,6 @@ export class SequencerPublisher {
|
|
|
861
1146
|
request,
|
|
862
1147
|
(receipt: TransactionReceipt) => !!tallySlashingProposer.tryExtractVoteCastEvent(receipt.logs),
|
|
863
1148
|
slotNumber,
|
|
864
|
-
timestamp,
|
|
865
1149
|
);
|
|
866
1150
|
break;
|
|
867
1151
|
}
|
|
@@ -883,7 +1167,6 @@ export class SequencerPublisher {
|
|
|
883
1167
|
request,
|
|
884
1168
|
(receipt: TransactionReceipt) => !!tallySlashingProposer.tryExtractRoundExecutedEvent(receipt.logs),
|
|
885
1169
|
slotNumber,
|
|
886
|
-
timestamp,
|
|
887
1170
|
);
|
|
888
1171
|
break;
|
|
889
1172
|
}
|
|
@@ -903,30 +1186,33 @@ export class SequencerPublisher {
|
|
|
903
1186
|
checkpoint: Checkpoint,
|
|
904
1187
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
905
1188
|
attestationsAndSignersSignature: Signature,
|
|
906
|
-
opts: {
|
|
1189
|
+
opts: {
|
|
1190
|
+
txTimeoutAt?: Date;
|
|
1191
|
+
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
1192
|
+
forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
|
|
1193
|
+
} = {},
|
|
907
1194
|
): Promise<void> {
|
|
908
1195
|
const checkpointHeader = checkpoint.header;
|
|
909
1196
|
|
|
910
1197
|
const blobFields = checkpoint.toBlobFields();
|
|
911
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
1198
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
912
1199
|
|
|
913
|
-
const proposeTxArgs = {
|
|
1200
|
+
const proposeTxArgs: L1ProcessArgs = {
|
|
914
1201
|
header: checkpointHeader,
|
|
915
1202
|
archive: checkpoint.archive.root.toBuffer(),
|
|
916
1203
|
blobs,
|
|
917
1204
|
attestationsAndSigners,
|
|
918
1205
|
attestationsAndSignersSignature,
|
|
1206
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
919
1207
|
};
|
|
920
1208
|
|
|
921
|
-
let ts: bigint;
|
|
922
|
-
|
|
923
1209
|
try {
|
|
924
1210
|
// @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
|
|
925
1211
|
// This means that we can avoid the simulation issues in later checks.
|
|
926
1212
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
927
1213
|
// make time consistency checks break.
|
|
928
1214
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
929
|
-
|
|
1215
|
+
await this.validateCheckpointForSubmission(
|
|
930
1216
|
checkpoint,
|
|
931
1217
|
attestationsAndSigners,
|
|
932
1218
|
attestationsAndSignersSignature,
|
|
@@ -942,7 +1228,7 @@ export class SequencerPublisher {
|
|
|
942
1228
|
}
|
|
943
1229
|
|
|
944
1230
|
this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
|
|
945
|
-
await this.addProposeTx(checkpoint, proposeTxArgs, opts
|
|
1231
|
+
await this.addProposeTx(checkpoint, proposeTxArgs, opts);
|
|
946
1232
|
}
|
|
947
1233
|
|
|
948
1234
|
public enqueueInvalidateCheckpoint(
|
|
@@ -985,8 +1271,8 @@ export class SequencerPublisher {
|
|
|
985
1271
|
request: L1TxRequest,
|
|
986
1272
|
checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
|
|
987
1273
|
slotNumber: SlotNumber,
|
|
988
|
-
timestamp: bigint,
|
|
989
1274
|
) {
|
|
1275
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
990
1276
|
const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
|
|
991
1277
|
if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
|
|
992
1278
|
this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
|
|
@@ -998,13 +1284,31 @@ export class SequencerPublisher {
|
|
|
998
1284
|
|
|
999
1285
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
1000
1286
|
|
|
1287
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1288
|
+
|
|
1001
1289
|
let gasUsed: bigint;
|
|
1290
|
+
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
1291
|
+
|
|
1002
1292
|
try {
|
|
1003
|
-
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [],
|
|
1293
|
+
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi));
|
|
1004
1294
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
1005
1295
|
} catch (err) {
|
|
1006
|
-
const viemError = formatViemError(err);
|
|
1296
|
+
const viemError = formatViemError(err, simulateAbi);
|
|
1007
1297
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1298
|
+
|
|
1299
|
+
this.backupFailedTx({
|
|
1300
|
+
id: keccak256(request.data!),
|
|
1301
|
+
failureType: 'simulation',
|
|
1302
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
1303
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1304
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1305
|
+
context: {
|
|
1306
|
+
actions: [action],
|
|
1307
|
+
slot: slotNumber,
|
|
1308
|
+
sender: this.getSenderAddress().toString(),
|
|
1309
|
+
},
|
|
1310
|
+
});
|
|
1311
|
+
|
|
1008
1312
|
return false;
|
|
1009
1313
|
}
|
|
1010
1314
|
|
|
@@ -1012,10 +1316,14 @@ export class SequencerPublisher {
|
|
|
1012
1316
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(gasUsed) * 64) / 63)));
|
|
1013
1317
|
logData.gasLimit = gasLimit;
|
|
1014
1318
|
|
|
1319
|
+
// Store the ABI used for simulation on the request so Multicall3.forward can decode errors
|
|
1320
|
+
// when the tx is sent and a revert is diagnosed via simulation.
|
|
1321
|
+
const requestWithAbi = { ...request, abi: simulateAbi };
|
|
1322
|
+
|
|
1015
1323
|
this.log.debug(`Enqueuing ${action}`, logData);
|
|
1016
1324
|
this.addRequest({
|
|
1017
1325
|
action,
|
|
1018
|
-
request,
|
|
1326
|
+
request: requestWithAbi,
|
|
1019
1327
|
gasConfig: { gasLimit },
|
|
1020
1328
|
lastValidL2Slot: slotNumber,
|
|
1021
1329
|
checkSuccess: (_req, result) => {
|
|
@@ -1040,6 +1348,7 @@ export class SequencerPublisher {
|
|
|
1040
1348
|
*/
|
|
1041
1349
|
public interrupt() {
|
|
1042
1350
|
this.interrupted = true;
|
|
1351
|
+
this.interruptibleSleep.interrupt();
|
|
1043
1352
|
this.l1TxUtils.interrupt();
|
|
1044
1353
|
}
|
|
1045
1354
|
|
|
@@ -1051,7 +1360,6 @@ export class SequencerPublisher {
|
|
|
1051
1360
|
|
|
1052
1361
|
private async prepareProposeTx(
|
|
1053
1362
|
encodedData: L1ProcessArgs,
|
|
1054
|
-
timestamp: bigint,
|
|
1055
1363
|
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1056
1364
|
) {
|
|
1057
1365
|
const kzg = Blob.getViemKzgInstance();
|
|
@@ -1084,9 +1392,27 @@ export class SequencerPublisher {
|
|
|
1084
1392
|
kzg,
|
|
1085
1393
|
},
|
|
1086
1394
|
)
|
|
1087
|
-
.catch(err => {
|
|
1088
|
-
const
|
|
1089
|
-
this.log.error(`Failed to validate blobs`, message, { metaMessages });
|
|
1395
|
+
.catch(async err => {
|
|
1396
|
+
const viemError = formatViemError(err);
|
|
1397
|
+
this.log.error(`Failed to validate blobs`, viemError.message, { metaMessages: viemError.metaMessages });
|
|
1398
|
+
const validateBlobsData = encodeFunctionData({
|
|
1399
|
+
abi: RollupAbi,
|
|
1400
|
+
functionName: 'validateBlobs',
|
|
1401
|
+
args: [blobInput],
|
|
1402
|
+
});
|
|
1403
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1404
|
+
this.backupFailedTx({
|
|
1405
|
+
id: keccak256(validateBlobsData),
|
|
1406
|
+
failureType: 'simulation',
|
|
1407
|
+
request: { to: this.rollupContract.address as Hex, data: validateBlobsData },
|
|
1408
|
+
blobData: encodedData.blobs.map(b => toHex(b.data)) as Hex[],
|
|
1409
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1410
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1411
|
+
context: {
|
|
1412
|
+
actions: ['validate-blobs'],
|
|
1413
|
+
sender: this.getSenderAddress().toString(),
|
|
1414
|
+
},
|
|
1415
|
+
});
|
|
1090
1416
|
throw new Error('Failed to validate blobs');
|
|
1091
1417
|
});
|
|
1092
1418
|
}
|
|
@@ -1097,8 +1423,7 @@ export class SequencerPublisher {
|
|
|
1097
1423
|
header: encodedData.header.toViem(),
|
|
1098
1424
|
archive: toHex(encodedData.archive),
|
|
1099
1425
|
oracleInput: {
|
|
1100
|
-
|
|
1101
|
-
feeAssetPriceModifier: 0n,
|
|
1426
|
+
feeAssetPriceModifier: encodedData.feeAssetPriceModifier,
|
|
1102
1427
|
},
|
|
1103
1428
|
},
|
|
1104
1429
|
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
@@ -1107,7 +1432,7 @@ export class SequencerPublisher {
|
|
|
1107
1432
|
blobInput,
|
|
1108
1433
|
] as const;
|
|
1109
1434
|
|
|
1110
|
-
const { rollupData, simulationResult } = await this.simulateProposeTx(args,
|
|
1435
|
+
const { rollupData, simulationResult } = await this.simulateProposeTx(args, options);
|
|
1111
1436
|
|
|
1112
1437
|
return { args, blobEvaluationGas, rollupData, simulationResult };
|
|
1113
1438
|
}
|
|
@@ -1115,7 +1440,6 @@ export class SequencerPublisher {
|
|
|
1115
1440
|
/**
|
|
1116
1441
|
* Simulates the propose tx with eth_simulateV1
|
|
1117
1442
|
* @param args - The propose tx args
|
|
1118
|
-
* @param timestamp - The timestamp to simulate proposal at
|
|
1119
1443
|
* @returns The simulation result
|
|
1120
1444
|
*/
|
|
1121
1445
|
private async simulateProposeTx(
|
|
@@ -1124,7 +1448,7 @@ export class SequencerPublisher {
|
|
|
1124
1448
|
readonly header: ViemHeader;
|
|
1125
1449
|
readonly archive: `0x${string}`;
|
|
1126
1450
|
readonly oracleInput: {
|
|
1127
|
-
readonly feeAssetPriceModifier:
|
|
1451
|
+
readonly feeAssetPriceModifier: bigint;
|
|
1128
1452
|
};
|
|
1129
1453
|
},
|
|
1130
1454
|
ViemCommitteeAttestations,
|
|
@@ -1132,8 +1456,10 @@ export class SequencerPublisher {
|
|
|
1132
1456
|
ViemSignature,
|
|
1133
1457
|
`0x${string}`,
|
|
1134
1458
|
],
|
|
1135
|
-
|
|
1136
|
-
|
|
1459
|
+
options: {
|
|
1460
|
+
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
1461
|
+
forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
|
|
1462
|
+
},
|
|
1137
1463
|
) {
|
|
1138
1464
|
const rollupData = encodeFunctionData({
|
|
1139
1465
|
abi: RollupAbi,
|
|
@@ -1141,13 +1467,23 @@ export class SequencerPublisher {
|
|
|
1141
1467
|
args,
|
|
1142
1468
|
});
|
|
1143
1469
|
|
|
1144
|
-
// override the
|
|
1470
|
+
// override the proposed checkpoint number if requested
|
|
1145
1471
|
const forcePendingCheckpointNumberStateDiff = (
|
|
1146
1472
|
options.forcePendingCheckpointNumber !== undefined
|
|
1147
1473
|
? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber)
|
|
1148
1474
|
: []
|
|
1149
1475
|
).flatMap(override => override.stateDiff ?? []);
|
|
1150
1476
|
|
|
1477
|
+
// override the fee header for a specific checkpoint number if requested (used when pipelining)
|
|
1478
|
+
const forceProposedFeeHeaderStateDiff = (
|
|
1479
|
+
options.forceProposedFeeHeader !== undefined
|
|
1480
|
+
? await this.rollupContract.makeFeeHeaderOverride(
|
|
1481
|
+
options.forceProposedFeeHeader.checkpointNumber,
|
|
1482
|
+
options.forceProposedFeeHeader.feeHeader,
|
|
1483
|
+
)
|
|
1484
|
+
: []
|
|
1485
|
+
).flatMap(override => override.stateDiff ?? []);
|
|
1486
|
+
|
|
1151
1487
|
const stateOverrides: StateOverride = [
|
|
1152
1488
|
{
|
|
1153
1489
|
address: this.rollupContract.address,
|
|
@@ -1155,6 +1491,7 @@ export class SequencerPublisher {
|
|
|
1155
1491
|
stateDiff: [
|
|
1156
1492
|
{ slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
|
|
1157
1493
|
...forcePendingCheckpointNumberStateDiff,
|
|
1494
|
+
...forceProposedFeeHeaderStateDiff,
|
|
1158
1495
|
],
|
|
1159
1496
|
},
|
|
1160
1497
|
];
|
|
@@ -1166,25 +1503,27 @@ export class SequencerPublisher {
|
|
|
1166
1503
|
});
|
|
1167
1504
|
}
|
|
1168
1505
|
|
|
1506
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1507
|
+
const simTs = this.getSimulationTimestamp(SlotNumber.fromBigInt(args[0].header.slotNumber));
|
|
1508
|
+
|
|
1169
1509
|
const simulationResult = await this.l1TxUtils
|
|
1170
1510
|
.simulate(
|
|
1171
1511
|
{
|
|
1172
1512
|
to: this.rollupContract.address,
|
|
1173
1513
|
data: rollupData,
|
|
1174
|
-
gas:
|
|
1514
|
+
gas: MAX_L1_TX_LIMIT,
|
|
1175
1515
|
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1176
1516
|
},
|
|
1177
1517
|
{
|
|
1178
|
-
|
|
1179
|
-
time: timestamp + 1n,
|
|
1518
|
+
time: simTs,
|
|
1180
1519
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1181
|
-
gasLimit:
|
|
1520
|
+
gasLimit: MAX_L1_TX_LIMIT * 2n,
|
|
1182
1521
|
},
|
|
1183
1522
|
stateOverrides,
|
|
1184
1523
|
RollupAbi,
|
|
1185
1524
|
{
|
|
1186
1525
|
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
1187
|
-
fallbackGasEstimate:
|
|
1526
|
+
fallbackGasEstimate: MAX_L1_TX_LIMIT,
|
|
1188
1527
|
},
|
|
1189
1528
|
)
|
|
1190
1529
|
.catch(err => {
|
|
@@ -1194,11 +1533,23 @@ export class SequencerPublisher {
|
|
|
1194
1533
|
this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
|
|
1195
1534
|
// Return a minimal simulation result with the fallback gas estimate
|
|
1196
1535
|
return {
|
|
1197
|
-
gasUsed:
|
|
1536
|
+
gasUsed: MAX_L1_TX_LIMIT,
|
|
1198
1537
|
logs: [],
|
|
1199
1538
|
};
|
|
1200
1539
|
}
|
|
1201
|
-
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1540
|
+
this.log.error(`Failed to simulate propose tx`, viemError, { simulationTimestamp: simTs });
|
|
1541
|
+
this.backupFailedTx({
|
|
1542
|
+
id: keccak256(rollupData),
|
|
1543
|
+
failureType: 'simulation',
|
|
1544
|
+
request: { to: this.rollupContract.address, data: rollupData },
|
|
1545
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1546
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1547
|
+
context: {
|
|
1548
|
+
actions: ['propose'],
|
|
1549
|
+
slot: Number(args[0].header.slotNumber),
|
|
1550
|
+
sender: this.getSenderAddress().toString(),
|
|
1551
|
+
},
|
|
1552
|
+
});
|
|
1202
1553
|
throw err;
|
|
1203
1554
|
});
|
|
1204
1555
|
|
|
@@ -1208,17 +1559,16 @@ export class SequencerPublisher {
|
|
|
1208
1559
|
private async addProposeTx(
|
|
1209
1560
|
checkpoint: Checkpoint,
|
|
1210
1561
|
encodedData: L1ProcessArgs,
|
|
1211
|
-
opts: {
|
|
1212
|
-
|
|
1562
|
+
opts: {
|
|
1563
|
+
txTimeoutAt?: Date;
|
|
1564
|
+
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
1565
|
+
forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
|
|
1566
|
+
} = {},
|
|
1213
1567
|
): Promise<void> {
|
|
1214
1568
|
const slot = checkpoint.header.slotNumber;
|
|
1215
1569
|
const timer = new Timer();
|
|
1216
1570
|
const kzg = Blob.getViemKzgInstance();
|
|
1217
|
-
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
1218
|
-
encodedData,
|
|
1219
|
-
timestamp,
|
|
1220
|
-
opts,
|
|
1221
|
-
);
|
|
1571
|
+
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, opts);
|
|
1222
1572
|
const startBlock = await this.l1TxUtils.getBlockNumber();
|
|
1223
1573
|
const gasLimit = this.l1TxUtils.bumpGasLimit(
|
|
1224
1574
|
BigInt(Math.ceil((Number(simulationResult.gasUsed) * 64) / 63)) +
|
|
@@ -1294,4 +1644,17 @@ export class SequencerPublisher {
|
|
|
1294
1644
|
},
|
|
1295
1645
|
});
|
|
1296
1646
|
}
|
|
1647
|
+
|
|
1648
|
+
/** Returns the timestamp of the last L1 slot within a given L2 slot. Used as the simulation timestamp
|
|
1649
|
+
* for eth_simulateV1 calls, since it's guaranteed to be greater than any L1 block produced during the slot. */
|
|
1650
|
+
private getSimulationTimestamp(slot: SlotNumber): bigint {
|
|
1651
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1652
|
+
return getLastL1SlotTimestampForL2Slot(slot, l1Constants);
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
/** Returns the timestamp of the next L1 slot boundary after now. */
|
|
1656
|
+
private getNextL1SlotTimestamp(): bigint {
|
|
1657
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1658
|
+
return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
|
|
1659
|
+
}
|
|
1297
1660
|
}
|