@aztec/sequencer-client 0.0.1-commit.f504929 → 0.0.1-commit.f81dbcf
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 -1
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +85 -13
- package/dest/config.d.ts +22 -3
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +14 -12
- package/dest/publisher/config.d.ts +1 -5
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +1 -6
- package/dest/publisher/index.d.ts +1 -2
- package/dest/publisher/index.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.d.ts +1 -1
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +0 -14
- package/dest/publisher/sequencer-publisher.d.ts +2 -12
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +9 -258
- package/dest/sequencer/checkpoint_proposal_job.d.ts +2 -4
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +55 -58
- package/dest/sequencer/sequencer.d.ts +7 -6
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +11 -13
- 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 +10 -8
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +41 -30
- package/package.json +28 -28
- package/src/client/sequencer-client.ts +111 -12
- package/src/config.ts +17 -14
- package/src/publisher/config.ts +0 -9
- package/src/publisher/index.ts +0 -3
- package/src/publisher/sequencer-publisher-factory.ts +0 -15
- package/src/publisher/sequencer-publisher.ts +15 -237
- package/src/sequencer/checkpoint_proposal_job.ts +65 -65
- package/src/sequencer/sequencer.ts +12 -14
- package/src/sequencer/timetable.ts +7 -7
- package/src/sequencer/types.ts +1 -1
- package/src/test/mock_checkpoint_builder.ts +52 -47
- package/dest/publisher/l1_tx_failed_store/factory.d.ts +0 -11
- package/dest/publisher/l1_tx_failed_store/factory.d.ts.map +0 -1
- package/dest/publisher/l1_tx_failed_store/factory.js +0 -22
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +0 -59
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +0 -1
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +0 -1
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +0 -15
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +0 -1
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +0 -34
- package/dest/publisher/l1_tx_failed_store/index.d.ts +0 -4
- package/dest/publisher/l1_tx_failed_store/index.d.ts.map +0 -1
- package/dest/publisher/l1_tx_failed_store/index.js +0 -2
- package/src/publisher/l1_tx_failed_store/factory.ts +0 -32
- package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +0 -55
- package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +0 -46
- package/src/publisher/l1_tx_failed_store/index.ts +0 -3
|
@@ -30,7 +30,6 @@ import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
|
30
30
|
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
31
31
|
import { pick } from '@aztec/foundation/collection';
|
|
32
32
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
33
|
-
import { TimeoutError } from '@aztec/foundation/error';
|
|
34
33
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
35
34
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
36
35
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
@@ -46,19 +45,9 @@ import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
|
46
45
|
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
47
46
|
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
48
47
|
|
|
49
|
-
import {
|
|
50
|
-
type Hex,
|
|
51
|
-
type StateOverride,
|
|
52
|
-
type TransactionReceipt,
|
|
53
|
-
type TypedDataDefinition,
|
|
54
|
-
encodeFunctionData,
|
|
55
|
-
keccak256,
|
|
56
|
-
multicall3Abi,
|
|
57
|
-
toHex,
|
|
58
|
-
} from 'viem';
|
|
48
|
+
import { type StateOverride, type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
|
|
59
49
|
|
|
60
50
|
import type { SequencerPublisherConfig } from './config.js';
|
|
61
|
-
import { type FailedL1Tx, type L1TxFailedStore, createL1TxFailedStore } from './l1_tx_failed_store/index.js';
|
|
62
51
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
63
52
|
|
|
64
53
|
/** Arguments to the process method of the rollup contract */
|
|
@@ -120,7 +109,6 @@ export class SequencerPublisher {
|
|
|
120
109
|
private interrupted = false;
|
|
121
110
|
private metrics: SequencerPublisherMetrics;
|
|
122
111
|
public epochCache: EpochCache;
|
|
123
|
-
private failedTxStore?: Promise<L1TxFailedStore | undefined>;
|
|
124
112
|
|
|
125
113
|
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
126
114
|
protected slashingLog = createLogger('sequencer:publisher:slashing');
|
|
@@ -138,9 +126,6 @@ export class SequencerPublisher {
|
|
|
138
126
|
/** Address to use for simulations in fisherman mode (actual proposer's address) */
|
|
139
127
|
private proposerAddressForSimulation?: EthAddress;
|
|
140
128
|
|
|
141
|
-
/** Optional callback to obtain a replacement publisher when the current one fails to send. */
|
|
142
|
-
private getNextPublisher?: (excludeAddresses: EthAddress[]) => Promise<L1TxUtils | undefined>;
|
|
143
|
-
|
|
144
129
|
/** L1 fee analyzer for fisherman mode */
|
|
145
130
|
private l1FeeAnalyzer?: L1FeeAnalyzer;
|
|
146
131
|
|
|
@@ -164,7 +149,7 @@ export class SequencerPublisher {
|
|
|
164
149
|
protected requests: RequestWithExpiry[] = [];
|
|
165
150
|
|
|
166
151
|
constructor(
|
|
167
|
-
private config: Pick<SequencerPublisherConfig, 'fishermanMode'
|
|
152
|
+
private config: Pick<SequencerPublisherConfig, 'fishermanMode'> &
|
|
168
153
|
Pick<L1ContractsConfig, 'ethereumSlotDuration'> & { l1ChainId: number },
|
|
169
154
|
deps: {
|
|
170
155
|
telemetry?: TelemetryClient;
|
|
@@ -179,7 +164,6 @@ export class SequencerPublisher {
|
|
|
179
164
|
metrics: SequencerPublisherMetrics;
|
|
180
165
|
lastActions: Partial<Record<Action, SlotNumber>>;
|
|
181
166
|
log?: Logger;
|
|
182
|
-
getNextPublisher?: (excludeAddresses: EthAddress[]) => Promise<L1TxUtils | undefined>;
|
|
183
167
|
},
|
|
184
168
|
) {
|
|
185
169
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
@@ -193,7 +177,6 @@ export class SequencerPublisher {
|
|
|
193
177
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
194
178
|
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
195
179
|
this.l1TxUtils = deps.l1TxUtils;
|
|
196
|
-
this.getNextPublisher = deps.getNextPublisher;
|
|
197
180
|
|
|
198
181
|
this.rollupContract = deps.rollupContract;
|
|
199
182
|
|
|
@@ -222,31 +205,6 @@ export class SequencerPublisher {
|
|
|
222
205
|
this.rollupContract,
|
|
223
206
|
createLogger('sequencer:publisher:price-oracle'),
|
|
224
207
|
);
|
|
225
|
-
|
|
226
|
-
// Initialize failed L1 tx store (optional, for test networks)
|
|
227
|
-
this.failedTxStore = createL1TxFailedStore(config.l1TxFailedStore, this.log);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Backs up a failed L1 transaction to the configured store for debugging.
|
|
232
|
-
* Does nothing if no store is configured.
|
|
233
|
-
*/
|
|
234
|
-
private backupFailedTx(failedTx: Omit<FailedL1Tx, 'timestamp'>): void {
|
|
235
|
-
if (!this.failedTxStore) {
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const tx: FailedL1Tx = {
|
|
240
|
-
...failedTx,
|
|
241
|
-
timestamp: Date.now(),
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
// Fire and forget - don't block on backup
|
|
245
|
-
void this.failedTxStore
|
|
246
|
-
.then(store => store?.saveFailedTx(tx))
|
|
247
|
-
.catch(err => {
|
|
248
|
-
this.log.warn(`Failed to backup failed L1 tx to store`, err);
|
|
249
|
-
});
|
|
250
208
|
}
|
|
251
209
|
|
|
252
210
|
public getRollupContract(): RollupContract {
|
|
@@ -428,36 +386,19 @@ export class SequencerPublisher {
|
|
|
428
386
|
validRequests.sort((a, b) => compareActions(a.action, b.action));
|
|
429
387
|
|
|
430
388
|
try {
|
|
431
|
-
// Capture context for failed tx backup before sending
|
|
432
|
-
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
433
|
-
const multicallData = encodeFunctionData({
|
|
434
|
-
abi: multicall3Abi,
|
|
435
|
-
functionName: 'aggregate3',
|
|
436
|
-
args: [
|
|
437
|
-
validRequests.map(r => ({
|
|
438
|
-
target: r.request.to!,
|
|
439
|
-
callData: r.request.data!,
|
|
440
|
-
allowFailure: true,
|
|
441
|
-
})),
|
|
442
|
-
],
|
|
443
|
-
});
|
|
444
|
-
const blobDataHex = blobConfig?.blobs?.map(b => toHex(b)) as Hex[] | undefined;
|
|
445
|
-
|
|
446
|
-
const txContext = { multicallData, blobData: blobDataHex, l1BlockNumber };
|
|
447
|
-
|
|
448
389
|
this.log.debug('Forwarding transactions', {
|
|
449
390
|
validRequests: validRequests.map(request => request.action),
|
|
450
391
|
txConfig,
|
|
451
392
|
});
|
|
452
|
-
const result = await
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
txContext,
|
|
393
|
+
const result = await Multicall3.forward(
|
|
394
|
+
validRequests.map(request => request.request),
|
|
395
|
+
this.l1TxUtils,
|
|
396
|
+
txConfig,
|
|
397
|
+
blobConfig,
|
|
398
|
+
this.rollupContract.address,
|
|
399
|
+
this.log,
|
|
460
400
|
);
|
|
401
|
+
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(validRequests, result);
|
|
461
402
|
return { result, expiredActions, sentActions: validActions, successfulActions, failedActions };
|
|
462
403
|
} catch (err) {
|
|
463
404
|
const viemError = formatViemError(err);
|
|
@@ -475,76 +416,13 @@ export class SequencerPublisher {
|
|
|
475
416
|
}
|
|
476
417
|
}
|
|
477
418
|
|
|
478
|
-
/**
|
|
479
|
-
* Forwards transactions via Multicall3, rotating to the next available publisher if a send
|
|
480
|
-
* failure occurs (i.e. the tx never reached the chain).
|
|
481
|
-
* On-chain reverts and simulation errors are returned as-is without rotation.
|
|
482
|
-
*/
|
|
483
|
-
private async forwardWithPublisherRotation(
|
|
484
|
-
validRequests: RequestWithExpiry[],
|
|
485
|
-
txConfig: RequestWithExpiry['gasConfig'],
|
|
486
|
-
blobConfig: L1BlobInputs | undefined,
|
|
487
|
-
) {
|
|
488
|
-
const triedAddresses: EthAddress[] = [];
|
|
489
|
-
let currentPublisher = this.l1TxUtils;
|
|
490
|
-
|
|
491
|
-
while (true) {
|
|
492
|
-
triedAddresses.push(currentPublisher.getSenderAddress());
|
|
493
|
-
try {
|
|
494
|
-
const result = await Multicall3.forward(
|
|
495
|
-
validRequests.map(r => r.request),
|
|
496
|
-
currentPublisher,
|
|
497
|
-
txConfig,
|
|
498
|
-
blobConfig,
|
|
499
|
-
this.rollupContract.address,
|
|
500
|
-
this.log,
|
|
501
|
-
);
|
|
502
|
-
this.l1TxUtils = currentPublisher;
|
|
503
|
-
return result;
|
|
504
|
-
} catch (err) {
|
|
505
|
-
if (err instanceof TimeoutError) {
|
|
506
|
-
throw err;
|
|
507
|
-
}
|
|
508
|
-
const viemError = formatViemError(err);
|
|
509
|
-
if (!this.getNextPublisher) {
|
|
510
|
-
this.log.error('Failed to publish bundled transactions', viemError);
|
|
511
|
-
return undefined;
|
|
512
|
-
}
|
|
513
|
-
this.log.warn(
|
|
514
|
-
`Publisher ${currentPublisher.getSenderAddress()} failed to send, rotating to next publisher`,
|
|
515
|
-
viemError,
|
|
516
|
-
);
|
|
517
|
-
const nextPublisher = await this.getNextPublisher([...triedAddresses]);
|
|
518
|
-
if (!nextPublisher) {
|
|
519
|
-
this.log.error('All available publishers exhausted, failed to publish bundled transactions');
|
|
520
|
-
return undefined;
|
|
521
|
-
}
|
|
522
|
-
currentPublisher = nextPublisher;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
|
|
527
419
|
private callbackBundledTransactions(
|
|
528
420
|
requests: RequestWithExpiry[],
|
|
529
|
-
result
|
|
530
|
-
txContext: { multicallData: Hex; blobData?: Hex[]; l1BlockNumber: bigint },
|
|
421
|
+
result?: { receipt: TransactionReceipt } | FormattedViemError,
|
|
531
422
|
) {
|
|
532
423
|
const actionsListStr = requests.map(r => r.action).join(', ');
|
|
533
424
|
if (result instanceof FormattedViemError) {
|
|
534
425
|
this.log.error(`Failed to publish bundled transactions (${actionsListStr})`, result);
|
|
535
|
-
this.backupFailedTx({
|
|
536
|
-
id: keccak256(txContext.multicallData),
|
|
537
|
-
failureType: 'send-error',
|
|
538
|
-
request: { to: MULTI_CALL_3_ADDRESS, data: txContext.multicallData },
|
|
539
|
-
blobData: txContext.blobData,
|
|
540
|
-
l1BlockNumber: txContext.l1BlockNumber.toString(),
|
|
541
|
-
error: { message: result.message, name: result.name },
|
|
542
|
-
context: {
|
|
543
|
-
actions: requests.map(r => r.action),
|
|
544
|
-
requests: requests.map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
|
|
545
|
-
sender: this.getSenderAddress().toString(),
|
|
546
|
-
},
|
|
547
|
-
});
|
|
548
426
|
return { failedActions: requests.map(r => r.action) };
|
|
549
427
|
} else {
|
|
550
428
|
this.log.verbose(`Published bundled transactions (${actionsListStr})`, { result, requests });
|
|
@@ -557,30 +435,6 @@ export class SequencerPublisher {
|
|
|
557
435
|
failedActions.push(request.action);
|
|
558
436
|
}
|
|
559
437
|
}
|
|
560
|
-
// Single backup for the whole reverted tx
|
|
561
|
-
if (failedActions.length > 0 && result?.receipt?.status === 'reverted') {
|
|
562
|
-
this.backupFailedTx({
|
|
563
|
-
id: result.receipt.transactionHash,
|
|
564
|
-
failureType: 'revert',
|
|
565
|
-
request: { to: MULTI_CALL_3_ADDRESS, data: txContext.multicallData },
|
|
566
|
-
blobData: txContext.blobData,
|
|
567
|
-
l1BlockNumber: result.receipt.blockNumber.toString(),
|
|
568
|
-
receipt: {
|
|
569
|
-
transactionHash: result.receipt.transactionHash,
|
|
570
|
-
blockNumber: result.receipt.blockNumber.toString(),
|
|
571
|
-
gasUsed: (result.receipt.gasUsed ?? 0n).toString(),
|
|
572
|
-
status: 'reverted',
|
|
573
|
-
},
|
|
574
|
-
error: { message: result.errorMsg ?? 'Transaction reverted' },
|
|
575
|
-
context: {
|
|
576
|
-
actions: failedActions,
|
|
577
|
-
requests: requests
|
|
578
|
-
.filter(r => failedActions.includes(r.action))
|
|
579
|
-
.map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
|
|
580
|
-
sender: this.getSenderAddress().toString(),
|
|
581
|
-
},
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
438
|
return { successfulActions, failedActions };
|
|
585
439
|
}
|
|
586
440
|
}
|
|
@@ -692,8 +546,6 @@ export class SequencerPublisher {
|
|
|
692
546
|
const request = this.buildInvalidateCheckpointRequest(validationResult);
|
|
693
547
|
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
|
|
694
548
|
|
|
695
|
-
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
696
|
-
|
|
697
549
|
try {
|
|
698
550
|
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
699
551
|
request,
|
|
@@ -745,18 +597,6 @@ export class SequencerPublisher {
|
|
|
745
597
|
|
|
746
598
|
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
747
599
|
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
|
|
748
|
-
this.backupFailedTx({
|
|
749
|
-
id: keccak256(request.data!),
|
|
750
|
-
failureType: 'simulation',
|
|
751
|
-
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
752
|
-
l1BlockNumber: l1BlockNumber.toString(),
|
|
753
|
-
error: { message: viemError.message, name: viemError.name },
|
|
754
|
-
context: {
|
|
755
|
-
actions: [`invalidate-${reason}`],
|
|
756
|
-
checkpointNumber,
|
|
757
|
-
sender: this.getSenderAddress().toString(),
|
|
758
|
-
},
|
|
759
|
-
});
|
|
760
600
|
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
|
|
761
601
|
}
|
|
762
602
|
}
|
|
@@ -904,26 +744,11 @@ export class SequencerPublisher {
|
|
|
904
744
|
lastValidL2Slot: slotNumber,
|
|
905
745
|
});
|
|
906
746
|
|
|
907
|
-
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
908
|
-
|
|
909
747
|
try {
|
|
910
748
|
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
911
749
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
912
750
|
} catch (err) {
|
|
913
|
-
|
|
914
|
-
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError);
|
|
915
|
-
this.backupFailedTx({
|
|
916
|
-
id: keccak256(request.data!),
|
|
917
|
-
failureType: 'simulation',
|
|
918
|
-
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
919
|
-
l1BlockNumber: l1BlockNumber.toString(),
|
|
920
|
-
error: { message: viemError.message, name: viemError.name },
|
|
921
|
-
context: {
|
|
922
|
-
actions: [action],
|
|
923
|
-
slot: slotNumber,
|
|
924
|
-
sender: this.getSenderAddress().toString(),
|
|
925
|
-
},
|
|
926
|
-
});
|
|
751
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, err);
|
|
927
752
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
928
753
|
}
|
|
929
754
|
|
|
@@ -1219,8 +1044,6 @@ export class SequencerPublisher {
|
|
|
1219
1044
|
|
|
1220
1045
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
1221
1046
|
|
|
1222
|
-
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1223
|
-
|
|
1224
1047
|
let gasUsed: bigint;
|
|
1225
1048
|
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
1226
1049
|
try {
|
|
@@ -1230,19 +1053,6 @@ export class SequencerPublisher {
|
|
|
1230
1053
|
const viemError = formatViemError(err, simulateAbi);
|
|
1231
1054
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1232
1055
|
|
|
1233
|
-
this.backupFailedTx({
|
|
1234
|
-
id: keccak256(request.data!),
|
|
1235
|
-
failureType: 'simulation',
|
|
1236
|
-
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
1237
|
-
l1BlockNumber: l1BlockNumber.toString(),
|
|
1238
|
-
error: { message: viemError.message, name: viemError.name },
|
|
1239
|
-
context: {
|
|
1240
|
-
actions: [action],
|
|
1241
|
-
slot: slotNumber,
|
|
1242
|
-
sender: this.getSenderAddress().toString(),
|
|
1243
|
-
},
|
|
1244
|
-
});
|
|
1245
|
-
|
|
1246
1056
|
return false;
|
|
1247
1057
|
}
|
|
1248
1058
|
|
|
@@ -1326,27 +1136,9 @@ export class SequencerPublisher {
|
|
|
1326
1136
|
kzg,
|
|
1327
1137
|
},
|
|
1328
1138
|
)
|
|
1329
|
-
.catch(
|
|
1330
|
-
const
|
|
1331
|
-
this.log.error(`Failed to validate blobs`,
|
|
1332
|
-
const validateBlobsData = encodeFunctionData({
|
|
1333
|
-
abi: RollupAbi,
|
|
1334
|
-
functionName: 'validateBlobs',
|
|
1335
|
-
args: [blobInput],
|
|
1336
|
-
});
|
|
1337
|
-
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1338
|
-
this.backupFailedTx({
|
|
1339
|
-
id: keccak256(validateBlobsData),
|
|
1340
|
-
failureType: 'simulation',
|
|
1341
|
-
request: { to: this.rollupContract.address as Hex, data: validateBlobsData },
|
|
1342
|
-
blobData: encodedData.blobs.map(b => toHex(b.data)) as Hex[],
|
|
1343
|
-
l1BlockNumber: l1BlockNumber.toString(),
|
|
1344
|
-
error: { message: viemError.message, name: viemError.name },
|
|
1345
|
-
context: {
|
|
1346
|
-
actions: ['validate-blobs'],
|
|
1347
|
-
sender: this.getSenderAddress().toString(),
|
|
1348
|
-
},
|
|
1349
|
-
});
|
|
1139
|
+
.catch(err => {
|
|
1140
|
+
const { message, metaMessages } = formatViemError(err);
|
|
1141
|
+
this.log.error(`Failed to validate blobs`, message, { metaMessages });
|
|
1350
1142
|
throw new Error('Failed to validate blobs');
|
|
1351
1143
|
});
|
|
1352
1144
|
}
|
|
@@ -1425,8 +1217,6 @@ export class SequencerPublisher {
|
|
|
1425
1217
|
});
|
|
1426
1218
|
}
|
|
1427
1219
|
|
|
1428
|
-
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1429
|
-
|
|
1430
1220
|
const simulationResult = await this.l1TxUtils
|
|
1431
1221
|
.simulate(
|
|
1432
1222
|
{
|
|
@@ -1460,18 +1250,6 @@ export class SequencerPublisher {
|
|
|
1460
1250
|
};
|
|
1461
1251
|
}
|
|
1462
1252
|
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1463
|
-
this.backupFailedTx({
|
|
1464
|
-
id: keccak256(rollupData),
|
|
1465
|
-
failureType: 'simulation',
|
|
1466
|
-
request: { to: this.rollupContract.address, data: rollupData },
|
|
1467
|
-
l1BlockNumber: l1BlockNumber.toString(),
|
|
1468
|
-
error: { message: viemError.message, name: viemError.name },
|
|
1469
|
-
context: {
|
|
1470
|
-
actions: ['propose'],
|
|
1471
|
-
slot: Number(args[0].header.slotNumber),
|
|
1472
|
-
sender: this.getSenderAddress().toString(),
|
|
1473
|
-
},
|
|
1474
|
-
});
|
|
1475
1253
|
throw err;
|
|
1476
1254
|
});
|
|
1477
1255
|
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { NUM_CHECKPOINT_END_MARKER_FIELDS, getNumBlockEndBlobFields } from '@aztec/blob-lib/encoding';
|
|
2
|
-
import { BLOBS_PER_CHECKPOINT, FIELDS_PER_BLOB } from '@aztec/constants';
|
|
3
1
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
4
2
|
import {
|
|
5
3
|
BlockNumber,
|
|
@@ -32,11 +30,11 @@ import {
|
|
|
32
30
|
type L2BlockSource,
|
|
33
31
|
MaliciousCommitteeAttestationsAndSigners,
|
|
34
32
|
} from '@aztec/stdlib/block';
|
|
35
|
-
import type
|
|
33
|
+
import { type Checkpoint, validateCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
36
34
|
import { getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
37
35
|
import { Gas } from '@aztec/stdlib/gas';
|
|
38
36
|
import {
|
|
39
|
-
|
|
37
|
+
InsufficientValidTxsError,
|
|
40
38
|
type PublicProcessorLimits,
|
|
41
39
|
type ResolvedSequencerConfig,
|
|
42
40
|
type WorldStateSynchronizer,
|
|
@@ -267,6 +265,22 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
267
265
|
this.setStateFn(SequencerState.ASSEMBLING_CHECKPOINT, this.slot);
|
|
268
266
|
const checkpoint = await checkpointBuilder.completeCheckpoint();
|
|
269
267
|
|
|
268
|
+
// Final validation round for the checkpoint before we propose it, just for safety
|
|
269
|
+
try {
|
|
270
|
+
validateCheckpoint(checkpoint, {
|
|
271
|
+
rollupManaLimit: this.l1Constants.rollupManaLimit,
|
|
272
|
+
maxL2BlockGas: this.config.maxL2BlockGas,
|
|
273
|
+
maxDABlockGas: this.config.maxDABlockGas,
|
|
274
|
+
maxTxsPerBlock: this.config.maxTxsPerBlock,
|
|
275
|
+
maxTxsPerCheckpoint: this.config.maxTxsPerCheckpoint,
|
|
276
|
+
});
|
|
277
|
+
} catch (err) {
|
|
278
|
+
this.log.error(`Built an invalid checkpoint at slot ${this.slot} (skipping proposal)`, err, {
|
|
279
|
+
checkpoint: checkpoint.header.toInspect(),
|
|
280
|
+
});
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
283
|
+
|
|
270
284
|
// Record checkpoint-level build metrics
|
|
271
285
|
this.metrics.recordCheckpointBuild(
|
|
272
286
|
checkpointBuildTimer.ms(),
|
|
@@ -389,9 +403,6 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
389
403
|
const txHashesAlreadyIncluded = new Set<string>();
|
|
390
404
|
const initialBlockNumber = BlockNumber(this.syncedToBlockNumber + 1);
|
|
391
405
|
|
|
392
|
-
// Remaining blob fields available for blocks (checkpoint end marker already subtracted)
|
|
393
|
-
let remainingBlobFields = BLOBS_PER_CHECKPOINT * FIELDS_PER_BLOB - NUM_CHECKPOINT_END_MARKER_FIELDS;
|
|
394
|
-
|
|
395
406
|
// Last block in the checkpoint will usually be flagged as pending broadcast, so we send it along with the checkpoint proposal
|
|
396
407
|
let blockPendingBroadcast: { block: L2Block; txs: Tx[] } | undefined = undefined;
|
|
397
408
|
|
|
@@ -424,7 +435,6 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
424
435
|
blockNumber,
|
|
425
436
|
indexWithinCheckpoint,
|
|
426
437
|
txHashesAlreadyIncluded,
|
|
427
|
-
remainingBlobFields,
|
|
428
438
|
});
|
|
429
439
|
|
|
430
440
|
// TODO(palla/mbps): Review these conditions. We may want to keep trying in some scenarios.
|
|
@@ -450,12 +460,9 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
450
460
|
break;
|
|
451
461
|
}
|
|
452
462
|
|
|
453
|
-
const { block, usedTxs
|
|
463
|
+
const { block, usedTxs } = buildResult;
|
|
454
464
|
blocksInCheckpoint.push(block);
|
|
455
465
|
|
|
456
|
-
// Update remaining blob fields for the next block
|
|
457
|
-
remainingBlobFields = newRemainingBlobFields;
|
|
458
|
-
|
|
459
466
|
// Sync the proposed block to the archiver to make it available
|
|
460
467
|
// Note that the checkpoint builder uses its own fork so it should not need to wait for this syncing
|
|
461
468
|
// Eventually we should refactor the checkpoint builder to not need a separate long-lived fork
|
|
@@ -523,18 +530,10 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
523
530
|
indexWithinCheckpoint: IndexWithinCheckpoint;
|
|
524
531
|
buildDeadline: Date | undefined;
|
|
525
532
|
txHashesAlreadyIncluded: Set<string>;
|
|
526
|
-
remainingBlobFields: number;
|
|
527
533
|
},
|
|
528
|
-
): Promise<{ block: L2Block; usedTxs: Tx[]
|
|
529
|
-
const {
|
|
530
|
-
|
|
531
|
-
forceCreate,
|
|
532
|
-
blockNumber,
|
|
533
|
-
indexWithinCheckpoint,
|
|
534
|
-
buildDeadline,
|
|
535
|
-
txHashesAlreadyIncluded,
|
|
536
|
-
remainingBlobFields,
|
|
537
|
-
} = opts;
|
|
534
|
+
): Promise<{ block: L2Block; usedTxs: Tx[] } | { error: Error } | undefined> {
|
|
535
|
+
const { blockTimestamp, forceCreate, blockNumber, indexWithinCheckpoint, buildDeadline, txHashesAlreadyIncluded } =
|
|
536
|
+
opts;
|
|
538
537
|
|
|
539
538
|
this.log.verbose(
|
|
540
539
|
`Preparing block ${blockNumber} index ${indexWithinCheckpoint} at checkpoint ${this.checkpointNumber} for slot ${this.slot}`,
|
|
@@ -543,8 +542,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
543
542
|
|
|
544
543
|
try {
|
|
545
544
|
// Wait until we have enough txs to build the block
|
|
546
|
-
const minTxs = this.
|
|
547
|
-
const { availableTxs, canStartBuilding } = await this.waitForMinTxs(opts);
|
|
545
|
+
const { availableTxs, canStartBuilding, minTxs } = await this.waitForMinTxs(opts);
|
|
548
546
|
if (!canStartBuilding) {
|
|
549
547
|
this.log.warn(
|
|
550
548
|
`Not enough txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.slot} (got ${availableTxs} txs but needs ${minTxs})`,
|
|
@@ -568,19 +566,24 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
568
566
|
);
|
|
569
567
|
this.setStateFn(SequencerState.CREATING_BLOCK, this.slot);
|
|
570
568
|
|
|
571
|
-
//
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
const blockBuilderOptions: PublicProcessorLimits = {
|
|
569
|
+
// Per-block limits derived at startup by computeBlockLimits(), further capped
|
|
570
|
+
// by remaining checkpoint-level budgets inside CheckpointBuilder before each block is built.
|
|
571
|
+
// minValidTxs is passed into the builder so it can reject the block *before* updating state.
|
|
572
|
+
const minValidTxs = forceCreate ? 0 : (this.config.minValidTxsPerBlock ?? minTxs);
|
|
573
|
+
const blockBuilderOptions: PublicProcessorLimits & { minValidTxs?: number } = {
|
|
576
574
|
maxTransactions: this.config.maxTxsPerBlock,
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
575
|
+
maxBlockGas:
|
|
576
|
+
this.config.maxL2BlockGas !== undefined || this.config.maxDABlockGas !== undefined
|
|
577
|
+
? new Gas(this.config.maxDABlockGas ?? Infinity, this.config.maxL2BlockGas ?? Infinity)
|
|
578
|
+
: undefined,
|
|
580
579
|
deadline: buildDeadline,
|
|
580
|
+
isBuildingProposal: true,
|
|
581
|
+
minValidTxs,
|
|
581
582
|
};
|
|
582
583
|
|
|
583
|
-
// Actually build the block by executing txs
|
|
584
|
+
// Actually build the block by executing txs. The builder throws InsufficientValidTxsError
|
|
585
|
+
// if the number of successfully processed txs is below minValidTxs, ensuring state is not
|
|
586
|
+
// updated for blocks that will be discarded.
|
|
584
587
|
const buildResult = await this.buildSingleBlockWithCheckpointBuilder(
|
|
585
588
|
checkpointBuilder,
|
|
586
589
|
pendingTxs,
|
|
@@ -592,14 +595,16 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
592
595
|
// If any txs failed during execution, drop them from the mempool so we don't pick them up again
|
|
593
596
|
await this.dropFailedTxsFromP2P(buildResult.failedTxs);
|
|
594
597
|
|
|
595
|
-
|
|
596
|
-
// too long, then we may not get to minTxsPerBlock after executing public functions.
|
|
597
|
-
const minValidTxs = this.config.minValidTxsPerBlock ?? minTxs;
|
|
598
|
-
const numTxs = buildResult.status === 'no-valid-txs' ? 0 : buildResult.numTxs;
|
|
599
|
-
if (buildResult.status === 'no-valid-txs' || (!forceCreate && numTxs < minValidTxs)) {
|
|
598
|
+
if (buildResult.status === 'insufficient-valid-txs') {
|
|
600
599
|
this.log.warn(
|
|
601
600
|
`Block ${blockNumber} at index ${indexWithinCheckpoint} on slot ${this.slot} has too few valid txs to be proposed`,
|
|
602
|
-
{
|
|
601
|
+
{
|
|
602
|
+
slot: this.slot,
|
|
603
|
+
blockNumber,
|
|
604
|
+
numTxs: buildResult.processedCount,
|
|
605
|
+
indexWithinCheckpoint,
|
|
606
|
+
minValidTxs,
|
|
607
|
+
},
|
|
603
608
|
);
|
|
604
609
|
this.eventEmitter.emit('block-build-failed', { reason: `Insufficient valid txs`, slot: this.slot });
|
|
605
610
|
this.metrics.recordBlockProposalFailed('insufficient_valid_txs');
|
|
@@ -607,7 +612,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
607
612
|
}
|
|
608
613
|
|
|
609
614
|
// Block creation succeeded, emit stats and metrics
|
|
610
|
-
const {
|
|
615
|
+
const { block, publicProcessorDuration, usedTxs, blockBuildDuration, numTxs } = buildResult;
|
|
611
616
|
|
|
612
617
|
const blockStats = {
|
|
613
618
|
eventName: 'l2-block-built',
|
|
@@ -618,7 +623,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
618
623
|
|
|
619
624
|
const blockHash = await block.hash();
|
|
620
625
|
const txHashes = block.body.txEffects.map(tx => tx.txHash);
|
|
621
|
-
const manaPerSec =
|
|
626
|
+
const manaPerSec = block.header.totalManaUsed.toNumberUnsafe() / (blockBuildDuration / 1000);
|
|
622
627
|
|
|
623
628
|
this.log.info(
|
|
624
629
|
`Built block ${block.number} at checkpoint ${this.checkpointNumber} for slot ${this.slot} with ${numTxs} txs`,
|
|
@@ -626,9 +631,9 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
626
631
|
);
|
|
627
632
|
|
|
628
633
|
this.eventEmitter.emit('block-proposed', { blockNumber: block.number, slot: this.slot });
|
|
629
|
-
this.metrics.recordBuiltBlock(blockBuildDuration,
|
|
634
|
+
this.metrics.recordBuiltBlock(blockBuildDuration, block.header.totalManaUsed.toNumberUnsafe());
|
|
630
635
|
|
|
631
|
-
return { block, usedTxs
|
|
636
|
+
return { block, usedTxs };
|
|
632
637
|
} catch (err: any) {
|
|
633
638
|
this.eventEmitter.emit('block-build-failed', { reason: err.message, slot: this.slot });
|
|
634
639
|
this.log.error(`Error building block`, err, { blockNumber, slot: this.slot });
|
|
@@ -638,13 +643,13 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
638
643
|
}
|
|
639
644
|
}
|
|
640
645
|
|
|
641
|
-
/** Uses the checkpoint builder to build a block, catching
|
|
646
|
+
/** Uses the checkpoint builder to build a block, catching InsufficientValidTxsError. */
|
|
642
647
|
private async buildSingleBlockWithCheckpointBuilder(
|
|
643
648
|
checkpointBuilder: CheckpointBuilder,
|
|
644
649
|
pendingTxs: AsyncIterable<Tx>,
|
|
645
650
|
blockNumber: BlockNumber,
|
|
646
651
|
blockTimestamp: bigint,
|
|
647
|
-
blockBuilderOptions: PublicProcessorLimits,
|
|
652
|
+
blockBuilderOptions: PublicProcessorLimits & { minValidTxs?: number },
|
|
648
653
|
) {
|
|
649
654
|
try {
|
|
650
655
|
const workTimer = new Timer();
|
|
@@ -652,8 +657,12 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
652
657
|
const blockBuildDuration = workTimer.ms();
|
|
653
658
|
return { ...result, blockBuildDuration, status: 'success' as const };
|
|
654
659
|
} catch (err: unknown) {
|
|
655
|
-
if (isErrorClass(err,
|
|
656
|
-
return {
|
|
660
|
+
if (isErrorClass(err, InsufficientValidTxsError)) {
|
|
661
|
+
return {
|
|
662
|
+
failedTxs: err.failedTxs,
|
|
663
|
+
processedCount: err.processedCount,
|
|
664
|
+
status: 'insufficient-valid-txs' as const,
|
|
665
|
+
};
|
|
657
666
|
}
|
|
658
667
|
throw err;
|
|
659
668
|
}
|
|
@@ -666,7 +675,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
666
675
|
blockNumber: BlockNumber;
|
|
667
676
|
indexWithinCheckpoint: IndexWithinCheckpoint;
|
|
668
677
|
buildDeadline: Date | undefined;
|
|
669
|
-
}): Promise<{ canStartBuilding: boolean; availableTxs: number }> {
|
|
678
|
+
}): Promise<{ canStartBuilding: boolean; availableTxs: number; minTxs: number }> {
|
|
670
679
|
const { indexWithinCheckpoint, blockNumber, buildDeadline, forceCreate } = opts;
|
|
671
680
|
|
|
672
681
|
// We only allow a block with 0 txs in the first block of the checkpoint
|
|
@@ -683,7 +692,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
683
692
|
// If we're past deadline, or we have no deadline, give up
|
|
684
693
|
const now = this.dateProvider.nowAsDate();
|
|
685
694
|
if (startBuildingDeadline === undefined || now >= startBuildingDeadline) {
|
|
686
|
-
return { canStartBuilding: false, availableTxs
|
|
695
|
+
return { canStartBuilding: false, availableTxs, minTxs };
|
|
687
696
|
}
|
|
688
697
|
|
|
689
698
|
// Wait a bit before checking again
|
|
@@ -696,7 +705,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
696
705
|
availableTxs = await this.p2pClient.getPendingTxCount();
|
|
697
706
|
}
|
|
698
707
|
|
|
699
|
-
return { canStartBuilding: true, availableTxs };
|
|
708
|
+
return { canStartBuilding: true, availableTxs, minTxs };
|
|
700
709
|
}
|
|
701
710
|
|
|
702
711
|
/**
|
|
@@ -834,20 +843,11 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
834
843
|
this.log.warn(`Shuffling attestation ordering in checkpoint for slot ${slotNumber} (proposer #${proposerIndex})`);
|
|
835
844
|
|
|
836
845
|
const shuffled = [...attestations];
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
for (let k = 0; k < shuffled.length; k++) {
|
|
843
|
-
if (!shuffled[k].signature.isEmpty() && k !== proposerIndex) {
|
|
844
|
-
swappable.push(k);
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
if (swappable.length >= 2) {
|
|
848
|
-
const [i, j] = [swappable[0], swappable[1]];
|
|
849
|
-
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
850
|
-
}
|
|
846
|
+
const [i, j] = [(proposerIndex + 1) % shuffled.length, (proposerIndex + 2) % shuffled.length];
|
|
847
|
+
const valueI = shuffled[i];
|
|
848
|
+
const valueJ = shuffled[j];
|
|
849
|
+
shuffled[i] = valueJ;
|
|
850
|
+
shuffled[j] = valueI;
|
|
851
851
|
|
|
852
852
|
const signers = new CommitteeAttestationsAndSigners(attestations).getSigners();
|
|
853
853
|
return new MaliciousCommitteeAttestationsAndSigners(shuffled, signers);
|