@aztec/sequencer-client 0.0.1-commit.87a0206 → 0.0.1-commit.88e6f9396
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/client/sequencer-client.d.ts +12 -7
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +56 -17
- package/dest/config.d.ts +26 -7
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +47 -28
- package/dest/global_variable_builder/global_builder.d.ts +2 -4
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +5 -4
- 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 +30 -9
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +323 -38
- 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 +236 -138
- package/dest/sequencer/events.d.ts +2 -1
- package/dest/sequencer/events.d.ts.map +1 -1
- package/dest/sequencer/metrics.d.ts +21 -5
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +97 -15
- package/dest/sequencer/sequencer.d.ts +28 -15
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +91 -82
- package/dest/sequencer/timetable.d.ts +4 -6
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +7 -11
- package/dest/sequencer/types.d.ts +2 -2
- package/dest/sequencer/types.d.ts.map +1 -1
- package/dest/test/index.d.ts +3 -5
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts +11 -11
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +45 -34
- package/dest/test/utils.d.ts +3 -3
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +5 -4
- package/package.json +27 -28
- package/src/client/sequencer-client.ts +77 -18
- package/src/config.ts +65 -38
- package/src/global_variable_builder/global_builder.ts +4 -3
- 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 +327 -52
- package/src/sequencer/checkpoint_proposal_job.ts +321 -149
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/metrics.ts +106 -18
- package/src/sequencer/sequencer.ts +125 -94
- package/src/sequencer/timetable.ts +13 -12
- package/src/sequencer/types.ts +1 -1
- package/src/test/index.ts +2 -4
- package/src/test/mock_checkpoint_builder.ts +63 -49
- package/src/test/utils.ts +5 -2
|
@@ -4,6 +4,7 @@ import type { EpochCache } from '@aztec/epoch-cache';
|
|
|
4
4
|
import type { L1ContractsConfig } from '@aztec/ethereum/config';
|
|
5
5
|
import {
|
|
6
6
|
type EmpireSlashingProposerContract,
|
|
7
|
+
FeeAssetPriceOracle,
|
|
7
8
|
type GovernanceProposerContract,
|
|
8
9
|
type IEmpireBase,
|
|
9
10
|
MULTI_CALL_3_ADDRESS,
|
|
@@ -18,20 +19,23 @@ import {
|
|
|
18
19
|
type L1BlobInputs,
|
|
19
20
|
type L1TxConfig,
|
|
20
21
|
type L1TxRequest,
|
|
22
|
+
type L1TxUtils,
|
|
21
23
|
MAX_L1_TX_LIMIT,
|
|
22
24
|
type TransactionStats,
|
|
23
25
|
WEI_CONST,
|
|
24
26
|
} from '@aztec/ethereum/l1-tx-utils';
|
|
25
|
-
import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs';
|
|
26
27
|
import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
27
28
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
28
29
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
29
30
|
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
31
|
+
import { trimmedBytesLength } from '@aztec/foundation/buffer';
|
|
30
32
|
import { pick } from '@aztec/foundation/collection';
|
|
31
33
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
34
|
+
import { TimeoutError } from '@aztec/foundation/error';
|
|
32
35
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
33
36
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
34
37
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
38
|
+
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
35
39
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
36
40
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
37
41
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
@@ -43,9 +47,19 @@ import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
|
43
47
|
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
44
48
|
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
45
49
|
|
|
46
|
-
import {
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
import {
|
|
51
|
+
type Hex,
|
|
52
|
+
type StateOverride,
|
|
53
|
+
type TransactionReceipt,
|
|
54
|
+
type TypedDataDefinition,
|
|
55
|
+
encodeFunctionData,
|
|
56
|
+
keccak256,
|
|
57
|
+
multicall3Abi,
|
|
58
|
+
toHex,
|
|
59
|
+
} from 'viem';
|
|
60
|
+
|
|
61
|
+
import type { SequencerPublisherConfig } from './config.js';
|
|
62
|
+
import { type FailedL1Tx, type L1TxFailedStore, createL1TxFailedStore } from './l1_tx_failed_store/index.js';
|
|
49
63
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
50
64
|
|
|
51
65
|
/** Arguments to the process method of the rollup contract */
|
|
@@ -60,6 +74,8 @@ type L1ProcessArgs = {
|
|
|
60
74
|
attestationsAndSigners: CommitteeAttestationsAndSigners;
|
|
61
75
|
/** Attestations and signers signature */
|
|
62
76
|
attestationsAndSignersSignature: Signature;
|
|
77
|
+
/** The fee asset price modifier in basis points (from oracle) */
|
|
78
|
+
feeAssetPriceModifier: bigint;
|
|
63
79
|
};
|
|
64
80
|
|
|
65
81
|
export const Actions = [
|
|
@@ -105,6 +121,7 @@ export class SequencerPublisher {
|
|
|
105
121
|
private interrupted = false;
|
|
106
122
|
private metrics: SequencerPublisherMetrics;
|
|
107
123
|
public epochCache: EpochCache;
|
|
124
|
+
private failedTxStore?: Promise<L1TxFailedStore | undefined>;
|
|
108
125
|
|
|
109
126
|
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
110
127
|
protected slashingLog = createLogger('sequencer:publisher:slashing');
|
|
@@ -112,24 +129,33 @@ export class SequencerPublisher {
|
|
|
112
129
|
protected lastActions: Partial<Record<Action, SlotNumber>> = {};
|
|
113
130
|
|
|
114
131
|
private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
|
|
132
|
+
private payloadProposedCache: Set<string> = new Set<string>();
|
|
115
133
|
|
|
116
134
|
protected log: Logger;
|
|
117
135
|
protected ethereumSlotDuration: bigint;
|
|
136
|
+
protected aztecSlotDuration: bigint;
|
|
118
137
|
|
|
119
138
|
private blobClient: BlobClientInterface;
|
|
120
139
|
|
|
121
140
|
/** Address to use for simulations in fisherman mode (actual proposer's address) */
|
|
122
141
|
private proposerAddressForSimulation?: EthAddress;
|
|
123
142
|
|
|
143
|
+
/** Optional callback to obtain a replacement publisher when the current one fails to send. */
|
|
144
|
+
private getNextPublisher?: (excludeAddresses: EthAddress[]) => Promise<L1TxUtils | undefined>;
|
|
145
|
+
|
|
124
146
|
/** L1 fee analyzer for fisherman mode */
|
|
125
147
|
private l1FeeAnalyzer?: L1FeeAnalyzer;
|
|
148
|
+
|
|
149
|
+
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */
|
|
150
|
+
private feeAssetPriceOracle: FeeAssetPriceOracle;
|
|
151
|
+
|
|
126
152
|
// A CALL to a cold address is 2700 gas
|
|
127
153
|
public static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
128
154
|
|
|
129
155
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
130
156
|
public static VOTE_GAS_GUESS: bigint = 800_000n;
|
|
131
157
|
|
|
132
|
-
public l1TxUtils:
|
|
158
|
+
public l1TxUtils: L1TxUtils;
|
|
133
159
|
public rollupContract: RollupContract;
|
|
134
160
|
public govProposerContract: GovernanceProposerContract;
|
|
135
161
|
public slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
@@ -140,11 +166,12 @@ export class SequencerPublisher {
|
|
|
140
166
|
protected requests: RequestWithExpiry[] = [];
|
|
141
167
|
|
|
142
168
|
constructor(
|
|
143
|
-
private config:
|
|
169
|
+
private config: Pick<SequencerPublisherConfig, 'fishermanMode' | 'l1TxFailedStore'> &
|
|
170
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration'> & { l1ChainId: number },
|
|
144
171
|
deps: {
|
|
145
172
|
telemetry?: TelemetryClient;
|
|
146
173
|
blobClient: BlobClientInterface;
|
|
147
|
-
l1TxUtils:
|
|
174
|
+
l1TxUtils: L1TxUtils;
|
|
148
175
|
rollupContract: RollupContract;
|
|
149
176
|
slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
150
177
|
governanceProposerContract: GovernanceProposerContract;
|
|
@@ -154,10 +181,12 @@ export class SequencerPublisher {
|
|
|
154
181
|
metrics: SequencerPublisherMetrics;
|
|
155
182
|
lastActions: Partial<Record<Action, SlotNumber>>;
|
|
156
183
|
log?: Logger;
|
|
184
|
+
getNextPublisher?: (excludeAddresses: EthAddress[]) => Promise<L1TxUtils | undefined>;
|
|
157
185
|
},
|
|
158
186
|
) {
|
|
159
187
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
160
188
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
189
|
+
this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
|
|
161
190
|
this.epochCache = deps.epochCache;
|
|
162
191
|
this.lastActions = deps.lastActions;
|
|
163
192
|
|
|
@@ -167,6 +196,7 @@ export class SequencerPublisher {
|
|
|
167
196
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
168
197
|
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
169
198
|
this.l1TxUtils = deps.l1TxUtils;
|
|
199
|
+
this.getNextPublisher = deps.getNextPublisher;
|
|
170
200
|
|
|
171
201
|
this.rollupContract = deps.rollupContract;
|
|
172
202
|
|
|
@@ -188,12 +218,52 @@ export class SequencerPublisher {
|
|
|
188
218
|
createLogger('sequencer:publisher:fee-analyzer'),
|
|
189
219
|
);
|
|
190
220
|
}
|
|
221
|
+
|
|
222
|
+
// Initialize fee asset price oracle
|
|
223
|
+
this.feeAssetPriceOracle = new FeeAssetPriceOracle(
|
|
224
|
+
this.l1TxUtils.client,
|
|
225
|
+
this.rollupContract,
|
|
226
|
+
createLogger('sequencer:publisher:price-oracle'),
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
// Initialize failed L1 tx store (optional, for test networks)
|
|
230
|
+
this.failedTxStore = createL1TxFailedStore(config.l1TxFailedStore, this.log);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Backs up a failed L1 transaction to the configured store for debugging.
|
|
235
|
+
* Does nothing if no store is configured.
|
|
236
|
+
*/
|
|
237
|
+
private backupFailedTx(failedTx: Omit<FailedL1Tx, 'timestamp'>): void {
|
|
238
|
+
if (!this.failedTxStore) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const tx: FailedL1Tx = {
|
|
243
|
+
...failedTx,
|
|
244
|
+
timestamp: Date.now(),
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// Fire and forget - don't block on backup
|
|
248
|
+
void this.failedTxStore
|
|
249
|
+
.then(store => store?.saveFailedTx(tx))
|
|
250
|
+
.catch(err => {
|
|
251
|
+
this.log.warn(`Failed to backup failed L1 tx to store`, err);
|
|
252
|
+
});
|
|
191
253
|
}
|
|
192
254
|
|
|
193
255
|
public getRollupContract(): RollupContract {
|
|
194
256
|
return this.rollupContract;
|
|
195
257
|
}
|
|
196
258
|
|
|
259
|
+
/**
|
|
260
|
+
* Gets the fee asset price modifier from the oracle.
|
|
261
|
+
* Returns 0n if the oracle query fails.
|
|
262
|
+
*/
|
|
263
|
+
public getFeeAssetPriceModifier(): Promise<bigint> {
|
|
264
|
+
return this.feeAssetPriceOracle.computePriceModifier();
|
|
265
|
+
}
|
|
266
|
+
|
|
197
267
|
public getSenderAddress() {
|
|
198
268
|
return this.l1TxUtils.getSenderAddress();
|
|
199
269
|
}
|
|
@@ -218,7 +288,7 @@ export class SequencerPublisher {
|
|
|
218
288
|
}
|
|
219
289
|
|
|
220
290
|
public getCurrentL2Slot(): SlotNumber {
|
|
221
|
-
return this.epochCache.
|
|
291
|
+
return this.epochCache.getSlotNow();
|
|
222
292
|
}
|
|
223
293
|
|
|
224
294
|
/**
|
|
@@ -331,8 +401,8 @@ export class SequencerPublisher {
|
|
|
331
401
|
// @note - we can only have one blob config per bundle
|
|
332
402
|
// find requests with gas and blob configs
|
|
333
403
|
// See https://github.com/AztecProtocol/aztec-packages/issues/11513
|
|
334
|
-
const gasConfigs =
|
|
335
|
-
const blobConfigs =
|
|
404
|
+
const gasConfigs = validRequests.filter(request => request.gasConfig).map(request => request.gasConfig);
|
|
405
|
+
const blobConfigs = validRequests.filter(request => request.blobConfig).map(request => request.blobConfig);
|
|
336
406
|
|
|
337
407
|
if (blobConfigs.length > 1) {
|
|
338
408
|
throw new Error('Multiple blob configs found');
|
|
@@ -361,19 +431,36 @@ export class SequencerPublisher {
|
|
|
361
431
|
validRequests.sort((a, b) => compareActions(a.action, b.action));
|
|
362
432
|
|
|
363
433
|
try {
|
|
434
|
+
// Capture context for failed tx backup before sending
|
|
435
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
436
|
+
const multicallData = encodeFunctionData({
|
|
437
|
+
abi: multicall3Abi,
|
|
438
|
+
functionName: 'aggregate3',
|
|
439
|
+
args: [
|
|
440
|
+
validRequests.map(r => ({
|
|
441
|
+
target: r.request.to!,
|
|
442
|
+
callData: r.request.data!,
|
|
443
|
+
allowFailure: true,
|
|
444
|
+
})),
|
|
445
|
+
],
|
|
446
|
+
});
|
|
447
|
+
const blobDataHex = blobConfig?.blobs?.map(b => toHex(b)) as Hex[] | undefined;
|
|
448
|
+
|
|
449
|
+
const txContext = { multicallData, blobData: blobDataHex, l1BlockNumber };
|
|
450
|
+
|
|
364
451
|
this.log.debug('Forwarding transactions', {
|
|
365
452
|
validRequests: validRequests.map(request => request.action),
|
|
366
453
|
txConfig,
|
|
367
454
|
});
|
|
368
|
-
const result = await
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
455
|
+
const result = await this.forwardWithPublisherRotation(validRequests, txConfig, blobConfig);
|
|
456
|
+
if (result === undefined) {
|
|
457
|
+
return undefined;
|
|
458
|
+
}
|
|
459
|
+
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(
|
|
460
|
+
validRequests,
|
|
461
|
+
result,
|
|
462
|
+
txContext,
|
|
375
463
|
);
|
|
376
|
-
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(validRequests, result);
|
|
377
464
|
return { result, expiredActions, sentActions: validActions, successfulActions, failedActions };
|
|
378
465
|
} catch (err) {
|
|
379
466
|
const viemError = formatViemError(err);
|
|
@@ -391,16 +478,88 @@ export class SequencerPublisher {
|
|
|
391
478
|
}
|
|
392
479
|
}
|
|
393
480
|
|
|
481
|
+
/**
|
|
482
|
+
* Forwards transactions via Multicall3, rotating to the next available publisher if a send
|
|
483
|
+
* failure occurs (i.e. the tx never reached the chain).
|
|
484
|
+
* On-chain reverts and simulation errors are returned as-is without rotation.
|
|
485
|
+
*/
|
|
486
|
+
private async forwardWithPublisherRotation(
|
|
487
|
+
validRequests: RequestWithExpiry[],
|
|
488
|
+
txConfig: RequestWithExpiry['gasConfig'],
|
|
489
|
+
blobConfig: L1BlobInputs | undefined,
|
|
490
|
+
) {
|
|
491
|
+
const triedAddresses: EthAddress[] = [];
|
|
492
|
+
let currentPublisher = this.l1TxUtils;
|
|
493
|
+
|
|
494
|
+
while (true) {
|
|
495
|
+
triedAddresses.push(currentPublisher.getSenderAddress());
|
|
496
|
+
try {
|
|
497
|
+
const result = await Multicall3.forward(
|
|
498
|
+
validRequests.map(r => r.request),
|
|
499
|
+
currentPublisher,
|
|
500
|
+
txConfig,
|
|
501
|
+
blobConfig,
|
|
502
|
+
this.rollupContract.address,
|
|
503
|
+
this.log,
|
|
504
|
+
);
|
|
505
|
+
this.l1TxUtils = currentPublisher;
|
|
506
|
+
return result;
|
|
507
|
+
} catch (err) {
|
|
508
|
+
if (err instanceof TimeoutError) {
|
|
509
|
+
throw err;
|
|
510
|
+
}
|
|
511
|
+
const viemError = formatViemError(err);
|
|
512
|
+
if (!this.getNextPublisher) {
|
|
513
|
+
this.log.error('Failed to publish bundled transactions', viemError);
|
|
514
|
+
return undefined;
|
|
515
|
+
}
|
|
516
|
+
this.log.warn(
|
|
517
|
+
`Publisher ${currentPublisher.getSenderAddress()} failed to send, rotating to next publisher`,
|
|
518
|
+
viemError,
|
|
519
|
+
);
|
|
520
|
+
const nextPublisher = await this.getNextPublisher([...triedAddresses]);
|
|
521
|
+
if (!nextPublisher) {
|
|
522
|
+
this.log.error('All available publishers exhausted, failed to publish bundled transactions');
|
|
523
|
+
return undefined;
|
|
524
|
+
}
|
|
525
|
+
currentPublisher = nextPublisher;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
394
530
|
private callbackBundledTransactions(
|
|
395
531
|
requests: RequestWithExpiry[],
|
|
396
|
-
result
|
|
532
|
+
result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError | undefined,
|
|
533
|
+
txContext: { multicallData: Hex; blobData?: Hex[]; l1BlockNumber: bigint },
|
|
397
534
|
) {
|
|
398
535
|
const actionsListStr = requests.map(r => r.action).join(', ');
|
|
399
536
|
if (result instanceof FormattedViemError) {
|
|
400
537
|
this.log.error(`Failed to publish bundled transactions (${actionsListStr})`, result);
|
|
538
|
+
this.backupFailedTx({
|
|
539
|
+
id: keccak256(txContext.multicallData),
|
|
540
|
+
failureType: 'send-error',
|
|
541
|
+
request: { to: MULTI_CALL_3_ADDRESS, data: txContext.multicallData },
|
|
542
|
+
blobData: txContext.blobData,
|
|
543
|
+
l1BlockNumber: txContext.l1BlockNumber.toString(),
|
|
544
|
+
error: { message: result.message, name: result.name },
|
|
545
|
+
context: {
|
|
546
|
+
actions: requests.map(r => r.action),
|
|
547
|
+
requests: requests.map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
|
|
548
|
+
sender: this.getSenderAddress().toString(),
|
|
549
|
+
},
|
|
550
|
+
});
|
|
401
551
|
return { failedActions: requests.map(r => r.action) };
|
|
402
552
|
} else {
|
|
403
|
-
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
553
|
+
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
554
|
+
result,
|
|
555
|
+
requests: requests.map(r => ({
|
|
556
|
+
...r,
|
|
557
|
+
// Avoid logging large blob data
|
|
558
|
+
blobConfig: r.blobConfig
|
|
559
|
+
? { ...r.blobConfig, blobs: r.blobConfig.blobs.map(b => ({ size: trimmedBytesLength(b) })) }
|
|
560
|
+
: undefined,
|
|
561
|
+
})),
|
|
562
|
+
});
|
|
404
563
|
const successfulActions: Action[] = [];
|
|
405
564
|
const failedActions: Action[] = [];
|
|
406
565
|
for (const request of requests) {
|
|
@@ -410,25 +569,52 @@ export class SequencerPublisher {
|
|
|
410
569
|
failedActions.push(request.action);
|
|
411
570
|
}
|
|
412
571
|
}
|
|
572
|
+
// Single backup for the whole reverted tx
|
|
573
|
+
if (failedActions.length > 0 && result?.receipt?.status === 'reverted') {
|
|
574
|
+
this.backupFailedTx({
|
|
575
|
+
id: result.receipt.transactionHash,
|
|
576
|
+
failureType: 'revert',
|
|
577
|
+
request: { to: MULTI_CALL_3_ADDRESS, data: txContext.multicallData },
|
|
578
|
+
blobData: txContext.blobData,
|
|
579
|
+
l1BlockNumber: result.receipt.blockNumber.toString(),
|
|
580
|
+
receipt: {
|
|
581
|
+
transactionHash: result.receipt.transactionHash,
|
|
582
|
+
blockNumber: result.receipt.blockNumber.toString(),
|
|
583
|
+
gasUsed: (result.receipt.gasUsed ?? 0n).toString(),
|
|
584
|
+
status: 'reverted',
|
|
585
|
+
},
|
|
586
|
+
error: { message: result.errorMsg ?? 'Transaction reverted' },
|
|
587
|
+
context: {
|
|
588
|
+
actions: failedActions,
|
|
589
|
+
requests: requests
|
|
590
|
+
.filter(r => failedActions.includes(r.action))
|
|
591
|
+
.map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
|
|
592
|
+
sender: this.getSenderAddress().toString(),
|
|
593
|
+
},
|
|
594
|
+
});
|
|
595
|
+
}
|
|
413
596
|
return { successfulActions, failedActions };
|
|
414
597
|
}
|
|
415
598
|
}
|
|
416
599
|
|
|
417
600
|
/**
|
|
418
|
-
* @notice Will call `
|
|
601
|
+
* @notice Will call `canProposeAt` to make sure that it is possible to propose
|
|
419
602
|
* @param tipArchive - The archive to check
|
|
420
603
|
* @returns The slot and block number if it is possible to propose, undefined otherwise
|
|
421
604
|
*/
|
|
422
|
-
public
|
|
605
|
+
public canProposeAt(
|
|
423
606
|
tipArchive: Fr,
|
|
424
607
|
msgSender: EthAddress,
|
|
425
|
-
opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
608
|
+
opts: { forcePendingCheckpointNumber?: CheckpointNumber; pipelined?: boolean } = {},
|
|
426
609
|
) {
|
|
427
610
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
428
611
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
429
612
|
|
|
613
|
+
const pipelined = opts.pipelined ?? this.epochCache.isProposerPipeliningEnabled();
|
|
614
|
+
const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
|
|
615
|
+
|
|
430
616
|
return this.rollupContract
|
|
431
|
-
.
|
|
617
|
+
.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), this.ethereumSlotDuration, slotOffset, {
|
|
432
618
|
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
433
619
|
})
|
|
434
620
|
.catch(err => {
|
|
@@ -442,6 +628,7 @@ export class SequencerPublisher {
|
|
|
442
628
|
return undefined;
|
|
443
629
|
});
|
|
444
630
|
}
|
|
631
|
+
|
|
445
632
|
/**
|
|
446
633
|
* @notice Will simulate `validateHeader` to make sure that the block header is valid
|
|
447
634
|
* @dev This is a convenience function that can be used by the sequencer to validate a "partial" header.
|
|
@@ -521,6 +708,8 @@ export class SequencerPublisher {
|
|
|
521
708
|
const request = this.buildInvalidateCheckpointRequest(validationResult);
|
|
522
709
|
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
|
|
523
710
|
|
|
711
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
712
|
+
|
|
524
713
|
try {
|
|
525
714
|
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
526
715
|
request,
|
|
@@ -572,6 +761,18 @@ export class SequencerPublisher {
|
|
|
572
761
|
|
|
573
762
|
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
574
763
|
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
|
|
764
|
+
this.backupFailedTx({
|
|
765
|
+
id: keccak256(request.data!),
|
|
766
|
+
failureType: 'simulation',
|
|
767
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
768
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
769
|
+
error: { message: viemError.message, name: viemError.name },
|
|
770
|
+
context: {
|
|
771
|
+
actions: [`invalidate-${reason}`],
|
|
772
|
+
checkpointNumber,
|
|
773
|
+
sender: this.getSenderAddress().toString(),
|
|
774
|
+
},
|
|
775
|
+
});
|
|
575
776
|
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
|
|
576
777
|
}
|
|
577
778
|
}
|
|
@@ -616,25 +817,11 @@ export class SequencerPublisher {
|
|
|
616
817
|
attestationsAndSignersSignature: Signature,
|
|
617
818
|
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
618
819
|
): Promise<bigint> {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
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
|
-
|
|
820
|
+
// Anchor the simulation timestamp to the checkpoint's own slot start time
|
|
821
|
+
// rather than the current L1 block timestamp, which may overshoot into the next slot if the build ran late.
|
|
822
|
+
const ts = checkpoint.header.timestamp;
|
|
636
823
|
const blobFields = checkpoint.toBlobFields();
|
|
637
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
824
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
638
825
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
639
826
|
|
|
640
827
|
const args = [
|
|
@@ -642,7 +829,7 @@ export class SequencerPublisher {
|
|
|
642
829
|
header: checkpoint.header.toViem(),
|
|
643
830
|
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
644
831
|
oracleInput: {
|
|
645
|
-
feeAssetPriceModifier:
|
|
832
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
646
833
|
},
|
|
647
834
|
},
|
|
648
835
|
attestationsAndSigners.getPackedAttestations(),
|
|
@@ -691,6 +878,32 @@ export class SequencerPublisher {
|
|
|
691
878
|
return false;
|
|
692
879
|
}
|
|
693
880
|
|
|
881
|
+
// Check if payload was already submitted to governance
|
|
882
|
+
const cacheKey = payload.toString();
|
|
883
|
+
if (!this.payloadProposedCache.has(cacheKey)) {
|
|
884
|
+
try {
|
|
885
|
+
const l1StartBlock = await this.rollupContract.getL1StartBlock();
|
|
886
|
+
const proposed = await retry(
|
|
887
|
+
() => base.hasPayloadBeenProposed(payload.toString(), l1StartBlock),
|
|
888
|
+
'Check if payload was proposed',
|
|
889
|
+
makeBackoff([0, 1, 2]),
|
|
890
|
+
this.log,
|
|
891
|
+
true,
|
|
892
|
+
);
|
|
893
|
+
if (proposed) {
|
|
894
|
+
this.payloadProposedCache.add(cacheKey);
|
|
895
|
+
}
|
|
896
|
+
} catch (err) {
|
|
897
|
+
this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
|
|
898
|
+
return false;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
if (this.payloadProposedCache.has(cacheKey)) {
|
|
903
|
+
this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
|
|
904
|
+
return false;
|
|
905
|
+
}
|
|
906
|
+
|
|
694
907
|
const cachedLastVote = this.lastActions[signalType];
|
|
695
908
|
this.lastActions[signalType] = slotNumber;
|
|
696
909
|
const action = signalType;
|
|
@@ -709,11 +922,26 @@ export class SequencerPublisher {
|
|
|
709
922
|
lastValidL2Slot: slotNumber,
|
|
710
923
|
});
|
|
711
924
|
|
|
925
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
926
|
+
|
|
712
927
|
try {
|
|
713
928
|
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
714
929
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
715
930
|
} catch (err) {
|
|
716
|
-
|
|
931
|
+
const viemError = formatViemError(err);
|
|
932
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError);
|
|
933
|
+
this.backupFailedTx({
|
|
934
|
+
id: keccak256(request.data!),
|
|
935
|
+
failureType: 'simulation',
|
|
936
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
937
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
938
|
+
error: { message: viemError.message, name: viemError.name },
|
|
939
|
+
context: {
|
|
940
|
+
actions: [action],
|
|
941
|
+
slot: slotNumber,
|
|
942
|
+
sender: this.getSenderAddress().toString(),
|
|
943
|
+
},
|
|
944
|
+
});
|
|
717
945
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
718
946
|
}
|
|
719
947
|
|
|
@@ -918,14 +1146,15 @@ export class SequencerPublisher {
|
|
|
918
1146
|
const checkpointHeader = checkpoint.header;
|
|
919
1147
|
|
|
920
1148
|
const blobFields = checkpoint.toBlobFields();
|
|
921
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
1149
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
922
1150
|
|
|
923
|
-
const proposeTxArgs = {
|
|
1151
|
+
const proposeTxArgs: L1ProcessArgs = {
|
|
924
1152
|
header: checkpointHeader,
|
|
925
1153
|
archive: checkpoint.archive.root.toBuffer(),
|
|
926
1154
|
blobs,
|
|
927
1155
|
attestationsAndSigners,
|
|
928
1156
|
attestationsAndSignersSignature,
|
|
1157
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
929
1158
|
};
|
|
930
1159
|
|
|
931
1160
|
let ts: bigint;
|
|
@@ -1008,6 +1237,8 @@ export class SequencerPublisher {
|
|
|
1008
1237
|
|
|
1009
1238
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
1010
1239
|
|
|
1240
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1241
|
+
|
|
1011
1242
|
let gasUsed: bigint;
|
|
1012
1243
|
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
1013
1244
|
try {
|
|
@@ -1017,6 +1248,19 @@ export class SequencerPublisher {
|
|
|
1017
1248
|
const viemError = formatViemError(err, simulateAbi);
|
|
1018
1249
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1019
1250
|
|
|
1251
|
+
this.backupFailedTx({
|
|
1252
|
+
id: keccak256(request.data!),
|
|
1253
|
+
failureType: 'simulation',
|
|
1254
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
1255
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1256
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1257
|
+
context: {
|
|
1258
|
+
actions: [action],
|
|
1259
|
+
slot: slotNumber,
|
|
1260
|
+
sender: this.getSenderAddress().toString(),
|
|
1261
|
+
},
|
|
1262
|
+
});
|
|
1263
|
+
|
|
1020
1264
|
return false;
|
|
1021
1265
|
}
|
|
1022
1266
|
|
|
@@ -1100,9 +1344,27 @@ export class SequencerPublisher {
|
|
|
1100
1344
|
kzg,
|
|
1101
1345
|
},
|
|
1102
1346
|
)
|
|
1103
|
-
.catch(err => {
|
|
1104
|
-
const
|
|
1105
|
-
this.log.error(`Failed to validate blobs`, message, { metaMessages });
|
|
1347
|
+
.catch(async err => {
|
|
1348
|
+
const viemError = formatViemError(err);
|
|
1349
|
+
this.log.error(`Failed to validate blobs`, viemError.message, { metaMessages: viemError.metaMessages });
|
|
1350
|
+
const validateBlobsData = encodeFunctionData({
|
|
1351
|
+
abi: RollupAbi,
|
|
1352
|
+
functionName: 'validateBlobs',
|
|
1353
|
+
args: [blobInput],
|
|
1354
|
+
});
|
|
1355
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1356
|
+
this.backupFailedTx({
|
|
1357
|
+
id: keccak256(validateBlobsData),
|
|
1358
|
+
failureType: 'simulation',
|
|
1359
|
+
request: { to: this.rollupContract.address as Hex, data: validateBlobsData },
|
|
1360
|
+
blobData: encodedData.blobs.map(b => toHex(b.data)) as Hex[],
|
|
1361
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1362
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1363
|
+
context: {
|
|
1364
|
+
actions: ['validate-blobs'],
|
|
1365
|
+
sender: this.getSenderAddress().toString(),
|
|
1366
|
+
},
|
|
1367
|
+
});
|
|
1106
1368
|
throw new Error('Failed to validate blobs');
|
|
1107
1369
|
});
|
|
1108
1370
|
}
|
|
@@ -1113,8 +1375,7 @@ export class SequencerPublisher {
|
|
|
1113
1375
|
header: encodedData.header.toViem(),
|
|
1114
1376
|
archive: toHex(encodedData.archive),
|
|
1115
1377
|
oracleInput: {
|
|
1116
|
-
|
|
1117
|
-
feeAssetPriceModifier: 0n,
|
|
1378
|
+
feeAssetPriceModifier: encodedData.feeAssetPriceModifier,
|
|
1118
1379
|
},
|
|
1119
1380
|
},
|
|
1120
1381
|
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
@@ -1140,7 +1401,7 @@ export class SequencerPublisher {
|
|
|
1140
1401
|
readonly header: ViemHeader;
|
|
1141
1402
|
readonly archive: `0x${string}`;
|
|
1142
1403
|
readonly oracleInput: {
|
|
1143
|
-
readonly feeAssetPriceModifier:
|
|
1404
|
+
readonly feeAssetPriceModifier: bigint;
|
|
1144
1405
|
};
|
|
1145
1406
|
},
|
|
1146
1407
|
ViemCommitteeAttestations,
|
|
@@ -1182,6 +1443,8 @@ export class SequencerPublisher {
|
|
|
1182
1443
|
});
|
|
1183
1444
|
}
|
|
1184
1445
|
|
|
1446
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1447
|
+
|
|
1185
1448
|
const simulationResult = await this.l1TxUtils
|
|
1186
1449
|
.simulate(
|
|
1187
1450
|
{
|
|
@@ -1215,6 +1478,18 @@ export class SequencerPublisher {
|
|
|
1215
1478
|
};
|
|
1216
1479
|
}
|
|
1217
1480
|
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1481
|
+
this.backupFailedTx({
|
|
1482
|
+
id: keccak256(rollupData),
|
|
1483
|
+
failureType: 'simulation',
|
|
1484
|
+
request: { to: this.rollupContract.address, data: rollupData },
|
|
1485
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1486
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1487
|
+
context: {
|
|
1488
|
+
actions: ['propose'],
|
|
1489
|
+
slot: Number(args[0].header.slotNumber),
|
|
1490
|
+
sender: this.getSenderAddress().toString(),
|
|
1491
|
+
},
|
|
1492
|
+
});
|
|
1218
1493
|
throw err;
|
|
1219
1494
|
});
|
|
1220
1495
|
|