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