@aztec/sequencer-client 0.0.1-commit.4d79d1f2d → 0.0.1-commit.5358163d3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/client/sequencer-client.d.ts +12 -7
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +15 -4
- package/dest/config.d.ts +3 -3
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +26 -12
- package/dest/global_variable_builder/global_builder.d.ts +2 -4
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/publisher/config.d.ts +35 -17
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +106 -42
- package/dest/publisher/index.d.ts +2 -1
- package/dest/publisher/index.d.ts.map +1 -1
- package/dest/publisher/l1_tx_failed_store/factory.d.ts +11 -0
- package/dest/publisher/l1_tx_failed_store/factory.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/factory.js +22 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +59 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +1 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +15 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +34 -0
- package/dest/publisher/l1_tx_failed_store/index.d.ts +4 -0
- package/dest/publisher/l1_tx_failed_store/index.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/index.js +2 -0
- package/dest/publisher/sequencer-publisher-factory.d.ts +11 -3
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +27 -2
- package/dest/publisher/sequencer-publisher.d.ts +26 -7
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +299 -30
- package/dest/sequencer/checkpoint_proposal_job.d.ts +1 -1
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +71 -24
- package/dest/sequencer/metrics.d.ts +17 -5
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +86 -15
- package/dest/sequencer/sequencer.d.ts +18 -8
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +24 -26
- package/dest/sequencer/timetable.js +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 +5 -3
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +6 -4
- 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 +28 -28
- package/src/client/sequencer-client.ts +25 -7
- package/src/config.ts +31 -16
- package/src/global_variable_builder/global_builder.ts +1 -1
- package/src/publisher/config.ts +121 -43
- package/src/publisher/index.ts +3 -0
- package/src/publisher/l1_tx_failed_store/factory.ts +32 -0
- package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +55 -0
- package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +46 -0
- package/src/publisher/l1_tx_failed_store/index.ts +3 -0
- package/src/publisher/sequencer-publisher-factory.ts +38 -6
- package/src/publisher/sequencer-publisher.ts +300 -43
- package/src/sequencer/checkpoint_proposal_job.ts +103 -19
- package/src/sequencer/metrics.ts +92 -18
- package/src/sequencer/sequencer.ts +32 -31
- package/src/sequencer/timetable.ts +1 -1
- package/src/test/index.ts +2 -4
- package/src/test/mock_checkpoint_builder.ts +12 -1
- 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,20 +19,22 @@ 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';
|
|
30
31
|
import { pick } from '@aztec/foundation/collection';
|
|
31
32
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
33
|
+
import { TimeoutError } from '@aztec/foundation/error';
|
|
32
34
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
33
35
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
34
36
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
37
|
+
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
35
38
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
36
39
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
37
40
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
@@ -43,9 +46,19 @@ import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
|
43
46
|
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
44
47
|
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
45
48
|
|
|
46
|
-
import {
|
|
47
|
-
|
|
48
|
-
|
|
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';
|
|
59
|
+
|
|
60
|
+
import type { SequencerPublisherConfig } from './config.js';
|
|
61
|
+
import { type FailedL1Tx, type L1TxFailedStore, createL1TxFailedStore } from './l1_tx_failed_store/index.js';
|
|
49
62
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
50
63
|
|
|
51
64
|
/** Arguments to the process method of the rollup contract */
|
|
@@ -60,6 +73,8 @@ type L1ProcessArgs = {
|
|
|
60
73
|
attestationsAndSigners: CommitteeAttestationsAndSigners;
|
|
61
74
|
/** Attestations and signers signature */
|
|
62
75
|
attestationsAndSignersSignature: Signature;
|
|
76
|
+
/** The fee asset price modifier in basis points (from oracle) */
|
|
77
|
+
feeAssetPriceModifier: bigint;
|
|
63
78
|
};
|
|
64
79
|
|
|
65
80
|
export const Actions = [
|
|
@@ -105,6 +120,7 @@ export class SequencerPublisher {
|
|
|
105
120
|
private interrupted = false;
|
|
106
121
|
private metrics: SequencerPublisherMetrics;
|
|
107
122
|
public epochCache: EpochCache;
|
|
123
|
+
private failedTxStore?: Promise<L1TxFailedStore | undefined>;
|
|
108
124
|
|
|
109
125
|
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
110
126
|
protected slashingLog = createLogger('sequencer:publisher:slashing');
|
|
@@ -112,6 +128,7 @@ export class SequencerPublisher {
|
|
|
112
128
|
protected lastActions: Partial<Record<Action, SlotNumber>> = {};
|
|
113
129
|
|
|
114
130
|
private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
|
|
131
|
+
private payloadProposedCache: Set<string> = new Set<string>();
|
|
115
132
|
|
|
116
133
|
protected log: Logger;
|
|
117
134
|
protected ethereumSlotDuration: bigint;
|
|
@@ -121,15 +138,22 @@ export class SequencerPublisher {
|
|
|
121
138
|
/** Address to use for simulations in fisherman mode (actual proposer's address) */
|
|
122
139
|
private proposerAddressForSimulation?: EthAddress;
|
|
123
140
|
|
|
141
|
+
/** Optional callback to obtain a replacement publisher when the current one fails to send. */
|
|
142
|
+
private getNextPublisher?: (excludeAddresses: EthAddress[]) => Promise<L1TxUtils | undefined>;
|
|
143
|
+
|
|
124
144
|
/** L1 fee analyzer for fisherman mode */
|
|
125
145
|
private l1FeeAnalyzer?: L1FeeAnalyzer;
|
|
146
|
+
|
|
147
|
+
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */
|
|
148
|
+
private feeAssetPriceOracle: FeeAssetPriceOracle;
|
|
149
|
+
|
|
126
150
|
// A CALL to a cold address is 2700 gas
|
|
127
151
|
public static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
128
152
|
|
|
129
153
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
130
154
|
public static VOTE_GAS_GUESS: bigint = 800_000n;
|
|
131
155
|
|
|
132
|
-
public l1TxUtils:
|
|
156
|
+
public l1TxUtils: L1TxUtils;
|
|
133
157
|
public rollupContract: RollupContract;
|
|
134
158
|
public govProposerContract: GovernanceProposerContract;
|
|
135
159
|
public slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
@@ -140,11 +164,12 @@ export class SequencerPublisher {
|
|
|
140
164
|
protected requests: RequestWithExpiry[] = [];
|
|
141
165
|
|
|
142
166
|
constructor(
|
|
143
|
-
private config:
|
|
167
|
+
private config: Pick<SequencerPublisherConfig, 'fishermanMode' | 'l1TxFailedStore'> &
|
|
168
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration'> & { l1ChainId: number },
|
|
144
169
|
deps: {
|
|
145
170
|
telemetry?: TelemetryClient;
|
|
146
171
|
blobClient: BlobClientInterface;
|
|
147
|
-
l1TxUtils:
|
|
172
|
+
l1TxUtils: L1TxUtils;
|
|
148
173
|
rollupContract: RollupContract;
|
|
149
174
|
slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
150
175
|
governanceProposerContract: GovernanceProposerContract;
|
|
@@ -154,6 +179,7 @@ export class SequencerPublisher {
|
|
|
154
179
|
metrics: SequencerPublisherMetrics;
|
|
155
180
|
lastActions: Partial<Record<Action, SlotNumber>>;
|
|
156
181
|
log?: Logger;
|
|
182
|
+
getNextPublisher?: (excludeAddresses: EthAddress[]) => Promise<L1TxUtils | undefined>;
|
|
157
183
|
},
|
|
158
184
|
) {
|
|
159
185
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
@@ -167,6 +193,7 @@ export class SequencerPublisher {
|
|
|
167
193
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
168
194
|
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
169
195
|
this.l1TxUtils = deps.l1TxUtils;
|
|
196
|
+
this.getNextPublisher = deps.getNextPublisher;
|
|
170
197
|
|
|
171
198
|
this.rollupContract = deps.rollupContract;
|
|
172
199
|
|
|
@@ -188,12 +215,52 @@ export class SequencerPublisher {
|
|
|
188
215
|
createLogger('sequencer:publisher:fee-analyzer'),
|
|
189
216
|
);
|
|
190
217
|
}
|
|
218
|
+
|
|
219
|
+
// Initialize fee asset price oracle
|
|
220
|
+
this.feeAssetPriceOracle = new FeeAssetPriceOracle(
|
|
221
|
+
this.l1TxUtils.client,
|
|
222
|
+
this.rollupContract,
|
|
223
|
+
createLogger('sequencer:publisher:price-oracle'),
|
|
224
|
+
);
|
|
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
|
+
});
|
|
191
250
|
}
|
|
192
251
|
|
|
193
252
|
public getRollupContract(): RollupContract {
|
|
194
253
|
return this.rollupContract;
|
|
195
254
|
}
|
|
196
255
|
|
|
256
|
+
/**
|
|
257
|
+
* Gets the fee asset price modifier from the oracle.
|
|
258
|
+
* Returns 0n if the oracle query fails.
|
|
259
|
+
*/
|
|
260
|
+
public getFeeAssetPriceModifier(): Promise<bigint> {
|
|
261
|
+
return this.feeAssetPriceOracle.computePriceModifier();
|
|
262
|
+
}
|
|
263
|
+
|
|
197
264
|
public getSenderAddress() {
|
|
198
265
|
return this.l1TxUtils.getSenderAddress();
|
|
199
266
|
}
|
|
@@ -361,19 +428,36 @@ export class SequencerPublisher {
|
|
|
361
428
|
validRequests.sort((a, b) => compareActions(a.action, b.action));
|
|
362
429
|
|
|
363
430
|
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
|
+
|
|
364
448
|
this.log.debug('Forwarding transactions', {
|
|
365
449
|
validRequests: validRequests.map(request => request.action),
|
|
366
450
|
txConfig,
|
|
367
451
|
});
|
|
368
|
-
const result = await
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
452
|
+
const result = await this.forwardWithPublisherRotation(validRequests, txConfig, blobConfig);
|
|
453
|
+
if (result === undefined) {
|
|
454
|
+
return undefined;
|
|
455
|
+
}
|
|
456
|
+
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(
|
|
457
|
+
validRequests,
|
|
458
|
+
result,
|
|
459
|
+
txContext,
|
|
375
460
|
);
|
|
376
|
-
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(validRequests, result);
|
|
377
461
|
return { result, expiredActions, sentActions: validActions, successfulActions, failedActions };
|
|
378
462
|
} catch (err) {
|
|
379
463
|
const viemError = formatViemError(err);
|
|
@@ -391,13 +475,76 @@ export class SequencerPublisher {
|
|
|
391
475
|
}
|
|
392
476
|
}
|
|
393
477
|
|
|
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
|
+
|
|
394
527
|
private callbackBundledTransactions(
|
|
395
528
|
requests: RequestWithExpiry[],
|
|
396
|
-
result
|
|
529
|
+
result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError | undefined,
|
|
530
|
+
txContext: { multicallData: Hex; blobData?: Hex[]; l1BlockNumber: bigint },
|
|
397
531
|
) {
|
|
398
532
|
const actionsListStr = requests.map(r => r.action).join(', ');
|
|
399
533
|
if (result instanceof FormattedViemError) {
|
|
400
534
|
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
|
+
});
|
|
401
548
|
return { failedActions: requests.map(r => r.action) };
|
|
402
549
|
} else {
|
|
403
550
|
this.log.verbose(`Published bundled transactions (${actionsListStr})`, { result, requests });
|
|
@@ -410,6 +557,30 @@ export class SequencerPublisher {
|
|
|
410
557
|
failedActions.push(request.action);
|
|
411
558
|
}
|
|
412
559
|
}
|
|
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
|
+
}
|
|
413
584
|
return { successfulActions, failedActions };
|
|
414
585
|
}
|
|
415
586
|
}
|
|
@@ -521,6 +692,8 @@ export class SequencerPublisher {
|
|
|
521
692
|
const request = this.buildInvalidateCheckpointRequest(validationResult);
|
|
522
693
|
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
|
|
523
694
|
|
|
695
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
696
|
+
|
|
524
697
|
try {
|
|
525
698
|
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
526
699
|
request,
|
|
@@ -572,6 +745,18 @@ export class SequencerPublisher {
|
|
|
572
745
|
|
|
573
746
|
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
574
747
|
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
|
+
});
|
|
575
760
|
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
|
|
576
761
|
}
|
|
577
762
|
}
|
|
@@ -617,24 +802,8 @@ export class SequencerPublisher {
|
|
|
617
802
|
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
618
803
|
): Promise<bigint> {
|
|
619
804
|
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
|
-
|
|
636
805
|
const blobFields = checkpoint.toBlobFields();
|
|
637
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
806
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
638
807
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
639
808
|
|
|
640
809
|
const args = [
|
|
@@ -642,7 +811,7 @@ export class SequencerPublisher {
|
|
|
642
811
|
header: checkpoint.header.toViem(),
|
|
643
812
|
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
644
813
|
oracleInput: {
|
|
645
|
-
feeAssetPriceModifier:
|
|
814
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
646
815
|
},
|
|
647
816
|
},
|
|
648
817
|
attestationsAndSigners.getPackedAttestations(),
|
|
@@ -691,6 +860,32 @@ export class SequencerPublisher {
|
|
|
691
860
|
return false;
|
|
692
861
|
}
|
|
693
862
|
|
|
863
|
+
// Check if payload was already submitted to governance
|
|
864
|
+
const cacheKey = payload.toString();
|
|
865
|
+
if (!this.payloadProposedCache.has(cacheKey)) {
|
|
866
|
+
try {
|
|
867
|
+
const l1StartBlock = await this.rollupContract.getL1StartBlock();
|
|
868
|
+
const proposed = await retry(
|
|
869
|
+
() => base.hasPayloadBeenProposed(payload.toString(), l1StartBlock),
|
|
870
|
+
'Check if payload was proposed',
|
|
871
|
+
makeBackoff([0, 1, 2]),
|
|
872
|
+
this.log,
|
|
873
|
+
true,
|
|
874
|
+
);
|
|
875
|
+
if (proposed) {
|
|
876
|
+
this.payloadProposedCache.add(cacheKey);
|
|
877
|
+
}
|
|
878
|
+
} catch (err) {
|
|
879
|
+
this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
|
|
880
|
+
return false;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
if (this.payloadProposedCache.has(cacheKey)) {
|
|
885
|
+
this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
|
|
886
|
+
return false;
|
|
887
|
+
}
|
|
888
|
+
|
|
694
889
|
const cachedLastVote = this.lastActions[signalType];
|
|
695
890
|
this.lastActions[signalType] = slotNumber;
|
|
696
891
|
const action = signalType;
|
|
@@ -709,11 +904,26 @@ export class SequencerPublisher {
|
|
|
709
904
|
lastValidL2Slot: slotNumber,
|
|
710
905
|
});
|
|
711
906
|
|
|
907
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
908
|
+
|
|
712
909
|
try {
|
|
713
910
|
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
714
911
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
715
912
|
} catch (err) {
|
|
716
|
-
|
|
913
|
+
const viemError = formatViemError(err);
|
|
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
|
+
});
|
|
717
927
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
718
928
|
}
|
|
719
929
|
|
|
@@ -918,14 +1128,15 @@ export class SequencerPublisher {
|
|
|
918
1128
|
const checkpointHeader = checkpoint.header;
|
|
919
1129
|
|
|
920
1130
|
const blobFields = checkpoint.toBlobFields();
|
|
921
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
1131
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
922
1132
|
|
|
923
|
-
const proposeTxArgs = {
|
|
1133
|
+
const proposeTxArgs: L1ProcessArgs = {
|
|
924
1134
|
header: checkpointHeader,
|
|
925
1135
|
archive: checkpoint.archive.root.toBuffer(),
|
|
926
1136
|
blobs,
|
|
927
1137
|
attestationsAndSigners,
|
|
928
1138
|
attestationsAndSignersSignature,
|
|
1139
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
929
1140
|
};
|
|
930
1141
|
|
|
931
1142
|
let ts: bigint;
|
|
@@ -1008,6 +1219,8 @@ export class SequencerPublisher {
|
|
|
1008
1219
|
|
|
1009
1220
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
1010
1221
|
|
|
1222
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1223
|
+
|
|
1011
1224
|
let gasUsed: bigint;
|
|
1012
1225
|
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
1013
1226
|
try {
|
|
@@ -1017,6 +1230,19 @@ export class SequencerPublisher {
|
|
|
1017
1230
|
const viemError = formatViemError(err, simulateAbi);
|
|
1018
1231
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1019
1232
|
|
|
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
|
+
|
|
1020
1246
|
return false;
|
|
1021
1247
|
}
|
|
1022
1248
|
|
|
@@ -1100,9 +1326,27 @@ export class SequencerPublisher {
|
|
|
1100
1326
|
kzg,
|
|
1101
1327
|
},
|
|
1102
1328
|
)
|
|
1103
|
-
.catch(err => {
|
|
1104
|
-
const
|
|
1105
|
-
this.log.error(`Failed to validate blobs`, message, { metaMessages });
|
|
1329
|
+
.catch(async err => {
|
|
1330
|
+
const viemError = formatViemError(err);
|
|
1331
|
+
this.log.error(`Failed to validate blobs`, viemError.message, { metaMessages: viemError.metaMessages });
|
|
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
|
+
});
|
|
1106
1350
|
throw new Error('Failed to validate blobs');
|
|
1107
1351
|
});
|
|
1108
1352
|
}
|
|
@@ -1113,8 +1357,7 @@ export class SequencerPublisher {
|
|
|
1113
1357
|
header: encodedData.header.toViem(),
|
|
1114
1358
|
archive: toHex(encodedData.archive),
|
|
1115
1359
|
oracleInput: {
|
|
1116
|
-
|
|
1117
|
-
feeAssetPriceModifier: 0n,
|
|
1360
|
+
feeAssetPriceModifier: encodedData.feeAssetPriceModifier,
|
|
1118
1361
|
},
|
|
1119
1362
|
},
|
|
1120
1363
|
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
@@ -1140,7 +1383,7 @@ export class SequencerPublisher {
|
|
|
1140
1383
|
readonly header: ViemHeader;
|
|
1141
1384
|
readonly archive: `0x${string}`;
|
|
1142
1385
|
readonly oracleInput: {
|
|
1143
|
-
readonly feeAssetPriceModifier:
|
|
1386
|
+
readonly feeAssetPriceModifier: bigint;
|
|
1144
1387
|
};
|
|
1145
1388
|
},
|
|
1146
1389
|
ViemCommitteeAttestations,
|
|
@@ -1182,6 +1425,8 @@ export class SequencerPublisher {
|
|
|
1182
1425
|
});
|
|
1183
1426
|
}
|
|
1184
1427
|
|
|
1428
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1429
|
+
|
|
1185
1430
|
const simulationResult = await this.l1TxUtils
|
|
1186
1431
|
.simulate(
|
|
1187
1432
|
{
|
|
@@ -1215,6 +1460,18 @@ export class SequencerPublisher {
|
|
|
1215
1460
|
};
|
|
1216
1461
|
}
|
|
1217
1462
|
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
|
+
});
|
|
1218
1475
|
throw err;
|
|
1219
1476
|
});
|
|
1220
1477
|
|