@aztec/sequencer-client 0.0.1-commit.9d2bcf6d → 0.0.1-commit.9ef841308
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 +39 -13
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +364 -66
- package/dest/sequencer/checkpoint_proposal_job.d.ts +15 -7
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +241 -140
- 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 +97 -15
- package/dest/sequencer/sequencer.d.ts +28 -15
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +93 -84
- 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 +11 -11
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +45 -34
- 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 +359 -86
- package/src/sequencer/checkpoint_proposal_job.ts +328 -151
- package/src/sequencer/checkpoint_voter.ts +1 -12
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/metrics.ts +106 -18
- package/src/sequencer/sequencer.ts +127 -96
- 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 +63 -49
- 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,34 +19,48 @@ import {
|
|
|
18
19
|
type L1BlobInputs,
|
|
19
20
|
type L1TxConfig,
|
|
20
21
|
type L1TxRequest,
|
|
22
|
+
type L1TxUtils,
|
|
21
23
|
MAX_L1_TX_LIMIT,
|
|
22
24
|
type TransactionStats,
|
|
23
25
|
WEI_CONST,
|
|
24
26
|
} from '@aztec/ethereum/l1-tx-utils';
|
|
25
|
-
import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs';
|
|
26
27
|
import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
27
28
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
28
29
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
29
30
|
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
31
|
+
import { trimmedBytesLength } from '@aztec/foundation/buffer';
|
|
30
32
|
import { pick } from '@aztec/foundation/collection';
|
|
31
33
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
34
|
+
import { TimeoutError } from '@aztec/foundation/error';
|
|
32
35
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
33
36
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
34
37
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
38
|
+
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
35
39
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
36
40
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
37
41
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
38
42
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
39
43
|
import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
40
44
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
45
|
+
import { getLastL1SlotTimestampForL2Slot, getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
41
46
|
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
42
47
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
43
48
|
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
44
49
|
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
45
50
|
|
|
46
|
-
import {
|
|
47
|
-
|
|
48
|
-
|
|
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';
|
|
49
64
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
50
65
|
|
|
51
66
|
/** Arguments to the process method of the rollup contract */
|
|
@@ -60,6 +75,8 @@ type L1ProcessArgs = {
|
|
|
60
75
|
attestationsAndSigners: CommitteeAttestationsAndSigners;
|
|
61
76
|
/** Attestations and signers signature */
|
|
62
77
|
attestationsAndSignersSignature: Signature;
|
|
78
|
+
/** The fee asset price modifier in basis points (from oracle) */
|
|
79
|
+
feeAssetPriceModifier: bigint;
|
|
63
80
|
};
|
|
64
81
|
|
|
65
82
|
export const Actions = [
|
|
@@ -105,6 +122,7 @@ export class SequencerPublisher {
|
|
|
105
122
|
private interrupted = false;
|
|
106
123
|
private metrics: SequencerPublisherMetrics;
|
|
107
124
|
public epochCache: EpochCache;
|
|
125
|
+
private failedTxStore?: Promise<L1TxFailedStore | undefined>;
|
|
108
126
|
|
|
109
127
|
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
110
128
|
protected slashingLog = createLogger('sequencer:publisher:slashing');
|
|
@@ -112,24 +130,34 @@ export class SequencerPublisher {
|
|
|
112
130
|
protected lastActions: Partial<Record<Action, SlotNumber>> = {};
|
|
113
131
|
|
|
114
132
|
private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
|
|
133
|
+
private payloadProposedCache: Set<string> = new Set<string>();
|
|
115
134
|
|
|
116
135
|
protected log: Logger;
|
|
117
136
|
protected ethereumSlotDuration: bigint;
|
|
137
|
+
protected aztecSlotDuration: bigint;
|
|
138
|
+
private dateProvider: DateProvider;
|
|
118
139
|
|
|
119
140
|
private blobClient: BlobClientInterface;
|
|
120
141
|
|
|
121
142
|
/** Address to use for simulations in fisherman mode (actual proposer's address) */
|
|
122
143
|
private proposerAddressForSimulation?: EthAddress;
|
|
123
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
|
+
|
|
124
148
|
/** L1 fee analyzer for fisherman mode */
|
|
125
149
|
private l1FeeAnalyzer?: L1FeeAnalyzer;
|
|
150
|
+
|
|
151
|
+
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */
|
|
152
|
+
private feeAssetPriceOracle: FeeAssetPriceOracle;
|
|
153
|
+
|
|
126
154
|
// A CALL to a cold address is 2700 gas
|
|
127
155
|
public static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
128
156
|
|
|
129
157
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
130
158
|
public static VOTE_GAS_GUESS: bigint = 800_000n;
|
|
131
159
|
|
|
132
|
-
public l1TxUtils:
|
|
160
|
+
public l1TxUtils: L1TxUtils;
|
|
133
161
|
public rollupContract: RollupContract;
|
|
134
162
|
public govProposerContract: GovernanceProposerContract;
|
|
135
163
|
public slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
@@ -140,11 +168,12 @@ export class SequencerPublisher {
|
|
|
140
168
|
protected requests: RequestWithExpiry[] = [];
|
|
141
169
|
|
|
142
170
|
constructor(
|
|
143
|
-
private config:
|
|
171
|
+
private config: Pick<SequencerPublisherConfig, 'fishermanMode' | 'l1TxFailedStore'> &
|
|
172
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration'> & { l1ChainId: number },
|
|
144
173
|
deps: {
|
|
145
174
|
telemetry?: TelemetryClient;
|
|
146
175
|
blobClient: BlobClientInterface;
|
|
147
|
-
l1TxUtils:
|
|
176
|
+
l1TxUtils: L1TxUtils;
|
|
148
177
|
rollupContract: RollupContract;
|
|
149
178
|
slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
150
179
|
governanceProposerContract: GovernanceProposerContract;
|
|
@@ -154,10 +183,13 @@ export class SequencerPublisher {
|
|
|
154
183
|
metrics: SequencerPublisherMetrics;
|
|
155
184
|
lastActions: Partial<Record<Action, SlotNumber>>;
|
|
156
185
|
log?: Logger;
|
|
186
|
+
getNextPublisher?: (excludeAddresses: EthAddress[]) => Promise<L1TxUtils | undefined>;
|
|
157
187
|
},
|
|
158
188
|
) {
|
|
159
189
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
160
190
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
191
|
+
this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
|
|
192
|
+
this.dateProvider = deps.dateProvider;
|
|
161
193
|
this.epochCache = deps.epochCache;
|
|
162
194
|
this.lastActions = deps.lastActions;
|
|
163
195
|
|
|
@@ -167,6 +199,7 @@ export class SequencerPublisher {
|
|
|
167
199
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
168
200
|
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
169
201
|
this.l1TxUtils = deps.l1TxUtils;
|
|
202
|
+
this.getNextPublisher = deps.getNextPublisher;
|
|
170
203
|
|
|
171
204
|
this.rollupContract = deps.rollupContract;
|
|
172
205
|
|
|
@@ -188,12 +221,52 @@ export class SequencerPublisher {
|
|
|
188
221
|
createLogger('sequencer:publisher:fee-analyzer'),
|
|
189
222
|
);
|
|
190
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
|
+
});
|
|
191
256
|
}
|
|
192
257
|
|
|
193
258
|
public getRollupContract(): RollupContract {
|
|
194
259
|
return this.rollupContract;
|
|
195
260
|
}
|
|
196
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
|
+
|
|
197
270
|
public getSenderAddress() {
|
|
198
271
|
return this.l1TxUtils.getSenderAddress();
|
|
199
272
|
}
|
|
@@ -218,7 +291,7 @@ export class SequencerPublisher {
|
|
|
218
291
|
}
|
|
219
292
|
|
|
220
293
|
public getCurrentL2Slot(): SlotNumber {
|
|
221
|
-
return this.epochCache.
|
|
294
|
+
return this.epochCache.getSlotNow();
|
|
222
295
|
}
|
|
223
296
|
|
|
224
297
|
/**
|
|
@@ -331,8 +404,8 @@ export class SequencerPublisher {
|
|
|
331
404
|
// @note - we can only have one blob config per bundle
|
|
332
405
|
// find requests with gas and blob configs
|
|
333
406
|
// See https://github.com/AztecProtocol/aztec-packages/issues/11513
|
|
334
|
-
const gasConfigs =
|
|
335
|
-
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);
|
|
336
409
|
|
|
337
410
|
if (blobConfigs.length > 1) {
|
|
338
411
|
throw new Error('Multiple blob configs found');
|
|
@@ -361,19 +434,36 @@ export class SequencerPublisher {
|
|
|
361
434
|
validRequests.sort((a, b) => compareActions(a.action, b.action));
|
|
362
435
|
|
|
363
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
|
+
|
|
364
454
|
this.log.debug('Forwarding transactions', {
|
|
365
455
|
validRequests: validRequests.map(request => request.action),
|
|
366
456
|
txConfig,
|
|
367
457
|
});
|
|
368
|
-
const result = await
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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,
|
|
375
466
|
);
|
|
376
|
-
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(validRequests, result);
|
|
377
467
|
return { result, expiredActions, sentActions: validActions, successfulActions, failedActions };
|
|
378
468
|
} catch (err) {
|
|
379
469
|
const viemError = formatViemError(err);
|
|
@@ -391,16 +481,88 @@ export class SequencerPublisher {
|
|
|
391
481
|
}
|
|
392
482
|
}
|
|
393
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
|
+
|
|
394
533
|
private callbackBundledTransactions(
|
|
395
534
|
requests: RequestWithExpiry[],
|
|
396
|
-
result
|
|
535
|
+
result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError | undefined,
|
|
536
|
+
txContext: { multicallData: Hex; blobData?: Hex[]; l1BlockNumber: bigint },
|
|
397
537
|
) {
|
|
398
538
|
const actionsListStr = requests.map(r => r.action).join(', ');
|
|
399
539
|
if (result instanceof FormattedViemError) {
|
|
400
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
|
+
});
|
|
401
554
|
return { failedActions: requests.map(r => r.action) };
|
|
402
555
|
} else {
|
|
403
|
-
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
|
+
});
|
|
404
566
|
const successfulActions: Action[] = [];
|
|
405
567
|
const failedActions: Action[] = [];
|
|
406
568
|
for (const request of requests) {
|
|
@@ -410,25 +572,53 @@ export class SequencerPublisher {
|
|
|
410
572
|
failedActions.push(request.action);
|
|
411
573
|
}
|
|
412
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
|
+
}
|
|
413
599
|
return { successfulActions, failedActions };
|
|
414
600
|
}
|
|
415
601
|
}
|
|
416
602
|
|
|
417
603
|
/**
|
|
418
|
-
* @notice Will call `
|
|
604
|
+
* @notice Will call `canProposeAt` to make sure that it is possible to propose
|
|
419
605
|
* @param tipArchive - The archive to check
|
|
420
606
|
* @returns The slot and block number if it is possible to propose, undefined otherwise
|
|
421
607
|
*/
|
|
422
|
-
public
|
|
608
|
+
public canProposeAt(
|
|
423
609
|
tipArchive: Fr,
|
|
424
610
|
msgSender: EthAddress,
|
|
425
|
-
opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
611
|
+
opts: { forcePendingCheckpointNumber?: CheckpointNumber; pipelined?: boolean } = {},
|
|
426
612
|
) {
|
|
427
613
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
428
614
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
429
615
|
|
|
616
|
+
const pipelined = opts.pipelined ?? this.epochCache.isProposerPipeliningEnabled();
|
|
617
|
+
const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
|
|
618
|
+
const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
|
|
619
|
+
|
|
430
620
|
return this.rollupContract
|
|
431
|
-
.
|
|
621
|
+
.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
|
|
432
622
|
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
433
623
|
})
|
|
434
624
|
.catch(err => {
|
|
@@ -442,6 +632,7 @@ export class SequencerPublisher {
|
|
|
442
632
|
return undefined;
|
|
443
633
|
});
|
|
444
634
|
}
|
|
635
|
+
|
|
445
636
|
/**
|
|
446
637
|
* @notice Will simulate `validateHeader` to make sure that the block header is valid
|
|
447
638
|
* @dev This is a convenience function that can be used by the sequencer to validate a "partial" header.
|
|
@@ -465,7 +656,7 @@ export class SequencerPublisher {
|
|
|
465
656
|
flags,
|
|
466
657
|
] as const;
|
|
467
658
|
|
|
468
|
-
const ts =
|
|
659
|
+
const ts = this.getSimulationTimestamp(header.slotNumber);
|
|
469
660
|
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
470
661
|
opts?.forcePendingCheckpointNumber,
|
|
471
662
|
);
|
|
@@ -488,7 +679,7 @@ export class SequencerPublisher {
|
|
|
488
679
|
data: encodeFunctionData({ abi: RollupAbi, functionName: 'validateHeaderWithAttestations', args }),
|
|
489
680
|
from: MULTI_CALL_3_ADDRESS,
|
|
490
681
|
},
|
|
491
|
-
{ time: ts
|
|
682
|
+
{ time: ts },
|
|
492
683
|
stateOverrides,
|
|
493
684
|
);
|
|
494
685
|
this.log.debug(`Simulated validateHeader`);
|
|
@@ -521,6 +712,8 @@ export class SequencerPublisher {
|
|
|
521
712
|
const request = this.buildInvalidateCheckpointRequest(validationResult);
|
|
522
713
|
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
|
|
523
714
|
|
|
715
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
716
|
+
|
|
524
717
|
try {
|
|
525
718
|
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
526
719
|
request,
|
|
@@ -572,6 +765,18 @@ export class SequencerPublisher {
|
|
|
572
765
|
|
|
573
766
|
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
574
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
|
+
});
|
|
575
780
|
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
|
|
576
781
|
}
|
|
577
782
|
}
|
|
@@ -615,26 +820,9 @@ export class SequencerPublisher {
|
|
|
615
820
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
616
821
|
attestationsAndSignersSignature: Signature,
|
|
617
822
|
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
618
|
-
): Promise<
|
|
619
|
-
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
620
|
-
|
|
621
|
-
// TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
|
|
622
|
-
// If we have no attestations, we still need to provide the empty attestations
|
|
623
|
-
// so that the committee is recalculated correctly
|
|
624
|
-
// const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
625
|
-
// if (ignoreSignatures) {
|
|
626
|
-
// const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
|
|
627
|
-
// if (!committee) {
|
|
628
|
-
// this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
629
|
-
// throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
630
|
-
// }
|
|
631
|
-
// attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
632
|
-
// CommitteeAttestation.fromAddress(committeeMember),
|
|
633
|
-
// );
|
|
634
|
-
// }
|
|
635
|
-
|
|
823
|
+
): Promise<void> {
|
|
636
824
|
const blobFields = checkpoint.toBlobFields();
|
|
637
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
825
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
638
826
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
639
827
|
|
|
640
828
|
const args = [
|
|
@@ -642,7 +830,7 @@ export class SequencerPublisher {
|
|
|
642
830
|
header: checkpoint.header.toViem(),
|
|
643
831
|
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
644
832
|
oracleInput: {
|
|
645
|
-
feeAssetPriceModifier:
|
|
833
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
646
834
|
},
|
|
647
835
|
},
|
|
648
836
|
attestationsAndSigners.getPackedAttestations(),
|
|
@@ -651,13 +839,11 @@ export class SequencerPublisher {
|
|
|
651
839
|
blobInput,
|
|
652
840
|
] as const;
|
|
653
841
|
|
|
654
|
-
await this.simulateProposeTx(args,
|
|
655
|
-
return ts;
|
|
842
|
+
await this.simulateProposeTx(args, options);
|
|
656
843
|
}
|
|
657
844
|
|
|
658
845
|
private async enqueueCastSignalHelper(
|
|
659
846
|
slotNumber: SlotNumber,
|
|
660
|
-
timestamp: bigint,
|
|
661
847
|
signalType: GovernanceSignalAction,
|
|
662
848
|
payload: EthAddress,
|
|
663
849
|
base: IEmpireBase,
|
|
@@ -691,6 +877,32 @@ export class SequencerPublisher {
|
|
|
691
877
|
return false;
|
|
692
878
|
}
|
|
693
879
|
|
|
880
|
+
// Check if payload was already submitted to governance
|
|
881
|
+
const cacheKey = payload.toString();
|
|
882
|
+
if (!this.payloadProposedCache.has(cacheKey)) {
|
|
883
|
+
try {
|
|
884
|
+
const l1StartBlock = await this.rollupContract.getL1StartBlock();
|
|
885
|
+
const proposed = await retry(
|
|
886
|
+
() => base.hasPayloadBeenProposed(payload.toString(), l1StartBlock),
|
|
887
|
+
'Check if payload was proposed',
|
|
888
|
+
makeBackoff([0, 1, 2]),
|
|
889
|
+
this.log,
|
|
890
|
+
true,
|
|
891
|
+
);
|
|
892
|
+
if (proposed) {
|
|
893
|
+
this.payloadProposedCache.add(cacheKey);
|
|
894
|
+
}
|
|
895
|
+
} catch (err) {
|
|
896
|
+
this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
|
|
897
|
+
return false;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
if (this.payloadProposedCache.has(cacheKey)) {
|
|
902
|
+
this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
|
|
903
|
+
return false;
|
|
904
|
+
}
|
|
905
|
+
|
|
694
906
|
const cachedLastVote = this.lastActions[signalType];
|
|
695
907
|
this.lastActions[signalType] = slotNumber;
|
|
696
908
|
const action = signalType;
|
|
@@ -709,11 +921,30 @@ export class SequencerPublisher {
|
|
|
709
921
|
lastValidL2Slot: slotNumber,
|
|
710
922
|
});
|
|
711
923
|
|
|
924
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
925
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
926
|
+
|
|
712
927
|
try {
|
|
713
928
|
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
714
929
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
715
930
|
} catch (err) {
|
|
716
|
-
|
|
931
|
+
const viemError = formatViemError(err);
|
|
932
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError, {
|
|
933
|
+
simulationTimestamp: timestamp,
|
|
934
|
+
l1BlockNumber,
|
|
935
|
+
});
|
|
936
|
+
this.backupFailedTx({
|
|
937
|
+
id: keccak256(request.data!),
|
|
938
|
+
failureType: 'simulation',
|
|
939
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
940
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
941
|
+
error: { message: viemError.message, name: viemError.name },
|
|
942
|
+
context: {
|
|
943
|
+
actions: [action],
|
|
944
|
+
slot: slotNumber,
|
|
945
|
+
sender: this.getSenderAddress().toString(),
|
|
946
|
+
},
|
|
947
|
+
});
|
|
717
948
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
718
949
|
}
|
|
719
950
|
|
|
@@ -764,19 +995,16 @@ export class SequencerPublisher {
|
|
|
764
995
|
/**
|
|
765
996
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
766
997
|
* @param slotNumber - The slot number to cast a signal for.
|
|
767
|
-
* @param timestamp - The timestamp of the slot to cast a signal for.
|
|
768
998
|
* @returns True if the signal was successfully enqueued, false otherwise.
|
|
769
999
|
*/
|
|
770
1000
|
public enqueueGovernanceCastSignal(
|
|
771
1001
|
governancePayload: EthAddress,
|
|
772
1002
|
slotNumber: SlotNumber,
|
|
773
|
-
timestamp: bigint,
|
|
774
1003
|
signerAddress: EthAddress,
|
|
775
1004
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
776
1005
|
): Promise<boolean> {
|
|
777
1006
|
return this.enqueueCastSignalHelper(
|
|
778
1007
|
slotNumber,
|
|
779
|
-
timestamp,
|
|
780
1008
|
'governance-signal',
|
|
781
1009
|
governancePayload,
|
|
782
1010
|
this.govProposerContract,
|
|
@@ -789,7 +1017,6 @@ export class SequencerPublisher {
|
|
|
789
1017
|
public async enqueueSlashingActions(
|
|
790
1018
|
actions: ProposerSlashAction[],
|
|
791
1019
|
slotNumber: SlotNumber,
|
|
792
|
-
timestamp: bigint,
|
|
793
1020
|
signerAddress: EthAddress,
|
|
794
1021
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
795
1022
|
): Promise<boolean> {
|
|
@@ -810,7 +1037,6 @@ export class SequencerPublisher {
|
|
|
810
1037
|
});
|
|
811
1038
|
await this.enqueueCastSignalHelper(
|
|
812
1039
|
slotNumber,
|
|
813
|
-
timestamp,
|
|
814
1040
|
'empire-slashing-signal',
|
|
815
1041
|
action.payload,
|
|
816
1042
|
this.slashingProposerContract,
|
|
@@ -829,7 +1055,6 @@ export class SequencerPublisher {
|
|
|
829
1055
|
(receipt: TransactionReceipt) =>
|
|
830
1056
|
!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs),
|
|
831
1057
|
slotNumber,
|
|
832
|
-
timestamp,
|
|
833
1058
|
);
|
|
834
1059
|
break;
|
|
835
1060
|
}
|
|
@@ -847,7 +1072,6 @@ export class SequencerPublisher {
|
|
|
847
1072
|
request,
|
|
848
1073
|
(receipt: TransactionReceipt) => !!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs),
|
|
849
1074
|
slotNumber,
|
|
850
|
-
timestamp,
|
|
851
1075
|
);
|
|
852
1076
|
break;
|
|
853
1077
|
}
|
|
@@ -871,7 +1095,6 @@ export class SequencerPublisher {
|
|
|
871
1095
|
request,
|
|
872
1096
|
(receipt: TransactionReceipt) => !!tallySlashingProposer.tryExtractVoteCastEvent(receipt.logs),
|
|
873
1097
|
slotNumber,
|
|
874
|
-
timestamp,
|
|
875
1098
|
);
|
|
876
1099
|
break;
|
|
877
1100
|
}
|
|
@@ -893,7 +1116,6 @@ export class SequencerPublisher {
|
|
|
893
1116
|
request,
|
|
894
1117
|
(receipt: TransactionReceipt) => !!tallySlashingProposer.tryExtractRoundExecutedEvent(receipt.logs),
|
|
895
1118
|
slotNumber,
|
|
896
|
-
timestamp,
|
|
897
1119
|
);
|
|
898
1120
|
break;
|
|
899
1121
|
}
|
|
@@ -918,25 +1140,24 @@ export class SequencerPublisher {
|
|
|
918
1140
|
const checkpointHeader = checkpoint.header;
|
|
919
1141
|
|
|
920
1142
|
const blobFields = checkpoint.toBlobFields();
|
|
921
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
1143
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
922
1144
|
|
|
923
|
-
const proposeTxArgs = {
|
|
1145
|
+
const proposeTxArgs: L1ProcessArgs = {
|
|
924
1146
|
header: checkpointHeader,
|
|
925
1147
|
archive: checkpoint.archive.root.toBuffer(),
|
|
926
1148
|
blobs,
|
|
927
1149
|
attestationsAndSigners,
|
|
928
1150
|
attestationsAndSignersSignature,
|
|
1151
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
929
1152
|
};
|
|
930
1153
|
|
|
931
|
-
let ts: bigint;
|
|
932
|
-
|
|
933
1154
|
try {
|
|
934
1155
|
// @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
|
|
935
1156
|
// This means that we can avoid the simulation issues in later checks.
|
|
936
1157
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
937
1158
|
// make time consistency checks break.
|
|
938
1159
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
939
|
-
|
|
1160
|
+
await this.validateCheckpointForSubmission(
|
|
940
1161
|
checkpoint,
|
|
941
1162
|
attestationsAndSigners,
|
|
942
1163
|
attestationsAndSignersSignature,
|
|
@@ -952,7 +1173,7 @@ export class SequencerPublisher {
|
|
|
952
1173
|
}
|
|
953
1174
|
|
|
954
1175
|
this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
|
|
955
|
-
await this.addProposeTx(checkpoint, proposeTxArgs, opts
|
|
1176
|
+
await this.addProposeTx(checkpoint, proposeTxArgs, opts);
|
|
956
1177
|
}
|
|
957
1178
|
|
|
958
1179
|
public enqueueInvalidateCheckpoint(
|
|
@@ -995,8 +1216,8 @@ export class SequencerPublisher {
|
|
|
995
1216
|
request: L1TxRequest,
|
|
996
1217
|
checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
|
|
997
1218
|
slotNumber: SlotNumber,
|
|
998
|
-
timestamp: bigint,
|
|
999
1219
|
) {
|
|
1220
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
1000
1221
|
const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
|
|
1001
1222
|
if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
|
|
1002
1223
|
this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
|
|
@@ -1008,15 +1229,31 @@ export class SequencerPublisher {
|
|
|
1008
1229
|
|
|
1009
1230
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
1010
1231
|
|
|
1232
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1233
|
+
|
|
1011
1234
|
let gasUsed: bigint;
|
|
1012
1235
|
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
1236
|
+
|
|
1013
1237
|
try {
|
|
1014
|
-
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi));
|
|
1238
|
+
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi));
|
|
1015
1239
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
1016
1240
|
} catch (err) {
|
|
1017
1241
|
const viemError = formatViemError(err, simulateAbi);
|
|
1018
1242
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1019
1243
|
|
|
1244
|
+
this.backupFailedTx({
|
|
1245
|
+
id: keccak256(request.data!),
|
|
1246
|
+
failureType: 'simulation',
|
|
1247
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
1248
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1249
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1250
|
+
context: {
|
|
1251
|
+
actions: [action],
|
|
1252
|
+
slot: slotNumber,
|
|
1253
|
+
sender: this.getSenderAddress().toString(),
|
|
1254
|
+
},
|
|
1255
|
+
});
|
|
1256
|
+
|
|
1020
1257
|
return false;
|
|
1021
1258
|
}
|
|
1022
1259
|
|
|
@@ -1067,7 +1304,6 @@ export class SequencerPublisher {
|
|
|
1067
1304
|
|
|
1068
1305
|
private async prepareProposeTx(
|
|
1069
1306
|
encodedData: L1ProcessArgs,
|
|
1070
|
-
timestamp: bigint,
|
|
1071
1307
|
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1072
1308
|
) {
|
|
1073
1309
|
const kzg = Blob.getViemKzgInstance();
|
|
@@ -1100,9 +1336,27 @@ export class SequencerPublisher {
|
|
|
1100
1336
|
kzg,
|
|
1101
1337
|
},
|
|
1102
1338
|
)
|
|
1103
|
-
.catch(err => {
|
|
1104
|
-
const
|
|
1105
|
-
this.log.error(`Failed to validate blobs`, message, { metaMessages });
|
|
1339
|
+
.catch(async err => {
|
|
1340
|
+
const viemError = formatViemError(err);
|
|
1341
|
+
this.log.error(`Failed to validate blobs`, viemError.message, { metaMessages: viemError.metaMessages });
|
|
1342
|
+
const validateBlobsData = encodeFunctionData({
|
|
1343
|
+
abi: RollupAbi,
|
|
1344
|
+
functionName: 'validateBlobs',
|
|
1345
|
+
args: [blobInput],
|
|
1346
|
+
});
|
|
1347
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1348
|
+
this.backupFailedTx({
|
|
1349
|
+
id: keccak256(validateBlobsData),
|
|
1350
|
+
failureType: 'simulation',
|
|
1351
|
+
request: { to: this.rollupContract.address as Hex, data: validateBlobsData },
|
|
1352
|
+
blobData: encodedData.blobs.map(b => toHex(b.data)) as Hex[],
|
|
1353
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1354
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1355
|
+
context: {
|
|
1356
|
+
actions: ['validate-blobs'],
|
|
1357
|
+
sender: this.getSenderAddress().toString(),
|
|
1358
|
+
},
|
|
1359
|
+
});
|
|
1106
1360
|
throw new Error('Failed to validate blobs');
|
|
1107
1361
|
});
|
|
1108
1362
|
}
|
|
@@ -1113,8 +1367,7 @@ export class SequencerPublisher {
|
|
|
1113
1367
|
header: encodedData.header.toViem(),
|
|
1114
1368
|
archive: toHex(encodedData.archive),
|
|
1115
1369
|
oracleInput: {
|
|
1116
|
-
|
|
1117
|
-
feeAssetPriceModifier: 0n,
|
|
1370
|
+
feeAssetPriceModifier: encodedData.feeAssetPriceModifier,
|
|
1118
1371
|
},
|
|
1119
1372
|
},
|
|
1120
1373
|
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
@@ -1123,7 +1376,7 @@ export class SequencerPublisher {
|
|
|
1123
1376
|
blobInput,
|
|
1124
1377
|
] as const;
|
|
1125
1378
|
|
|
1126
|
-
const { rollupData, simulationResult } = await this.simulateProposeTx(args,
|
|
1379
|
+
const { rollupData, simulationResult } = await this.simulateProposeTx(args, options);
|
|
1127
1380
|
|
|
1128
1381
|
return { args, blobEvaluationGas, rollupData, simulationResult };
|
|
1129
1382
|
}
|
|
@@ -1131,7 +1384,6 @@ export class SequencerPublisher {
|
|
|
1131
1384
|
/**
|
|
1132
1385
|
* Simulates the propose tx with eth_simulateV1
|
|
1133
1386
|
* @param args - The propose tx args
|
|
1134
|
-
* @param timestamp - The timestamp to simulate proposal at
|
|
1135
1387
|
* @returns The simulation result
|
|
1136
1388
|
*/
|
|
1137
1389
|
private async simulateProposeTx(
|
|
@@ -1140,7 +1392,7 @@ export class SequencerPublisher {
|
|
|
1140
1392
|
readonly header: ViemHeader;
|
|
1141
1393
|
readonly archive: `0x${string}`;
|
|
1142
1394
|
readonly oracleInput: {
|
|
1143
|
-
readonly feeAssetPriceModifier:
|
|
1395
|
+
readonly feeAssetPriceModifier: bigint;
|
|
1144
1396
|
};
|
|
1145
1397
|
},
|
|
1146
1398
|
ViemCommitteeAttestations,
|
|
@@ -1148,7 +1400,6 @@ export class SequencerPublisher {
|
|
|
1148
1400
|
ViemSignature,
|
|
1149
1401
|
`0x${string}`,
|
|
1150
1402
|
],
|
|
1151
|
-
timestamp: bigint,
|
|
1152
1403
|
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1153
1404
|
) {
|
|
1154
1405
|
const rollupData = encodeFunctionData({
|
|
@@ -1182,6 +1433,9 @@ export class SequencerPublisher {
|
|
|
1182
1433
|
});
|
|
1183
1434
|
}
|
|
1184
1435
|
|
|
1436
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1437
|
+
const simTs = this.getSimulationTimestamp(SlotNumber.fromBigInt(args[0].header.slotNumber));
|
|
1438
|
+
|
|
1185
1439
|
const simulationResult = await this.l1TxUtils
|
|
1186
1440
|
.simulate(
|
|
1187
1441
|
{
|
|
@@ -1191,8 +1445,7 @@ export class SequencerPublisher {
|
|
|
1191
1445
|
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1192
1446
|
},
|
|
1193
1447
|
{
|
|
1194
|
-
|
|
1195
|
-
time: timestamp + 1n,
|
|
1448
|
+
time: simTs,
|
|
1196
1449
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1197
1450
|
gasLimit: MAX_L1_TX_LIMIT * 2n,
|
|
1198
1451
|
},
|
|
@@ -1214,7 +1467,19 @@ export class SequencerPublisher {
|
|
|
1214
1467
|
logs: [],
|
|
1215
1468
|
};
|
|
1216
1469
|
}
|
|
1217
|
-
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1470
|
+
this.log.error(`Failed to simulate propose tx`, viemError, { simulationTimestamp: simTs });
|
|
1471
|
+
this.backupFailedTx({
|
|
1472
|
+
id: keccak256(rollupData),
|
|
1473
|
+
failureType: 'simulation',
|
|
1474
|
+
request: { to: this.rollupContract.address, data: rollupData },
|
|
1475
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1476
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1477
|
+
context: {
|
|
1478
|
+
actions: ['propose'],
|
|
1479
|
+
slot: Number(args[0].header.slotNumber),
|
|
1480
|
+
sender: this.getSenderAddress().toString(),
|
|
1481
|
+
},
|
|
1482
|
+
});
|
|
1218
1483
|
throw err;
|
|
1219
1484
|
});
|
|
1220
1485
|
|
|
@@ -1225,16 +1490,11 @@ export class SequencerPublisher {
|
|
|
1225
1490
|
checkpoint: Checkpoint,
|
|
1226
1491
|
encodedData: L1ProcessArgs,
|
|
1227
1492
|
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
1228
|
-
timestamp: bigint,
|
|
1229
1493
|
): Promise<void> {
|
|
1230
1494
|
const slot = checkpoint.header.slotNumber;
|
|
1231
1495
|
const timer = new Timer();
|
|
1232
1496
|
const kzg = Blob.getViemKzgInstance();
|
|
1233
|
-
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
1234
|
-
encodedData,
|
|
1235
|
-
timestamp,
|
|
1236
|
-
opts,
|
|
1237
|
-
);
|
|
1497
|
+
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, opts);
|
|
1238
1498
|
const startBlock = await this.l1TxUtils.getBlockNumber();
|
|
1239
1499
|
const gasLimit = this.l1TxUtils.bumpGasLimit(
|
|
1240
1500
|
BigInt(Math.ceil((Number(simulationResult.gasUsed) * 64) / 63)) +
|
|
@@ -1310,4 +1570,17 @@ export class SequencerPublisher {
|
|
|
1310
1570
|
},
|
|
1311
1571
|
});
|
|
1312
1572
|
}
|
|
1573
|
+
|
|
1574
|
+
/** Returns the timestamp of the last L1 slot within a given L2 slot. Used as the simulation timestamp
|
|
1575
|
+
* for eth_simulateV1 calls, since it's guaranteed to be greater than any L1 block produced during the slot. */
|
|
1576
|
+
private getSimulationTimestamp(slot: SlotNumber): bigint {
|
|
1577
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1578
|
+
return getLastL1SlotTimestampForL2Slot(slot, l1Constants);
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
/** Returns the timestamp of the next L1 slot boundary after now. */
|
|
1582
|
+
private getNextL1SlotTimestamp(): bigint {
|
|
1583
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1584
|
+
return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
|
|
1585
|
+
}
|
|
1313
1586
|
}
|