@aztec/sequencer-client 0.0.1-commit.cd76b27 → 0.0.1-commit.ce4f8c4f2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/client/sequencer-client.d.ts +4 -1
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +46 -23
- package/dest/config.d.ts +25 -5
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +31 -17
- package/dest/global_variable_builder/global_builder.d.ts +13 -7
- 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 +17 -1
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +23 -3
- 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 +3 -3
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +16 -2
- package/dest/publisher/sequencer-publisher.d.ts +19 -4
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +294 -18
- package/dest/sequencer/checkpoint_proposal_job.d.ts +13 -7
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +206 -130
- package/dest/sequencer/events.d.ts +2 -1
- package/dest/sequencer/events.d.ts.map +1 -1
- package/dest/sequencer/metrics.d.ts +5 -1
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +11 -0
- package/dest/sequencer/sequencer.d.ts +18 -9
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +77 -62
- package/dest/sequencer/timetable.d.ts +4 -3
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +6 -7
- package/dest/sequencer/types.d.ts +2 -2
- package/dest/sequencer/types.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts +7 -9
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +39 -30
- package/package.json +27 -28
- package/src/client/sequencer-client.ts +56 -21
- package/src/config.ts +39 -19
- package/src/global_variable_builder/global_builder.ts +22 -23
- package/src/global_variable_builder/index.ts +1 -1
- package/src/publisher/config.ts +41 -0
- 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 +18 -3
- package/src/publisher/sequencer-publisher.ts +281 -26
- package/src/sequencer/checkpoint_proposal_job.ts +277 -142
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/metrics.ts +14 -0
- package/src/sequencer/sequencer.ts +105 -69
- package/src/sequencer/timetable.ts +7 -7
- package/src/sequencer/types.ts +1 -1
- package/src/test/mock_checkpoint_builder.ts +51 -48
|
@@ -28,8 +28,10 @@ import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from
|
|
|
28
28
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
29
29
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
30
30
|
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
31
|
+
import { trimmedBytesLength } from '@aztec/foundation/buffer';
|
|
31
32
|
import { pick } from '@aztec/foundation/collection';
|
|
32
33
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
34
|
+
import { TimeoutError } from '@aztec/foundation/error';
|
|
33
35
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
34
36
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
35
37
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
@@ -40,14 +42,25 @@ import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
|
40
42
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
41
43
|
import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
42
44
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
45
|
+
import { getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
43
46
|
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
44
47
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
45
48
|
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
46
49
|
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
47
50
|
|
|
48
|
-
import {
|
|
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';
|
|
49
61
|
|
|
50
62
|
import type { SequencerPublisherConfig } from './config.js';
|
|
63
|
+
import { type FailedL1Tx, type L1TxFailedStore, createL1TxFailedStore } from './l1_tx_failed_store/index.js';
|
|
51
64
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
52
65
|
|
|
53
66
|
/** Arguments to the process method of the rollup contract */
|
|
@@ -109,6 +122,7 @@ export class SequencerPublisher {
|
|
|
109
122
|
private interrupted = false;
|
|
110
123
|
private metrics: SequencerPublisherMetrics;
|
|
111
124
|
public epochCache: EpochCache;
|
|
125
|
+
private failedTxStore?: Promise<L1TxFailedStore | undefined>;
|
|
112
126
|
|
|
113
127
|
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
114
128
|
protected slashingLog = createLogger('sequencer:publisher:slashing');
|
|
@@ -120,12 +134,17 @@ export class SequencerPublisher {
|
|
|
120
134
|
|
|
121
135
|
protected log: Logger;
|
|
122
136
|
protected ethereumSlotDuration: bigint;
|
|
137
|
+
protected aztecSlotDuration: bigint;
|
|
138
|
+
private dateProvider: DateProvider;
|
|
123
139
|
|
|
124
140
|
private blobClient: BlobClientInterface;
|
|
125
141
|
|
|
126
142
|
/** Address to use for simulations in fisherman mode (actual proposer's address) */
|
|
127
143
|
private proposerAddressForSimulation?: EthAddress;
|
|
128
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
|
+
|
|
129
148
|
/** L1 fee analyzer for fisherman mode */
|
|
130
149
|
private l1FeeAnalyzer?: L1FeeAnalyzer;
|
|
131
150
|
|
|
@@ -149,8 +168,8 @@ export class SequencerPublisher {
|
|
|
149
168
|
protected requests: RequestWithExpiry[] = [];
|
|
150
169
|
|
|
151
170
|
constructor(
|
|
152
|
-
private config: Pick<SequencerPublisherConfig, 'fishermanMode'> &
|
|
153
|
-
Pick<L1ContractsConfig, 'ethereumSlotDuration'> & { l1ChainId: number },
|
|
171
|
+
private config: Pick<SequencerPublisherConfig, 'fishermanMode' | 'l1TxFailedStore'> &
|
|
172
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration'> & { l1ChainId: number },
|
|
154
173
|
deps: {
|
|
155
174
|
telemetry?: TelemetryClient;
|
|
156
175
|
blobClient: BlobClientInterface;
|
|
@@ -164,10 +183,13 @@ export class SequencerPublisher {
|
|
|
164
183
|
metrics: SequencerPublisherMetrics;
|
|
165
184
|
lastActions: Partial<Record<Action, SlotNumber>>;
|
|
166
185
|
log?: Logger;
|
|
186
|
+
getNextPublisher?: (excludeAddresses: EthAddress[]) => Promise<L1TxUtils | undefined>;
|
|
167
187
|
},
|
|
168
188
|
) {
|
|
169
189
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
170
190
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
191
|
+
this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
|
|
192
|
+
this.dateProvider = deps.dateProvider;
|
|
171
193
|
this.epochCache = deps.epochCache;
|
|
172
194
|
this.lastActions = deps.lastActions;
|
|
173
195
|
|
|
@@ -177,6 +199,7 @@ export class SequencerPublisher {
|
|
|
177
199
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
178
200
|
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
179
201
|
this.l1TxUtils = deps.l1TxUtils;
|
|
202
|
+
this.getNextPublisher = deps.getNextPublisher;
|
|
180
203
|
|
|
181
204
|
this.rollupContract = deps.rollupContract;
|
|
182
205
|
|
|
@@ -205,6 +228,31 @@ export class SequencerPublisher {
|
|
|
205
228
|
this.rollupContract,
|
|
206
229
|
createLogger('sequencer:publisher:price-oracle'),
|
|
207
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
|
+
});
|
|
208
256
|
}
|
|
209
257
|
|
|
210
258
|
public getRollupContract(): RollupContract {
|
|
@@ -243,7 +291,7 @@ export class SequencerPublisher {
|
|
|
243
291
|
}
|
|
244
292
|
|
|
245
293
|
public getCurrentL2Slot(): SlotNumber {
|
|
246
|
-
return this.epochCache.
|
|
294
|
+
return this.epochCache.getSlotNow();
|
|
247
295
|
}
|
|
248
296
|
|
|
249
297
|
/**
|
|
@@ -356,8 +404,8 @@ export class SequencerPublisher {
|
|
|
356
404
|
// @note - we can only have one blob config per bundle
|
|
357
405
|
// find requests with gas and blob configs
|
|
358
406
|
// See https://github.com/AztecProtocol/aztec-packages/issues/11513
|
|
359
|
-
const gasConfigs =
|
|
360
|
-
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);
|
|
361
409
|
|
|
362
410
|
if (blobConfigs.length > 1) {
|
|
363
411
|
throw new Error('Multiple blob configs found');
|
|
@@ -386,19 +434,36 @@ export class SequencerPublisher {
|
|
|
386
434
|
validRequests.sort((a, b) => compareActions(a.action, b.action));
|
|
387
435
|
|
|
388
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
|
+
|
|
389
454
|
this.log.debug('Forwarding transactions', {
|
|
390
455
|
validRequests: validRequests.map(request => request.action),
|
|
391
456
|
txConfig,
|
|
392
457
|
});
|
|
393
|
-
const result = await
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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,
|
|
400
466
|
);
|
|
401
|
-
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(validRequests, result);
|
|
402
467
|
return { result, expiredActions, sentActions: validActions, successfulActions, failedActions };
|
|
403
468
|
} catch (err) {
|
|
404
469
|
const viemError = formatViemError(err);
|
|
@@ -416,16 +481,88 @@ export class SequencerPublisher {
|
|
|
416
481
|
}
|
|
417
482
|
}
|
|
418
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
|
+
|
|
419
533
|
private callbackBundledTransactions(
|
|
420
534
|
requests: RequestWithExpiry[],
|
|
421
|
-
result
|
|
535
|
+
result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError | undefined,
|
|
536
|
+
txContext: { multicallData: Hex; blobData?: Hex[]; l1BlockNumber: bigint },
|
|
422
537
|
) {
|
|
423
538
|
const actionsListStr = requests.map(r => r.action).join(', ');
|
|
424
539
|
if (result instanceof FormattedViemError) {
|
|
425
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
|
+
});
|
|
426
554
|
return { failedActions: requests.map(r => r.action) };
|
|
427
555
|
} else {
|
|
428
|
-
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
|
+
});
|
|
429
566
|
const successfulActions: Action[] = [];
|
|
430
567
|
const failedActions: Action[] = [];
|
|
431
568
|
for (const request of requests) {
|
|
@@ -435,25 +572,53 @@ export class SequencerPublisher {
|
|
|
435
572
|
failedActions.push(request.action);
|
|
436
573
|
}
|
|
437
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
|
+
}
|
|
438
599
|
return { successfulActions, failedActions };
|
|
439
600
|
}
|
|
440
601
|
}
|
|
441
602
|
|
|
442
603
|
/**
|
|
443
|
-
* @notice Will call `
|
|
604
|
+
* @notice Will call `canProposeAt` to make sure that it is possible to propose
|
|
444
605
|
* @param tipArchive - The archive to check
|
|
445
606
|
* @returns The slot and block number if it is possible to propose, undefined otherwise
|
|
446
607
|
*/
|
|
447
|
-
public
|
|
608
|
+
public canProposeAt(
|
|
448
609
|
tipArchive: Fr,
|
|
449
610
|
msgSender: EthAddress,
|
|
450
|
-
opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
611
|
+
opts: { forcePendingCheckpointNumber?: CheckpointNumber; pipelined?: boolean } = {},
|
|
451
612
|
) {
|
|
452
613
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
453
614
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
454
615
|
|
|
616
|
+
const pipelined = opts.pipelined ?? this.epochCache.isProposerPipeliningEnabled();
|
|
617
|
+
const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
|
|
618
|
+
const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
|
|
619
|
+
|
|
455
620
|
return this.rollupContract
|
|
456
|
-
.
|
|
621
|
+
.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
|
|
457
622
|
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
458
623
|
})
|
|
459
624
|
.catch(err => {
|
|
@@ -467,6 +632,7 @@ export class SequencerPublisher {
|
|
|
467
632
|
return undefined;
|
|
468
633
|
});
|
|
469
634
|
}
|
|
635
|
+
|
|
470
636
|
/**
|
|
471
637
|
* @notice Will simulate `validateHeader` to make sure that the block header is valid
|
|
472
638
|
* @dev This is a convenience function that can be used by the sequencer to validate a "partial" header.
|
|
@@ -490,7 +656,7 @@ export class SequencerPublisher {
|
|
|
490
656
|
flags,
|
|
491
657
|
] as const;
|
|
492
658
|
|
|
493
|
-
const ts =
|
|
659
|
+
const ts = this.getNextL1SlotTimestamp();
|
|
494
660
|
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
495
661
|
opts?.forcePendingCheckpointNumber,
|
|
496
662
|
);
|
|
@@ -546,6 +712,8 @@ export class SequencerPublisher {
|
|
|
546
712
|
const request = this.buildInvalidateCheckpointRequest(validationResult);
|
|
547
713
|
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
|
|
548
714
|
|
|
715
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
716
|
+
|
|
549
717
|
try {
|
|
550
718
|
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
551
719
|
request,
|
|
@@ -597,6 +765,18 @@ export class SequencerPublisher {
|
|
|
597
765
|
|
|
598
766
|
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
599
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
|
+
});
|
|
600
780
|
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
|
|
601
781
|
}
|
|
602
782
|
}
|
|
@@ -641,7 +821,14 @@ export class SequencerPublisher {
|
|
|
641
821
|
attestationsAndSignersSignature: Signature,
|
|
642
822
|
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
643
823
|
): Promise<bigint> {
|
|
644
|
-
|
|
824
|
+
// When pipelining, the checkpoint targets the next slot so its timestamp is in the future.
|
|
825
|
+
// Without pipelining, the checkpoint targets the current slot so its timestamp is in the past
|
|
826
|
+
// by the time we simulate (~24s of build time), causing eth_simulateV1 to reject it.
|
|
827
|
+
// In that case, use the latest L1 block timestamp + one ethereum slot, which is just ahead
|
|
828
|
+
// of L1 and still within the same L2 slot.
|
|
829
|
+
const ts = this.epochCache.isProposerPipeliningEnabled()
|
|
830
|
+
? checkpoint.header.timestamp
|
|
831
|
+
: (await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration;
|
|
645
832
|
const blobFields = checkpoint.toBlobFields();
|
|
646
833
|
const blobs = await getBlobsPerL1Block(blobFields);
|
|
647
834
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
@@ -744,11 +931,26 @@ export class SequencerPublisher {
|
|
|
744
931
|
lastValidL2Slot: slotNumber,
|
|
745
932
|
});
|
|
746
933
|
|
|
934
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
935
|
+
|
|
747
936
|
try {
|
|
748
937
|
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
749
938
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
750
939
|
} catch (err) {
|
|
751
|
-
|
|
940
|
+
const viemError = formatViemError(err);
|
|
941
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError);
|
|
942
|
+
this.backupFailedTx({
|
|
943
|
+
id: keccak256(request.data!),
|
|
944
|
+
failureType: 'simulation',
|
|
945
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
946
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
947
|
+
error: { message: viemError.message, name: viemError.name },
|
|
948
|
+
context: {
|
|
949
|
+
actions: [action],
|
|
950
|
+
slot: slotNumber,
|
|
951
|
+
sender: this.getSenderAddress().toString(),
|
|
952
|
+
},
|
|
953
|
+
});
|
|
752
954
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
753
955
|
}
|
|
754
956
|
|
|
@@ -1044,6 +1246,8 @@ export class SequencerPublisher {
|
|
|
1044
1246
|
|
|
1045
1247
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
1046
1248
|
|
|
1249
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1250
|
+
|
|
1047
1251
|
let gasUsed: bigint;
|
|
1048
1252
|
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
1049
1253
|
try {
|
|
@@ -1053,6 +1257,19 @@ export class SequencerPublisher {
|
|
|
1053
1257
|
const viemError = formatViemError(err, simulateAbi);
|
|
1054
1258
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1055
1259
|
|
|
1260
|
+
this.backupFailedTx({
|
|
1261
|
+
id: keccak256(request.data!),
|
|
1262
|
+
failureType: 'simulation',
|
|
1263
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
1264
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1265
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1266
|
+
context: {
|
|
1267
|
+
actions: [action],
|
|
1268
|
+
slot: slotNumber,
|
|
1269
|
+
sender: this.getSenderAddress().toString(),
|
|
1270
|
+
},
|
|
1271
|
+
});
|
|
1272
|
+
|
|
1056
1273
|
return false;
|
|
1057
1274
|
}
|
|
1058
1275
|
|
|
@@ -1136,9 +1353,27 @@ export class SequencerPublisher {
|
|
|
1136
1353
|
kzg,
|
|
1137
1354
|
},
|
|
1138
1355
|
)
|
|
1139
|
-
.catch(err => {
|
|
1140
|
-
const
|
|
1141
|
-
this.log.error(`Failed to validate blobs`, message, { metaMessages });
|
|
1356
|
+
.catch(async err => {
|
|
1357
|
+
const viemError = formatViemError(err);
|
|
1358
|
+
this.log.error(`Failed to validate blobs`, viemError.message, { metaMessages: viemError.metaMessages });
|
|
1359
|
+
const validateBlobsData = encodeFunctionData({
|
|
1360
|
+
abi: RollupAbi,
|
|
1361
|
+
functionName: 'validateBlobs',
|
|
1362
|
+
args: [blobInput],
|
|
1363
|
+
});
|
|
1364
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1365
|
+
this.backupFailedTx({
|
|
1366
|
+
id: keccak256(validateBlobsData),
|
|
1367
|
+
failureType: 'simulation',
|
|
1368
|
+
request: { to: this.rollupContract.address as Hex, data: validateBlobsData },
|
|
1369
|
+
blobData: encodedData.blobs.map(b => toHex(b.data)) as Hex[],
|
|
1370
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1371
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1372
|
+
context: {
|
|
1373
|
+
actions: ['validate-blobs'],
|
|
1374
|
+
sender: this.getSenderAddress().toString(),
|
|
1375
|
+
},
|
|
1376
|
+
});
|
|
1142
1377
|
throw new Error('Failed to validate blobs');
|
|
1143
1378
|
});
|
|
1144
1379
|
}
|
|
@@ -1217,6 +1452,8 @@ export class SequencerPublisher {
|
|
|
1217
1452
|
});
|
|
1218
1453
|
}
|
|
1219
1454
|
|
|
1455
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1456
|
+
|
|
1220
1457
|
const simulationResult = await this.l1TxUtils
|
|
1221
1458
|
.simulate(
|
|
1222
1459
|
{
|
|
@@ -1250,6 +1487,18 @@ export class SequencerPublisher {
|
|
|
1250
1487
|
};
|
|
1251
1488
|
}
|
|
1252
1489
|
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1490
|
+
this.backupFailedTx({
|
|
1491
|
+
id: keccak256(rollupData),
|
|
1492
|
+
failureType: 'simulation',
|
|
1493
|
+
request: { to: this.rollupContract.address, data: rollupData },
|
|
1494
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1495
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1496
|
+
context: {
|
|
1497
|
+
actions: ['propose'],
|
|
1498
|
+
slot: Number(args[0].header.slotNumber),
|
|
1499
|
+
sender: this.getSenderAddress().toString(),
|
|
1500
|
+
},
|
|
1501
|
+
});
|
|
1253
1502
|
throw err;
|
|
1254
1503
|
});
|
|
1255
1504
|
|
|
@@ -1345,4 +1594,10 @@ export class SequencerPublisher {
|
|
|
1345
1594
|
},
|
|
1346
1595
|
});
|
|
1347
1596
|
}
|
|
1597
|
+
|
|
1598
|
+
/** Returns the timestamp to use when simulating L1 proposal calls */
|
|
1599
|
+
private getNextL1SlotTimestamp(): bigint {
|
|
1600
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1601
|
+
return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
|
|
1602
|
+
}
|
|
1348
1603
|
}
|