@aztec/sequencer-client 0.0.1-commit.c2595eba → 0.0.1-commit.c2eed6949
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 +14 -10
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +22 -21
- 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.d.ts +33 -10
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +371 -57
- package/dest/sequencer/checkpoint_proposal_job.d.ts +39 -10
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +287 -167
- 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 +97 -15
- package/dest/sequencer/sequencer.d.ts +30 -15
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +95 -82
- 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 +23 -24
- 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.ts +371 -70
- package/src/sequencer/checkpoint_proposal_job.ts +392 -193
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/metrics.ts +106 -18
- package/src/sequencer/sequencer.ts +131 -94
- 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,7 @@ 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,
|
|
7
8
|
type GovernanceProposerContract,
|
|
8
9
|
type IEmpireBase,
|
|
9
10
|
MULTI_CALL_3_ADDRESS,
|
|
@@ -18,33 +19,48 @@ import {
|
|
|
18
19
|
type L1BlobInputs,
|
|
19
20
|
type L1TxConfig,
|
|
20
21
|
type L1TxRequest,
|
|
22
|
+
type L1TxUtils,
|
|
23
|
+
MAX_L1_TX_LIMIT,
|
|
21
24
|
type TransactionStats,
|
|
22
25
|
WEI_CONST,
|
|
23
26
|
} from '@aztec/ethereum/l1-tx-utils';
|
|
24
|
-
import
|
|
25
|
-
import { FormattedViemError, formatViemError, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
27
|
+
import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
26
28
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
27
29
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
28
30
|
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
31
|
+
import { trimmedBytesLength } from '@aztec/foundation/buffer';
|
|
29
32
|
import { pick } from '@aztec/foundation/collection';
|
|
30
33
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
34
|
+
import { TimeoutError } from '@aztec/foundation/error';
|
|
31
35
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
32
36
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
33
37
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
38
|
+
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
34
39
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
35
40
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
36
41
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
37
42
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
38
43
|
import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
39
44
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
45
|
+
import { getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
40
46
|
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
41
47
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
42
48
|
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
43
49
|
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
44
50
|
|
|
45
|
-
import {
|
|
46
|
-
|
|
47
|
-
|
|
51
|
+
import {
|
|
52
|
+
type Hex,
|
|
53
|
+
type StateOverride,
|
|
54
|
+
type TransactionReceipt,
|
|
55
|
+
type TypedDataDefinition,
|
|
56
|
+
encodeFunctionData,
|
|
57
|
+
keccak256,
|
|
58
|
+
multicall3Abi,
|
|
59
|
+
toHex,
|
|
60
|
+
} from 'viem';
|
|
61
|
+
|
|
62
|
+
import type { SequencerPublisherConfig } from './config.js';
|
|
63
|
+
import { type FailedL1Tx, type L1TxFailedStore, createL1TxFailedStore } from './l1_tx_failed_store/index.js';
|
|
48
64
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
49
65
|
|
|
50
66
|
/** Arguments to the process method of the rollup contract */
|
|
@@ -59,6 +75,8 @@ type L1ProcessArgs = {
|
|
|
59
75
|
attestationsAndSigners: CommitteeAttestationsAndSigners;
|
|
60
76
|
/** Attestations and signers signature */
|
|
61
77
|
attestationsAndSignersSignature: Signature;
|
|
78
|
+
/** The fee asset price modifier in basis points (from oracle) */
|
|
79
|
+
feeAssetPriceModifier: bigint;
|
|
62
80
|
};
|
|
63
81
|
|
|
64
82
|
export const Actions = [
|
|
@@ -104,6 +122,7 @@ export class SequencerPublisher {
|
|
|
104
122
|
private interrupted = false;
|
|
105
123
|
private metrics: SequencerPublisherMetrics;
|
|
106
124
|
public epochCache: EpochCache;
|
|
125
|
+
private failedTxStore?: Promise<L1TxFailedStore | undefined>;
|
|
107
126
|
|
|
108
127
|
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
109
128
|
protected slashingLog = createLogger('sequencer:publisher:slashing');
|
|
@@ -111,21 +130,26 @@ export class SequencerPublisher {
|
|
|
111
130
|
protected lastActions: Partial<Record<Action, SlotNumber>> = {};
|
|
112
131
|
|
|
113
132
|
private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
|
|
133
|
+
private payloadProposedCache: Set<string> = new Set<string>();
|
|
114
134
|
|
|
115
135
|
protected log: Logger;
|
|
116
136
|
protected ethereumSlotDuration: bigint;
|
|
137
|
+
protected aztecSlotDuration: bigint;
|
|
138
|
+
private dateProvider: DateProvider;
|
|
117
139
|
|
|
118
140
|
private blobClient: BlobClientInterface;
|
|
119
141
|
|
|
120
142
|
/** Address to use for simulations in fisherman mode (actual proposer's address) */
|
|
121
143
|
private proposerAddressForSimulation?: EthAddress;
|
|
122
144
|
|
|
145
|
+
/** Optional callback to obtain a replacement publisher when the current one fails to send. */
|
|
146
|
+
private getNextPublisher?: (excludeAddresses: EthAddress[]) => Promise<L1TxUtils | undefined>;
|
|
147
|
+
|
|
123
148
|
/** L1 fee analyzer for fisherman mode */
|
|
124
149
|
private l1FeeAnalyzer?: L1FeeAnalyzer;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
public static PROPOSE_GAS_GUESS: bigint = 12_000_000n;
|
|
150
|
+
|
|
151
|
+
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */
|
|
152
|
+
private feeAssetPriceOracle: FeeAssetPriceOracle;
|
|
129
153
|
|
|
130
154
|
// A CALL to a cold address is 2700 gas
|
|
131
155
|
public static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
@@ -133,7 +157,7 @@ export class SequencerPublisher {
|
|
|
133
157
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
134
158
|
public static VOTE_GAS_GUESS: bigint = 800_000n;
|
|
135
159
|
|
|
136
|
-
public l1TxUtils:
|
|
160
|
+
public l1TxUtils: L1TxUtils;
|
|
137
161
|
public rollupContract: RollupContract;
|
|
138
162
|
public govProposerContract: GovernanceProposerContract;
|
|
139
163
|
public slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
@@ -144,11 +168,12 @@ export class SequencerPublisher {
|
|
|
144
168
|
protected requests: RequestWithExpiry[] = [];
|
|
145
169
|
|
|
146
170
|
constructor(
|
|
147
|
-
private config:
|
|
171
|
+
private config: Pick<SequencerPublisherConfig, 'fishermanMode' | 'l1TxFailedStore'> &
|
|
172
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration'> & { l1ChainId: number },
|
|
148
173
|
deps: {
|
|
149
174
|
telemetry?: TelemetryClient;
|
|
150
175
|
blobClient: BlobClientInterface;
|
|
151
|
-
l1TxUtils:
|
|
176
|
+
l1TxUtils: L1TxUtils;
|
|
152
177
|
rollupContract: RollupContract;
|
|
153
178
|
slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
154
179
|
governanceProposerContract: GovernanceProposerContract;
|
|
@@ -158,10 +183,13 @@ export class SequencerPublisher {
|
|
|
158
183
|
metrics: SequencerPublisherMetrics;
|
|
159
184
|
lastActions: Partial<Record<Action, SlotNumber>>;
|
|
160
185
|
log?: Logger;
|
|
186
|
+
getNextPublisher?: (excludeAddresses: EthAddress[]) => Promise<L1TxUtils | undefined>;
|
|
161
187
|
},
|
|
162
188
|
) {
|
|
163
189
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
164
190
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
191
|
+
this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
|
|
192
|
+
this.dateProvider = deps.dateProvider;
|
|
165
193
|
this.epochCache = deps.epochCache;
|
|
166
194
|
this.lastActions = deps.lastActions;
|
|
167
195
|
|
|
@@ -171,6 +199,7 @@ export class SequencerPublisher {
|
|
|
171
199
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
172
200
|
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
173
201
|
this.l1TxUtils = deps.l1TxUtils;
|
|
202
|
+
this.getNextPublisher = deps.getNextPublisher;
|
|
174
203
|
|
|
175
204
|
this.rollupContract = deps.rollupContract;
|
|
176
205
|
|
|
@@ -192,12 +221,52 @@ export class SequencerPublisher {
|
|
|
192
221
|
createLogger('sequencer:publisher:fee-analyzer'),
|
|
193
222
|
);
|
|
194
223
|
}
|
|
224
|
+
|
|
225
|
+
// Initialize fee asset price oracle
|
|
226
|
+
this.feeAssetPriceOracle = new FeeAssetPriceOracle(
|
|
227
|
+
this.l1TxUtils.client,
|
|
228
|
+
this.rollupContract,
|
|
229
|
+
createLogger('sequencer:publisher:price-oracle'),
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
// Initialize failed L1 tx store (optional, for test networks)
|
|
233
|
+
this.failedTxStore = createL1TxFailedStore(config.l1TxFailedStore, this.log);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Backs up a failed L1 transaction to the configured store for debugging.
|
|
238
|
+
* Does nothing if no store is configured.
|
|
239
|
+
*/
|
|
240
|
+
private backupFailedTx(failedTx: Omit<FailedL1Tx, 'timestamp'>): void {
|
|
241
|
+
if (!this.failedTxStore) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const tx: FailedL1Tx = {
|
|
246
|
+
...failedTx,
|
|
247
|
+
timestamp: Date.now(),
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// Fire and forget - don't block on backup
|
|
251
|
+
void this.failedTxStore
|
|
252
|
+
.then(store => store?.saveFailedTx(tx))
|
|
253
|
+
.catch(err => {
|
|
254
|
+
this.log.warn(`Failed to backup failed L1 tx to store`, err);
|
|
255
|
+
});
|
|
195
256
|
}
|
|
196
257
|
|
|
197
258
|
public getRollupContract(): RollupContract {
|
|
198
259
|
return this.rollupContract;
|
|
199
260
|
}
|
|
200
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Gets the fee asset price modifier from the oracle.
|
|
264
|
+
* Returns 0n if the oracle query fails.
|
|
265
|
+
*/
|
|
266
|
+
public getFeeAssetPriceModifier(): Promise<bigint> {
|
|
267
|
+
return this.feeAssetPriceOracle.computePriceModifier();
|
|
268
|
+
}
|
|
269
|
+
|
|
201
270
|
public getSenderAddress() {
|
|
202
271
|
return this.l1TxUtils.getSenderAddress();
|
|
203
272
|
}
|
|
@@ -222,7 +291,7 @@ export class SequencerPublisher {
|
|
|
222
291
|
}
|
|
223
292
|
|
|
224
293
|
public getCurrentL2Slot(): SlotNumber {
|
|
225
|
-
return this.epochCache.
|
|
294
|
+
return this.epochCache.getSlotNow();
|
|
226
295
|
}
|
|
227
296
|
|
|
228
297
|
/**
|
|
@@ -273,7 +342,7 @@ export class SequencerPublisher {
|
|
|
273
342
|
// Start the analysis
|
|
274
343
|
const analysisId = await this.l1FeeAnalyzer.startAnalysis(
|
|
275
344
|
l2SlotNumber,
|
|
276
|
-
gasLimit > 0n ? gasLimit :
|
|
345
|
+
gasLimit > 0n ? gasLimit : MAX_L1_TX_LIMIT,
|
|
277
346
|
l1Requests,
|
|
278
347
|
blobConfig,
|
|
279
348
|
onComplete,
|
|
@@ -335,8 +404,8 @@ export class SequencerPublisher {
|
|
|
335
404
|
// @note - we can only have one blob config per bundle
|
|
336
405
|
// find requests with gas and blob configs
|
|
337
406
|
// See https://github.com/AztecProtocol/aztec-packages/issues/11513
|
|
338
|
-
const gasConfigs =
|
|
339
|
-
const blobConfigs =
|
|
407
|
+
const gasConfigs = validRequests.filter(request => request.gasConfig).map(request => request.gasConfig);
|
|
408
|
+
const blobConfigs = validRequests.filter(request => request.blobConfig).map(request => request.blobConfig);
|
|
340
409
|
|
|
341
410
|
if (blobConfigs.length > 1) {
|
|
342
411
|
throw new Error('Multiple blob configs found');
|
|
@@ -346,7 +415,16 @@ export class SequencerPublisher {
|
|
|
346
415
|
|
|
347
416
|
// Merge gasConfigs. Yields the sum of gasLimits, and the earliest txTimeoutAt, or undefined if no gasConfig sets them.
|
|
348
417
|
const gasLimits = gasConfigs.map(g => g?.gasLimit).filter((g): g is bigint => g !== undefined);
|
|
349
|
-
|
|
418
|
+
let gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
|
|
419
|
+
// Cap at L1 block gas limit so the node accepts the tx ("gas limit too high" otherwise).
|
|
420
|
+
const maxGas = MAX_L1_TX_LIMIT;
|
|
421
|
+
if (gasLimit !== undefined && gasLimit > maxGas) {
|
|
422
|
+
this.log.debug('Capping bundled tx gas limit to L1 max', {
|
|
423
|
+
requested: gasLimit,
|
|
424
|
+
capped: maxGas,
|
|
425
|
+
});
|
|
426
|
+
gasLimit = maxGas;
|
|
427
|
+
}
|
|
350
428
|
const txTimeoutAts = gasConfigs.map(g => g?.txTimeoutAt).filter((g): g is Date => g !== undefined);
|
|
351
429
|
const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map(g => g.getTime()))) : undefined; // earliest
|
|
352
430
|
const txConfig: RequestWithExpiry['gasConfig'] = { gasLimit, txTimeoutAt };
|
|
@@ -356,19 +434,36 @@ export class SequencerPublisher {
|
|
|
356
434
|
validRequests.sort((a, b) => compareActions(a.action, b.action));
|
|
357
435
|
|
|
358
436
|
try {
|
|
437
|
+
// Capture context for failed tx backup before sending
|
|
438
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
439
|
+
const multicallData = encodeFunctionData({
|
|
440
|
+
abi: multicall3Abi,
|
|
441
|
+
functionName: 'aggregate3',
|
|
442
|
+
args: [
|
|
443
|
+
validRequests.map(r => ({
|
|
444
|
+
target: r.request.to!,
|
|
445
|
+
callData: r.request.data!,
|
|
446
|
+
allowFailure: true,
|
|
447
|
+
})),
|
|
448
|
+
],
|
|
449
|
+
});
|
|
450
|
+
const blobDataHex = blobConfig?.blobs?.map(b => toHex(b)) as Hex[] | undefined;
|
|
451
|
+
|
|
452
|
+
const txContext = { multicallData, blobData: blobDataHex, l1BlockNumber };
|
|
453
|
+
|
|
359
454
|
this.log.debug('Forwarding transactions', {
|
|
360
455
|
validRequests: validRequests.map(request => request.action),
|
|
361
456
|
txConfig,
|
|
362
457
|
});
|
|
363
|
-
const result = await
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
458
|
+
const result = await this.forwardWithPublisherRotation(validRequests, txConfig, blobConfig);
|
|
459
|
+
if (result === undefined) {
|
|
460
|
+
return undefined;
|
|
461
|
+
}
|
|
462
|
+
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(
|
|
463
|
+
validRequests,
|
|
464
|
+
result,
|
|
465
|
+
txContext,
|
|
370
466
|
);
|
|
371
|
-
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(validRequests, result);
|
|
372
467
|
return { result, expiredActions, sentActions: validActions, successfulActions, failedActions };
|
|
373
468
|
} catch (err) {
|
|
374
469
|
const viemError = formatViemError(err);
|
|
@@ -386,16 +481,88 @@ export class SequencerPublisher {
|
|
|
386
481
|
}
|
|
387
482
|
}
|
|
388
483
|
|
|
484
|
+
/**
|
|
485
|
+
* Forwards transactions via Multicall3, rotating to the next available publisher if a send
|
|
486
|
+
* failure occurs (i.e. the tx never reached the chain).
|
|
487
|
+
* On-chain reverts and simulation errors are returned as-is without rotation.
|
|
488
|
+
*/
|
|
489
|
+
private async forwardWithPublisherRotation(
|
|
490
|
+
validRequests: RequestWithExpiry[],
|
|
491
|
+
txConfig: RequestWithExpiry['gasConfig'],
|
|
492
|
+
blobConfig: L1BlobInputs | undefined,
|
|
493
|
+
) {
|
|
494
|
+
const triedAddresses: EthAddress[] = [];
|
|
495
|
+
let currentPublisher = this.l1TxUtils;
|
|
496
|
+
|
|
497
|
+
while (true) {
|
|
498
|
+
triedAddresses.push(currentPublisher.getSenderAddress());
|
|
499
|
+
try {
|
|
500
|
+
const result = await Multicall3.forward(
|
|
501
|
+
validRequests.map(r => r.request),
|
|
502
|
+
currentPublisher,
|
|
503
|
+
txConfig,
|
|
504
|
+
blobConfig,
|
|
505
|
+
this.rollupContract.address,
|
|
506
|
+
this.log,
|
|
507
|
+
);
|
|
508
|
+
this.l1TxUtils = currentPublisher;
|
|
509
|
+
return result;
|
|
510
|
+
} catch (err) {
|
|
511
|
+
if (err instanceof TimeoutError) {
|
|
512
|
+
throw err;
|
|
513
|
+
}
|
|
514
|
+
const viemError = formatViemError(err);
|
|
515
|
+
if (!this.getNextPublisher) {
|
|
516
|
+
this.log.error('Failed to publish bundled transactions', viemError);
|
|
517
|
+
return undefined;
|
|
518
|
+
}
|
|
519
|
+
this.log.warn(
|
|
520
|
+
`Publisher ${currentPublisher.getSenderAddress()} failed to send, rotating to next publisher`,
|
|
521
|
+
viemError,
|
|
522
|
+
);
|
|
523
|
+
const nextPublisher = await this.getNextPublisher([...triedAddresses]);
|
|
524
|
+
if (!nextPublisher) {
|
|
525
|
+
this.log.error('All available publishers exhausted, failed to publish bundled transactions');
|
|
526
|
+
return undefined;
|
|
527
|
+
}
|
|
528
|
+
currentPublisher = nextPublisher;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
389
533
|
private callbackBundledTransactions(
|
|
390
534
|
requests: RequestWithExpiry[],
|
|
391
|
-
result
|
|
535
|
+
result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError | undefined,
|
|
536
|
+
txContext: { multicallData: Hex; blobData?: Hex[]; l1BlockNumber: bigint },
|
|
392
537
|
) {
|
|
393
538
|
const actionsListStr = requests.map(r => r.action).join(', ');
|
|
394
539
|
if (result instanceof FormattedViemError) {
|
|
395
540
|
this.log.error(`Failed to publish bundled transactions (${actionsListStr})`, result);
|
|
541
|
+
this.backupFailedTx({
|
|
542
|
+
id: keccak256(txContext.multicallData),
|
|
543
|
+
failureType: 'send-error',
|
|
544
|
+
request: { to: MULTI_CALL_3_ADDRESS, data: txContext.multicallData },
|
|
545
|
+
blobData: txContext.blobData,
|
|
546
|
+
l1BlockNumber: txContext.l1BlockNumber.toString(),
|
|
547
|
+
error: { message: result.message, name: result.name },
|
|
548
|
+
context: {
|
|
549
|
+
actions: requests.map(r => r.action),
|
|
550
|
+
requests: requests.map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
|
|
551
|
+
sender: this.getSenderAddress().toString(),
|
|
552
|
+
},
|
|
553
|
+
});
|
|
396
554
|
return { failedActions: requests.map(r => r.action) };
|
|
397
555
|
} else {
|
|
398
|
-
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
556
|
+
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
557
|
+
result,
|
|
558
|
+
requests: requests.map(r => ({
|
|
559
|
+
...r,
|
|
560
|
+
// Avoid logging large blob data
|
|
561
|
+
blobConfig: r.blobConfig
|
|
562
|
+
? { ...r.blobConfig, blobs: r.blobConfig.blobs.map(b => ({ size: trimmedBytesLength(b) })) }
|
|
563
|
+
: undefined,
|
|
564
|
+
})),
|
|
565
|
+
});
|
|
399
566
|
const successfulActions: Action[] = [];
|
|
400
567
|
const failedActions: Action[] = [];
|
|
401
568
|
for (const request of requests) {
|
|
@@ -405,25 +572,53 @@ export class SequencerPublisher {
|
|
|
405
572
|
failedActions.push(request.action);
|
|
406
573
|
}
|
|
407
574
|
}
|
|
575
|
+
// Single backup for the whole reverted tx
|
|
576
|
+
if (failedActions.length > 0 && result?.receipt?.status === 'reverted') {
|
|
577
|
+
this.backupFailedTx({
|
|
578
|
+
id: result.receipt.transactionHash,
|
|
579
|
+
failureType: 'revert',
|
|
580
|
+
request: { to: MULTI_CALL_3_ADDRESS, data: txContext.multicallData },
|
|
581
|
+
blobData: txContext.blobData,
|
|
582
|
+
l1BlockNumber: result.receipt.blockNumber.toString(),
|
|
583
|
+
receipt: {
|
|
584
|
+
transactionHash: result.receipt.transactionHash,
|
|
585
|
+
blockNumber: result.receipt.blockNumber.toString(),
|
|
586
|
+
gasUsed: (result.receipt.gasUsed ?? 0n).toString(),
|
|
587
|
+
status: 'reverted',
|
|
588
|
+
},
|
|
589
|
+
error: { message: result.errorMsg ?? 'Transaction reverted' },
|
|
590
|
+
context: {
|
|
591
|
+
actions: failedActions,
|
|
592
|
+
requests: requests
|
|
593
|
+
.filter(r => failedActions.includes(r.action))
|
|
594
|
+
.map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
|
|
595
|
+
sender: this.getSenderAddress().toString(),
|
|
596
|
+
},
|
|
597
|
+
});
|
|
598
|
+
}
|
|
408
599
|
return { successfulActions, failedActions };
|
|
409
600
|
}
|
|
410
601
|
}
|
|
411
602
|
|
|
412
603
|
/**
|
|
413
|
-
* @notice Will call `
|
|
604
|
+
* @notice Will call `canProposeAt` to make sure that it is possible to propose
|
|
414
605
|
* @param tipArchive - The archive to check
|
|
415
606
|
* @returns The slot and block number if it is possible to propose, undefined otherwise
|
|
416
607
|
*/
|
|
417
|
-
public
|
|
608
|
+
public canProposeAt(
|
|
418
609
|
tipArchive: Fr,
|
|
419
610
|
msgSender: EthAddress,
|
|
420
|
-
opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
611
|
+
opts: { forcePendingCheckpointNumber?: CheckpointNumber; pipelined?: boolean } = {},
|
|
421
612
|
) {
|
|
422
613
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
423
614
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
424
615
|
|
|
616
|
+
const pipelined = opts.pipelined ?? this.epochCache.isProposerPipeliningEnabled();
|
|
617
|
+
const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
|
|
618
|
+
const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
|
|
619
|
+
|
|
425
620
|
return this.rollupContract
|
|
426
|
-
.
|
|
621
|
+
.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
|
|
427
622
|
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
428
623
|
})
|
|
429
624
|
.catch(err => {
|
|
@@ -437,6 +632,7 @@ export class SequencerPublisher {
|
|
|
437
632
|
return undefined;
|
|
438
633
|
});
|
|
439
634
|
}
|
|
635
|
+
|
|
440
636
|
/**
|
|
441
637
|
* @notice Will simulate `validateHeader` to make sure that the block header is valid
|
|
442
638
|
* @dev This is a convenience function that can be used by the sequencer to validate a "partial" header.
|
|
@@ -460,7 +656,7 @@ export class SequencerPublisher {
|
|
|
460
656
|
flags,
|
|
461
657
|
] as const;
|
|
462
658
|
|
|
463
|
-
const ts =
|
|
659
|
+
const ts = this.getNextL1SlotTimestamp();
|
|
464
660
|
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
465
661
|
opts?.forcePendingCheckpointNumber,
|
|
466
662
|
);
|
|
@@ -516,8 +712,15 @@ export class SequencerPublisher {
|
|
|
516
712
|
const request = this.buildInvalidateCheckpointRequest(validationResult);
|
|
517
713
|
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
|
|
518
714
|
|
|
715
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
716
|
+
|
|
519
717
|
try {
|
|
520
|
-
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
718
|
+
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
719
|
+
request,
|
|
720
|
+
undefined,
|
|
721
|
+
undefined,
|
|
722
|
+
mergeAbis([request.abi ?? [], ErrorsAbi]),
|
|
723
|
+
);
|
|
521
724
|
this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} succeeded`, {
|
|
522
725
|
...logData,
|
|
523
726
|
request,
|
|
@@ -536,7 +739,7 @@ export class SequencerPublisher {
|
|
|
536
739
|
|
|
537
740
|
// If the error is due to the checkpoint not being in the pending chain, and it was indeed removed by someone else,
|
|
538
741
|
// we can safely ignore it and return undefined so we go ahead with checkpoint building.
|
|
539
|
-
if (viemError.message?.includes('
|
|
742
|
+
if (viemError.message?.includes('Rollup__CheckpointNotInPendingChain')) {
|
|
540
743
|
this.log.verbose(
|
|
541
744
|
`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
|
|
542
745
|
{ ...logData, request, error: viemError.message },
|
|
@@ -562,6 +765,18 @@ export class SequencerPublisher {
|
|
|
562
765
|
|
|
563
766
|
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
564
767
|
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
|
|
768
|
+
this.backupFailedTx({
|
|
769
|
+
id: keccak256(request.data!),
|
|
770
|
+
failureType: 'simulation',
|
|
771
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
772
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
773
|
+
error: { message: viemError.message, name: viemError.name },
|
|
774
|
+
context: {
|
|
775
|
+
actions: [`invalidate-${reason}`],
|
|
776
|
+
checkpointNumber,
|
|
777
|
+
sender: this.getSenderAddress().toString(),
|
|
778
|
+
},
|
|
779
|
+
});
|
|
565
780
|
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
|
|
566
781
|
}
|
|
567
782
|
}
|
|
@@ -606,25 +821,11 @@ export class SequencerPublisher {
|
|
|
606
821
|
attestationsAndSignersSignature: Signature,
|
|
607
822
|
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
608
823
|
): Promise<bigint> {
|
|
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
|
-
|
|
824
|
+
// Anchor the simulation timestamp to the checkpoint's own slot start time
|
|
825
|
+
// rather than the current L1 block timestamp, which may overshoot into the next slot if the build ran late.
|
|
826
|
+
const ts = checkpoint.header.timestamp;
|
|
626
827
|
const blobFields = checkpoint.toBlobFields();
|
|
627
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
828
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
628
829
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
629
830
|
|
|
630
831
|
const args = [
|
|
@@ -632,7 +833,7 @@ export class SequencerPublisher {
|
|
|
632
833
|
header: checkpoint.header.toViem(),
|
|
633
834
|
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
634
835
|
oracleInput: {
|
|
635
|
-
feeAssetPriceModifier:
|
|
836
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
636
837
|
},
|
|
637
838
|
},
|
|
638
839
|
attestationsAndSigners.getPackedAttestations(),
|
|
@@ -681,6 +882,32 @@ export class SequencerPublisher {
|
|
|
681
882
|
return false;
|
|
682
883
|
}
|
|
683
884
|
|
|
885
|
+
// Check if payload was already submitted to governance
|
|
886
|
+
const cacheKey = payload.toString();
|
|
887
|
+
if (!this.payloadProposedCache.has(cacheKey)) {
|
|
888
|
+
try {
|
|
889
|
+
const l1StartBlock = await this.rollupContract.getL1StartBlock();
|
|
890
|
+
const proposed = await retry(
|
|
891
|
+
() => base.hasPayloadBeenProposed(payload.toString(), l1StartBlock),
|
|
892
|
+
'Check if payload was proposed',
|
|
893
|
+
makeBackoff([0, 1, 2]),
|
|
894
|
+
this.log,
|
|
895
|
+
true,
|
|
896
|
+
);
|
|
897
|
+
if (proposed) {
|
|
898
|
+
this.payloadProposedCache.add(cacheKey);
|
|
899
|
+
}
|
|
900
|
+
} catch (err) {
|
|
901
|
+
this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
|
|
902
|
+
return false;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
if (this.payloadProposedCache.has(cacheKey)) {
|
|
907
|
+
this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
|
|
908
|
+
return false;
|
|
909
|
+
}
|
|
910
|
+
|
|
684
911
|
const cachedLastVote = this.lastActions[signalType];
|
|
685
912
|
this.lastActions[signalType] = slotNumber;
|
|
686
913
|
const action = signalType;
|
|
@@ -699,11 +926,26 @@ export class SequencerPublisher {
|
|
|
699
926
|
lastValidL2Slot: slotNumber,
|
|
700
927
|
});
|
|
701
928
|
|
|
929
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
930
|
+
|
|
702
931
|
try {
|
|
703
|
-
await this.l1TxUtils.simulate(request, { time: timestamp }, [], ErrorsAbi);
|
|
932
|
+
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
704
933
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
705
934
|
} catch (err) {
|
|
706
|
-
|
|
935
|
+
const viemError = formatViemError(err);
|
|
936
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError);
|
|
937
|
+
this.backupFailedTx({
|
|
938
|
+
id: keccak256(request.data!),
|
|
939
|
+
failureType: 'simulation',
|
|
940
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
941
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
942
|
+
error: { message: viemError.message, name: viemError.name },
|
|
943
|
+
context: {
|
|
944
|
+
actions: [action],
|
|
945
|
+
slot: slotNumber,
|
|
946
|
+
sender: this.getSenderAddress().toString(),
|
|
947
|
+
},
|
|
948
|
+
});
|
|
707
949
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
708
950
|
}
|
|
709
951
|
|
|
@@ -908,14 +1150,15 @@ export class SequencerPublisher {
|
|
|
908
1150
|
const checkpointHeader = checkpoint.header;
|
|
909
1151
|
|
|
910
1152
|
const blobFields = checkpoint.toBlobFields();
|
|
911
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
1153
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
912
1154
|
|
|
913
|
-
const proposeTxArgs = {
|
|
1155
|
+
const proposeTxArgs: L1ProcessArgs = {
|
|
914
1156
|
header: checkpointHeader,
|
|
915
1157
|
archive: checkpoint.archive.root.toBuffer(),
|
|
916
1158
|
blobs,
|
|
917
1159
|
attestationsAndSigners,
|
|
918
1160
|
attestationsAndSignersSignature,
|
|
1161
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
919
1162
|
};
|
|
920
1163
|
|
|
921
1164
|
let ts: bigint;
|
|
@@ -998,13 +1241,30 @@ export class SequencerPublisher {
|
|
|
998
1241
|
|
|
999
1242
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
1000
1243
|
|
|
1244
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1245
|
+
|
|
1001
1246
|
let gasUsed: bigint;
|
|
1247
|
+
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
1002
1248
|
try {
|
|
1003
|
-
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [],
|
|
1249
|
+
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi)); // TODO(palla/slash): Check the timestamp logic
|
|
1004
1250
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
1005
1251
|
} catch (err) {
|
|
1006
|
-
const viemError = formatViemError(err);
|
|
1252
|
+
const viemError = formatViemError(err, simulateAbi);
|
|
1007
1253
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1254
|
+
|
|
1255
|
+
this.backupFailedTx({
|
|
1256
|
+
id: keccak256(request.data!),
|
|
1257
|
+
failureType: 'simulation',
|
|
1258
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
1259
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1260
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1261
|
+
context: {
|
|
1262
|
+
actions: [action],
|
|
1263
|
+
slot: slotNumber,
|
|
1264
|
+
sender: this.getSenderAddress().toString(),
|
|
1265
|
+
},
|
|
1266
|
+
});
|
|
1267
|
+
|
|
1008
1268
|
return false;
|
|
1009
1269
|
}
|
|
1010
1270
|
|
|
@@ -1012,10 +1272,14 @@ export class SequencerPublisher {
|
|
|
1012
1272
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(gasUsed) * 64) / 63)));
|
|
1013
1273
|
logData.gasLimit = gasLimit;
|
|
1014
1274
|
|
|
1275
|
+
// Store the ABI used for simulation on the request so Multicall3.forward can decode errors
|
|
1276
|
+
// when the tx is sent and a revert is diagnosed via simulation.
|
|
1277
|
+
const requestWithAbi = { ...request, abi: simulateAbi };
|
|
1278
|
+
|
|
1015
1279
|
this.log.debug(`Enqueuing ${action}`, logData);
|
|
1016
1280
|
this.addRequest({
|
|
1017
1281
|
action,
|
|
1018
|
-
request,
|
|
1282
|
+
request: requestWithAbi,
|
|
1019
1283
|
gasConfig: { gasLimit },
|
|
1020
1284
|
lastValidL2Slot: slotNumber,
|
|
1021
1285
|
checkSuccess: (_req, result) => {
|
|
@@ -1084,9 +1348,27 @@ export class SequencerPublisher {
|
|
|
1084
1348
|
kzg,
|
|
1085
1349
|
},
|
|
1086
1350
|
)
|
|
1087
|
-
.catch(err => {
|
|
1088
|
-
const
|
|
1089
|
-
this.log.error(`Failed to validate blobs`, message, { metaMessages });
|
|
1351
|
+
.catch(async err => {
|
|
1352
|
+
const viemError = formatViemError(err);
|
|
1353
|
+
this.log.error(`Failed to validate blobs`, viemError.message, { metaMessages: viemError.metaMessages });
|
|
1354
|
+
const validateBlobsData = encodeFunctionData({
|
|
1355
|
+
abi: RollupAbi,
|
|
1356
|
+
functionName: 'validateBlobs',
|
|
1357
|
+
args: [blobInput],
|
|
1358
|
+
});
|
|
1359
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1360
|
+
this.backupFailedTx({
|
|
1361
|
+
id: keccak256(validateBlobsData),
|
|
1362
|
+
failureType: 'simulation',
|
|
1363
|
+
request: { to: this.rollupContract.address as Hex, data: validateBlobsData },
|
|
1364
|
+
blobData: encodedData.blobs.map(b => toHex(b.data)) as Hex[],
|
|
1365
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1366
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1367
|
+
context: {
|
|
1368
|
+
actions: ['validate-blobs'],
|
|
1369
|
+
sender: this.getSenderAddress().toString(),
|
|
1370
|
+
},
|
|
1371
|
+
});
|
|
1090
1372
|
throw new Error('Failed to validate blobs');
|
|
1091
1373
|
});
|
|
1092
1374
|
}
|
|
@@ -1097,8 +1379,7 @@ export class SequencerPublisher {
|
|
|
1097
1379
|
header: encodedData.header.toViem(),
|
|
1098
1380
|
archive: toHex(encodedData.archive),
|
|
1099
1381
|
oracleInput: {
|
|
1100
|
-
|
|
1101
|
-
feeAssetPriceModifier: 0n,
|
|
1382
|
+
feeAssetPriceModifier: encodedData.feeAssetPriceModifier,
|
|
1102
1383
|
},
|
|
1103
1384
|
},
|
|
1104
1385
|
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
@@ -1124,7 +1405,7 @@ export class SequencerPublisher {
|
|
|
1124
1405
|
readonly header: ViemHeader;
|
|
1125
1406
|
readonly archive: `0x${string}`;
|
|
1126
1407
|
readonly oracleInput: {
|
|
1127
|
-
readonly feeAssetPriceModifier:
|
|
1408
|
+
readonly feeAssetPriceModifier: bigint;
|
|
1128
1409
|
};
|
|
1129
1410
|
},
|
|
1130
1411
|
ViemCommitteeAttestations,
|
|
@@ -1166,25 +1447,27 @@ export class SequencerPublisher {
|
|
|
1166
1447
|
});
|
|
1167
1448
|
}
|
|
1168
1449
|
|
|
1450
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1451
|
+
|
|
1169
1452
|
const simulationResult = await this.l1TxUtils
|
|
1170
1453
|
.simulate(
|
|
1171
1454
|
{
|
|
1172
1455
|
to: this.rollupContract.address,
|
|
1173
1456
|
data: rollupData,
|
|
1174
|
-
gas:
|
|
1457
|
+
gas: MAX_L1_TX_LIMIT,
|
|
1175
1458
|
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1176
1459
|
},
|
|
1177
1460
|
{
|
|
1178
1461
|
// @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
|
|
1179
1462
|
time: timestamp + 1n,
|
|
1180
1463
|
// @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:
|
|
1464
|
+
gasLimit: MAX_L1_TX_LIMIT * 2n,
|
|
1182
1465
|
},
|
|
1183
1466
|
stateOverrides,
|
|
1184
1467
|
RollupAbi,
|
|
1185
1468
|
{
|
|
1186
1469
|
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
1187
|
-
fallbackGasEstimate:
|
|
1470
|
+
fallbackGasEstimate: MAX_L1_TX_LIMIT,
|
|
1188
1471
|
},
|
|
1189
1472
|
)
|
|
1190
1473
|
.catch(err => {
|
|
@@ -1194,11 +1477,23 @@ export class SequencerPublisher {
|
|
|
1194
1477
|
this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
|
|
1195
1478
|
// Return a minimal simulation result with the fallback gas estimate
|
|
1196
1479
|
return {
|
|
1197
|
-
gasUsed:
|
|
1480
|
+
gasUsed: MAX_L1_TX_LIMIT,
|
|
1198
1481
|
logs: [],
|
|
1199
1482
|
};
|
|
1200
1483
|
}
|
|
1201
1484
|
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1485
|
+
this.backupFailedTx({
|
|
1486
|
+
id: keccak256(rollupData),
|
|
1487
|
+
failureType: 'simulation',
|
|
1488
|
+
request: { to: this.rollupContract.address, data: rollupData },
|
|
1489
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1490
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1491
|
+
context: {
|
|
1492
|
+
actions: ['propose'],
|
|
1493
|
+
slot: Number(args[0].header.slotNumber),
|
|
1494
|
+
sender: this.getSenderAddress().toString(),
|
|
1495
|
+
},
|
|
1496
|
+
});
|
|
1202
1497
|
throw err;
|
|
1203
1498
|
});
|
|
1204
1499
|
|
|
@@ -1294,4 +1589,10 @@ export class SequencerPublisher {
|
|
|
1294
1589
|
},
|
|
1295
1590
|
});
|
|
1296
1591
|
}
|
|
1592
|
+
|
|
1593
|
+
/** Returns the timestamp to use when simulating L1 proposal calls */
|
|
1594
|
+
private getNextL1SlotTimestamp(): bigint {
|
|
1595
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1596
|
+
return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
|
|
1597
|
+
}
|
|
1297
1598
|
}
|