@aztec/sequencer-client 0.0.1-commit.03f7ef2 → 0.0.1-commit.0658669b3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/client/sequencer-client.d.ts +15 -11
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +15 -4
- package/dest/config.d.ts +3 -4
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +22 -12
- package/dest/global_variable_builder/global_builder.d.ts +5 -7
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +13 -13
- package/dest/index.d.ts +2 -3
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -2
- 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 +12 -4
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +13 -2
- package/dest/publisher/sequencer-publisher-metrics.d.ts +1 -1
- package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-metrics.js +23 -86
- package/dest/publisher/sequencer-publisher.d.ts +40 -25
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +740 -100
- package/dest/sequencer/checkpoint_proposal_job.d.ts +40 -12
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +643 -64
- package/dest/sequencer/checkpoint_voter.d.ts +3 -2
- package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_voter.js +34 -10
- package/dest/sequencer/index.d.ts +1 -3
- package/dest/sequencer/index.d.ts.map +1 -1
- package/dest/sequencer/index.js +0 -2
- package/dest/sequencer/metrics.d.ts +19 -7
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +131 -141
- package/dest/sequencer/sequencer.d.ts +38 -18
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +513 -66
- package/dest/sequencer/timetable.d.ts +1 -4
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +1 -4
- package/dest/test/index.d.ts +4 -7
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts +25 -11
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +52 -9
- package/dest/test/utils.d.ts +13 -9
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +27 -17
- package/package.json +30 -28
- package/src/client/sequencer-client.ts +29 -12
- package/src/config.ts +31 -19
- package/src/global_variable_builder/global_builder.ts +14 -14
- package/src/index.ts +1 -9
- 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 +24 -7
- package/src/publisher/sequencer-publisher-metrics.ts +17 -69
- package/src/publisher/sequencer-publisher.ts +361 -130
- package/src/sequencer/checkpoint_proposal_job.ts +316 -93
- package/src/sequencer/checkpoint_voter.ts +32 -7
- package/src/sequencer/index.ts +0 -2
- package/src/sequencer/metrics.ts +132 -148
- package/src/sequencer/sequencer.ts +159 -68
- package/src/sequencer/timetable.ts +6 -5
- package/src/test/index.ts +3 -6
- package/src/test/mock_checkpoint_builder.ts +102 -29
- package/src/test/utils.ts +58 -28
- package/dest/sequencer/block_builder.d.ts +0 -26
- package/dest/sequencer/block_builder.d.ts.map +0 -1
- package/dest/sequencer/block_builder.js +0 -129
- package/dest/sequencer/checkpoint_builder.d.ts +0 -63
- package/dest/sequencer/checkpoint_builder.d.ts.map +0 -1
- package/dest/sequencer/checkpoint_builder.js +0 -131
- package/dest/tx_validator/nullifier_cache.d.ts +0 -14
- package/dest/tx_validator/nullifier_cache.d.ts.map +0 -1
- package/dest/tx_validator/nullifier_cache.js +0 -24
- package/dest/tx_validator/tx_validator_factory.d.ts +0 -18
- package/dest/tx_validator/tx_validator_factory.d.ts.map +0 -1
- package/dest/tx_validator/tx_validator_factory.js +0 -53
- package/src/sequencer/block_builder.ts +0 -217
- package/src/sequencer/checkpoint_builder.ts +0 -217
- package/src/tx_validator/nullifier_cache.ts +0 -30
- package/src/tx_validator/tx_validator_factory.ts +0 -133
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { BlobClientInterface } from '@aztec/blob-client/client';
|
|
2
2
|
import { Blob, getBlobsPerL1Block, getPrefixedEthBlobCommitments } from '@aztec/blob-lib';
|
|
3
3
|
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,33 +19,45 @@ import {
|
|
|
18
19
|
type L1BlobInputs,
|
|
19
20
|
type L1TxConfig,
|
|
20
21
|
type L1TxRequest,
|
|
22
|
+
type L1TxUtils,
|
|
23
|
+
MAX_L1_TX_LIMIT,
|
|
21
24
|
type TransactionStats,
|
|
22
25
|
WEI_CONST,
|
|
23
26
|
} from '@aztec/ethereum/l1-tx-utils';
|
|
24
|
-
import
|
|
25
|
-
import { FormattedViemError, formatViemError, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
27
|
+
import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
26
28
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
27
29
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
28
|
-
import {
|
|
30
|
+
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
29
31
|
import { pick } from '@aztec/foundation/collection';
|
|
30
32
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
31
33
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
32
34
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
33
35
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
36
|
+
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
34
37
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
35
38
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
36
39
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
37
40
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
38
|
-
import { CommitteeAttestationsAndSigners, type
|
|
41
|
+
import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
39
42
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
40
43
|
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
41
44
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
42
45
|
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
43
|
-
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
46
|
+
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
44
47
|
|
|
45
|
-
import {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
import {
|
|
49
|
+
type Hex,
|
|
50
|
+
type StateOverride,
|
|
51
|
+
type TransactionReceipt,
|
|
52
|
+
type TypedDataDefinition,
|
|
53
|
+
encodeFunctionData,
|
|
54
|
+
keccak256,
|
|
55
|
+
multicall3Abi,
|
|
56
|
+
toHex,
|
|
57
|
+
} from 'viem';
|
|
58
|
+
|
|
59
|
+
import type { SequencerPublisherConfig } from './config.js';
|
|
60
|
+
import { type FailedL1Tx, type L1TxFailedStore, createL1TxFailedStore } from './l1_tx_failed_store/index.js';
|
|
48
61
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
49
62
|
|
|
50
63
|
/** Arguments to the process method of the rollup contract */
|
|
@@ -59,6 +72,8 @@ type L1ProcessArgs = {
|
|
|
59
72
|
attestationsAndSigners: CommitteeAttestationsAndSigners;
|
|
60
73
|
/** Attestations and signers signature */
|
|
61
74
|
attestationsAndSignersSignature: Signature;
|
|
75
|
+
/** The fee asset price modifier in basis points (from oracle) */
|
|
76
|
+
feeAssetPriceModifier: bigint;
|
|
62
77
|
};
|
|
63
78
|
|
|
64
79
|
export const Actions = [
|
|
@@ -80,12 +95,12 @@ type GovernanceSignalAction = Extract<Action, 'governance-signal' | 'empire-slas
|
|
|
80
95
|
// Sorting for actions such that invalidations go before proposals, and proposals go before votes
|
|
81
96
|
export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
|
|
82
97
|
|
|
83
|
-
export type
|
|
98
|
+
export type InvalidateCheckpointRequest = {
|
|
84
99
|
request: L1TxRequest;
|
|
85
100
|
reason: 'invalid-attestation' | 'insufficient-attestations';
|
|
86
101
|
gasUsed: bigint;
|
|
87
|
-
|
|
88
|
-
|
|
102
|
+
checkpointNumber: CheckpointNumber;
|
|
103
|
+
forcePendingCheckpointNumber: CheckpointNumber;
|
|
89
104
|
};
|
|
90
105
|
|
|
91
106
|
interface RequestWithExpiry {
|
|
@@ -104,6 +119,7 @@ export class SequencerPublisher {
|
|
|
104
119
|
private interrupted = false;
|
|
105
120
|
private metrics: SequencerPublisherMetrics;
|
|
106
121
|
public epochCache: EpochCache;
|
|
122
|
+
private failedTxStore?: Promise<L1TxFailedStore | undefined>;
|
|
107
123
|
|
|
108
124
|
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
109
125
|
protected slashingLog = createLogger('sequencer:publisher:slashing');
|
|
@@ -111,6 +127,7 @@ export class SequencerPublisher {
|
|
|
111
127
|
protected lastActions: Partial<Record<Action, SlotNumber>> = {};
|
|
112
128
|
|
|
113
129
|
private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
|
|
130
|
+
private payloadProposedCache: Set<string> = new Set<string>();
|
|
114
131
|
|
|
115
132
|
protected log: Logger;
|
|
116
133
|
protected ethereumSlotDuration: bigint;
|
|
@@ -122,10 +139,9 @@ export class SequencerPublisher {
|
|
|
122
139
|
|
|
123
140
|
/** L1 fee analyzer for fisherman mode */
|
|
124
141
|
private l1FeeAnalyzer?: L1FeeAnalyzer;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
public static PROPOSE_GAS_GUESS: bigint = 12_000_000n;
|
|
142
|
+
|
|
143
|
+
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */
|
|
144
|
+
private feeAssetPriceOracle: FeeAssetPriceOracle;
|
|
129
145
|
|
|
130
146
|
// A CALL to a cold address is 2700 gas
|
|
131
147
|
public static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
@@ -133,20 +149,23 @@ export class SequencerPublisher {
|
|
|
133
149
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
134
150
|
public static VOTE_GAS_GUESS: bigint = 800_000n;
|
|
135
151
|
|
|
136
|
-
public l1TxUtils:
|
|
152
|
+
public l1TxUtils: L1TxUtils;
|
|
137
153
|
public rollupContract: RollupContract;
|
|
138
154
|
public govProposerContract: GovernanceProposerContract;
|
|
139
155
|
public slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
140
156
|
public slashFactoryContract: SlashFactoryContract;
|
|
141
157
|
|
|
158
|
+
public readonly tracer: Tracer;
|
|
159
|
+
|
|
142
160
|
protected requests: RequestWithExpiry[] = [];
|
|
143
161
|
|
|
144
162
|
constructor(
|
|
145
|
-
private config:
|
|
163
|
+
private config: Pick<SequencerPublisherConfig, 'fishermanMode' | 'l1TxFailedStore'> &
|
|
164
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration'> & { l1ChainId: number },
|
|
146
165
|
deps: {
|
|
147
166
|
telemetry?: TelemetryClient;
|
|
148
|
-
blobClient
|
|
149
|
-
l1TxUtils:
|
|
167
|
+
blobClient: BlobClientInterface;
|
|
168
|
+
l1TxUtils: L1TxUtils;
|
|
150
169
|
rollupContract: RollupContract;
|
|
151
170
|
slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
152
171
|
governanceProposerContract: GovernanceProposerContract;
|
|
@@ -163,11 +182,11 @@ export class SequencerPublisher {
|
|
|
163
182
|
this.epochCache = deps.epochCache;
|
|
164
183
|
this.lastActions = deps.lastActions;
|
|
165
184
|
|
|
166
|
-
this.blobClient =
|
|
167
|
-
deps.blobClient ?? createBlobClient(config, { logger: createLogger('sequencer:blob-client:client') });
|
|
185
|
+
this.blobClient = deps.blobClient;
|
|
168
186
|
|
|
169
187
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
170
188
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
189
|
+
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
171
190
|
this.l1TxUtils = deps.l1TxUtils;
|
|
172
191
|
|
|
173
192
|
this.rollupContract = deps.rollupContract;
|
|
@@ -190,12 +209,52 @@ export class SequencerPublisher {
|
|
|
190
209
|
createLogger('sequencer:publisher:fee-analyzer'),
|
|
191
210
|
);
|
|
192
211
|
}
|
|
212
|
+
|
|
213
|
+
// Initialize fee asset price oracle
|
|
214
|
+
this.feeAssetPriceOracle = new FeeAssetPriceOracle(
|
|
215
|
+
this.l1TxUtils.client,
|
|
216
|
+
this.rollupContract,
|
|
217
|
+
createLogger('sequencer:publisher:price-oracle'),
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// Initialize failed L1 tx store (optional, for test networks)
|
|
221
|
+
this.failedTxStore = createL1TxFailedStore(config.l1TxFailedStore, this.log);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Backs up a failed L1 transaction to the configured store for debugging.
|
|
226
|
+
* Does nothing if no store is configured.
|
|
227
|
+
*/
|
|
228
|
+
private backupFailedTx(failedTx: Omit<FailedL1Tx, 'timestamp'>): void {
|
|
229
|
+
if (!this.failedTxStore) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const tx: FailedL1Tx = {
|
|
234
|
+
...failedTx,
|
|
235
|
+
timestamp: Date.now(),
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Fire and forget - don't block on backup
|
|
239
|
+
void this.failedTxStore
|
|
240
|
+
.then(store => store?.saveFailedTx(tx))
|
|
241
|
+
.catch(err => {
|
|
242
|
+
this.log.warn(`Failed to backup failed L1 tx to store`, err);
|
|
243
|
+
});
|
|
193
244
|
}
|
|
194
245
|
|
|
195
246
|
public getRollupContract(): RollupContract {
|
|
196
247
|
return this.rollupContract;
|
|
197
248
|
}
|
|
198
249
|
|
|
250
|
+
/**
|
|
251
|
+
* Gets the fee asset price modifier from the oracle.
|
|
252
|
+
* Returns 0n if the oracle query fails.
|
|
253
|
+
*/
|
|
254
|
+
public getFeeAssetPriceModifier(): Promise<bigint> {
|
|
255
|
+
return this.feeAssetPriceOracle.computePriceModifier();
|
|
256
|
+
}
|
|
257
|
+
|
|
199
258
|
public getSenderAddress() {
|
|
200
259
|
return this.l1TxUtils.getSenderAddress();
|
|
201
260
|
}
|
|
@@ -271,7 +330,7 @@ export class SequencerPublisher {
|
|
|
271
330
|
// Start the analysis
|
|
272
331
|
const analysisId = await this.l1FeeAnalyzer.startAnalysis(
|
|
273
332
|
l2SlotNumber,
|
|
274
|
-
gasLimit > 0n ? gasLimit :
|
|
333
|
+
gasLimit > 0n ? gasLimit : MAX_L1_TX_LIMIT,
|
|
275
334
|
l1Requests,
|
|
276
335
|
blobConfig,
|
|
277
336
|
onComplete,
|
|
@@ -297,6 +356,7 @@ export class SequencerPublisher {
|
|
|
297
356
|
* - a receipt and errorMsg if it failed on L1
|
|
298
357
|
* - undefined if no valid requests are found OR the tx failed to send.
|
|
299
358
|
*/
|
|
359
|
+
@trackSpan('SequencerPublisher.sendRequests')
|
|
300
360
|
public async sendRequests() {
|
|
301
361
|
const requestsToProcess = [...this.requests];
|
|
302
362
|
this.requests = [];
|
|
@@ -343,7 +403,16 @@ export class SequencerPublisher {
|
|
|
343
403
|
|
|
344
404
|
// Merge gasConfigs. Yields the sum of gasLimits, and the earliest txTimeoutAt, or undefined if no gasConfig sets them.
|
|
345
405
|
const gasLimits = gasConfigs.map(g => g?.gasLimit).filter((g): g is bigint => g !== undefined);
|
|
346
|
-
|
|
406
|
+
let gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
|
|
407
|
+
// Cap at L1 block gas limit so the node accepts the tx ("gas limit too high" otherwise).
|
|
408
|
+
const maxGas = MAX_L1_TX_LIMIT;
|
|
409
|
+
if (gasLimit !== undefined && gasLimit > maxGas) {
|
|
410
|
+
this.log.debug('Capping bundled tx gas limit to L1 max', {
|
|
411
|
+
requested: gasLimit,
|
|
412
|
+
capped: maxGas,
|
|
413
|
+
});
|
|
414
|
+
gasLimit = maxGas;
|
|
415
|
+
}
|
|
347
416
|
const txTimeoutAts = gasConfigs.map(g => g?.txTimeoutAt).filter((g): g is Date => g !== undefined);
|
|
348
417
|
const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map(g => g.getTime()))) : undefined; // earliest
|
|
349
418
|
const txConfig: RequestWithExpiry['gasConfig'] = { gasLimit, txTimeoutAt };
|
|
@@ -353,6 +422,21 @@ export class SequencerPublisher {
|
|
|
353
422
|
validRequests.sort((a, b) => compareActions(a.action, b.action));
|
|
354
423
|
|
|
355
424
|
try {
|
|
425
|
+
// Capture context for failed tx backup before sending
|
|
426
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
427
|
+
const multicallData = encodeFunctionData({
|
|
428
|
+
abi: multicall3Abi,
|
|
429
|
+
functionName: 'aggregate3',
|
|
430
|
+
args: [
|
|
431
|
+
validRequests.map(r => ({
|
|
432
|
+
target: r.request.to!,
|
|
433
|
+
callData: r.request.data!,
|
|
434
|
+
allowFailure: true,
|
|
435
|
+
})),
|
|
436
|
+
],
|
|
437
|
+
});
|
|
438
|
+
const blobDataHex = blobConfig?.blobs?.map(b => toHex(b)) as Hex[] | undefined;
|
|
439
|
+
|
|
356
440
|
this.log.debug('Forwarding transactions', {
|
|
357
441
|
validRequests: validRequests.map(request => request.action),
|
|
358
442
|
txConfig,
|
|
@@ -365,7 +449,12 @@ export class SequencerPublisher {
|
|
|
365
449
|
this.rollupContract.address,
|
|
366
450
|
this.log,
|
|
367
451
|
);
|
|
368
|
-
const
|
|
452
|
+
const txContext = { multicallData, blobData: blobDataHex, l1BlockNumber };
|
|
453
|
+
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(
|
|
454
|
+
validRequests,
|
|
455
|
+
result,
|
|
456
|
+
txContext,
|
|
457
|
+
);
|
|
369
458
|
return { result, expiredActions, sentActions: validActions, successfulActions, failedActions };
|
|
370
459
|
} catch (err) {
|
|
371
460
|
const viemError = formatViemError(err);
|
|
@@ -385,11 +474,25 @@ export class SequencerPublisher {
|
|
|
385
474
|
|
|
386
475
|
private callbackBundledTransactions(
|
|
387
476
|
requests: RequestWithExpiry[],
|
|
388
|
-
result
|
|
477
|
+
result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError | undefined,
|
|
478
|
+
txContext: { multicallData: Hex; blobData?: Hex[]; l1BlockNumber: bigint },
|
|
389
479
|
) {
|
|
390
480
|
const actionsListStr = requests.map(r => r.action).join(', ');
|
|
391
481
|
if (result instanceof FormattedViemError) {
|
|
392
482
|
this.log.error(`Failed to publish bundled transactions (${actionsListStr})`, result);
|
|
483
|
+
this.backupFailedTx({
|
|
484
|
+
id: keccak256(txContext.multicallData),
|
|
485
|
+
failureType: 'send-error',
|
|
486
|
+
request: { to: MULTI_CALL_3_ADDRESS, data: txContext.multicallData },
|
|
487
|
+
blobData: txContext.blobData,
|
|
488
|
+
l1BlockNumber: txContext.l1BlockNumber.toString(),
|
|
489
|
+
error: { message: result.message, name: result.name },
|
|
490
|
+
context: {
|
|
491
|
+
actions: requests.map(r => r.action),
|
|
492
|
+
requests: requests.map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
|
|
493
|
+
sender: this.getSenderAddress().toString(),
|
|
494
|
+
},
|
|
495
|
+
});
|
|
393
496
|
return { failedActions: requests.map(r => r.action) };
|
|
394
497
|
} else {
|
|
395
498
|
this.log.verbose(`Published bundled transactions (${actionsListStr})`, { result, requests });
|
|
@@ -402,6 +505,30 @@ export class SequencerPublisher {
|
|
|
402
505
|
failedActions.push(request.action);
|
|
403
506
|
}
|
|
404
507
|
}
|
|
508
|
+
// Single backup for the whole reverted tx
|
|
509
|
+
if (failedActions.length > 0 && result?.receipt?.status === 'reverted') {
|
|
510
|
+
this.backupFailedTx({
|
|
511
|
+
id: result.receipt.transactionHash,
|
|
512
|
+
failureType: 'revert',
|
|
513
|
+
request: { to: MULTI_CALL_3_ADDRESS, data: txContext.multicallData },
|
|
514
|
+
blobData: txContext.blobData,
|
|
515
|
+
l1BlockNumber: result.receipt.blockNumber.toString(),
|
|
516
|
+
receipt: {
|
|
517
|
+
transactionHash: result.receipt.transactionHash,
|
|
518
|
+
blockNumber: result.receipt.blockNumber.toString(),
|
|
519
|
+
gasUsed: (result.receipt.gasUsed ?? 0n).toString(),
|
|
520
|
+
status: 'reverted',
|
|
521
|
+
},
|
|
522
|
+
error: { message: result.errorMsg ?? 'Transaction reverted' },
|
|
523
|
+
context: {
|
|
524
|
+
actions: failedActions,
|
|
525
|
+
requests: requests
|
|
526
|
+
.filter(r => failedActions.includes(r.action))
|
|
527
|
+
.map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
|
|
528
|
+
sender: this.getSenderAddress().toString(),
|
|
529
|
+
},
|
|
530
|
+
});
|
|
531
|
+
}
|
|
405
532
|
return { successfulActions, failedActions };
|
|
406
533
|
}
|
|
407
534
|
}
|
|
@@ -414,17 +541,14 @@ export class SequencerPublisher {
|
|
|
414
541
|
public canProposeAtNextEthBlock(
|
|
415
542
|
tipArchive: Fr,
|
|
416
543
|
msgSender: EthAddress,
|
|
417
|
-
opts: {
|
|
544
|
+
opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
418
545
|
) {
|
|
419
546
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
420
547
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
421
548
|
|
|
422
549
|
return this.rollupContract
|
|
423
550
|
.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
|
|
424
|
-
forcePendingCheckpointNumber:
|
|
425
|
-
opts.forcePendingBlockNumber !== undefined
|
|
426
|
-
? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber)
|
|
427
|
-
: undefined,
|
|
551
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
428
552
|
})
|
|
429
553
|
.catch(err => {
|
|
430
554
|
if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
|
|
@@ -443,10 +567,11 @@ export class SequencerPublisher {
|
|
|
443
567
|
* It will throw if the block header is invalid.
|
|
444
568
|
* @param header - The block header to validate
|
|
445
569
|
*/
|
|
570
|
+
@trackSpan('SequencerPublisher.validateBlockHeader')
|
|
446
571
|
public async validateBlockHeader(
|
|
447
572
|
header: CheckpointHeader,
|
|
448
|
-
opts?: {
|
|
449
|
-
) {
|
|
573
|
+
opts?: { forcePendingCheckpointNumber: CheckpointNumber | undefined },
|
|
574
|
+
): Promise<void> {
|
|
450
575
|
const flags = { ignoreDA: true, ignoreSignatures: true };
|
|
451
576
|
|
|
452
577
|
const args = [
|
|
@@ -455,17 +580,13 @@ export class SequencerPublisher {
|
|
|
455
580
|
[], // no signers
|
|
456
581
|
Signature.empty().toViemSignature(),
|
|
457
582
|
`0x${'0'.repeat(64)}`, // 32 empty bytes
|
|
458
|
-
header.
|
|
583
|
+
header.blobsHash.toString(),
|
|
459
584
|
flags,
|
|
460
585
|
] as const;
|
|
461
586
|
|
|
462
587
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
463
|
-
const optsForcePendingCheckpointNumber =
|
|
464
|
-
opts?.forcePendingBlockNumber !== undefined
|
|
465
|
-
? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber)
|
|
466
|
-
: undefined;
|
|
467
588
|
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
468
|
-
|
|
589
|
+
opts?.forcePendingCheckpointNumber,
|
|
469
590
|
);
|
|
470
591
|
let balance = 0n;
|
|
471
592
|
if (this.config.fishermanMode) {
|
|
@@ -493,77 +614,109 @@ export class SequencerPublisher {
|
|
|
493
614
|
}
|
|
494
615
|
|
|
495
616
|
/**
|
|
496
|
-
* Simulate making a call to invalidate a
|
|
497
|
-
* @param
|
|
617
|
+
* Simulate making a call to invalidate a checkpoint with invalid attestations. Returns undefined if no need to invalidate.
|
|
618
|
+
* @param validationResult - The validation result indicating which checkpoint to invalidate (as returned by the archiver)
|
|
498
619
|
*/
|
|
499
|
-
public async
|
|
500
|
-
validationResult:
|
|
501
|
-
): Promise<
|
|
620
|
+
public async simulateInvalidateCheckpoint(
|
|
621
|
+
validationResult: ValidateCheckpointResult,
|
|
622
|
+
): Promise<InvalidateCheckpointRequest | undefined> {
|
|
502
623
|
if (validationResult.valid) {
|
|
503
624
|
return undefined;
|
|
504
625
|
}
|
|
505
626
|
|
|
506
|
-
const { reason,
|
|
507
|
-
const
|
|
508
|
-
const logData = { ...
|
|
627
|
+
const { reason, checkpoint } = validationResult;
|
|
628
|
+
const checkpointNumber = checkpoint.checkpointNumber;
|
|
629
|
+
const logData = { ...checkpoint, reason };
|
|
509
630
|
|
|
510
|
-
const
|
|
511
|
-
if (
|
|
631
|
+
const currentCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
632
|
+
if (currentCheckpointNumber < checkpointNumber) {
|
|
512
633
|
this.log.verbose(
|
|
513
|
-
`Skipping
|
|
514
|
-
{
|
|
634
|
+
`Skipping checkpoint ${checkpointNumber} invalidation since it has already been removed from the pending chain`,
|
|
635
|
+
{ currentCheckpointNumber, ...logData },
|
|
515
636
|
);
|
|
516
637
|
return undefined;
|
|
517
638
|
}
|
|
518
639
|
|
|
519
|
-
const request = this.
|
|
520
|
-
this.log.debug(`Simulating invalidate
|
|
640
|
+
const request = this.buildInvalidateCheckpointRequest(validationResult);
|
|
641
|
+
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
|
|
642
|
+
|
|
643
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
521
644
|
|
|
522
645
|
try {
|
|
523
|
-
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
524
|
-
|
|
646
|
+
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
647
|
+
request,
|
|
648
|
+
undefined,
|
|
649
|
+
undefined,
|
|
650
|
+
mergeAbis([request.abi ?? [], ErrorsAbi]),
|
|
651
|
+
);
|
|
652
|
+
this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} succeeded`, {
|
|
653
|
+
...logData,
|
|
654
|
+
request,
|
|
655
|
+
gasUsed,
|
|
656
|
+
});
|
|
525
657
|
|
|
526
|
-
return {
|
|
658
|
+
return {
|
|
659
|
+
request,
|
|
660
|
+
gasUsed,
|
|
661
|
+
checkpointNumber,
|
|
662
|
+
forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
|
|
663
|
+
reason,
|
|
664
|
+
};
|
|
527
665
|
} catch (err) {
|
|
528
666
|
const viemError = formatViemError(err);
|
|
529
667
|
|
|
530
|
-
// If the error is due to the
|
|
531
|
-
// we can safely ignore it and return undefined so we go ahead with
|
|
532
|
-
if (viemError.message?.includes('
|
|
668
|
+
// If the error is due to the checkpoint not being in the pending chain, and it was indeed removed by someone else,
|
|
669
|
+
// we can safely ignore it and return undefined so we go ahead with checkpoint building.
|
|
670
|
+
if (viemError.message?.includes('Rollup__CheckpointNotInPendingChain')) {
|
|
533
671
|
this.log.verbose(
|
|
534
|
-
`Simulation for invalidate
|
|
672
|
+
`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
|
|
535
673
|
{ ...logData, request, error: viemError.message },
|
|
536
674
|
);
|
|
537
|
-
const
|
|
538
|
-
if (
|
|
539
|
-
this.log.verbose(`
|
|
675
|
+
const latestPendingCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
676
|
+
if (latestPendingCheckpointNumber < checkpointNumber) {
|
|
677
|
+
this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, { ...logData });
|
|
540
678
|
return undefined;
|
|
541
679
|
} else {
|
|
542
680
|
this.log.error(
|
|
543
|
-
`Simulation for invalidate ${
|
|
681
|
+
`Simulation for invalidate checkpoint ${checkpointNumber} failed and it is still in pending chain`,
|
|
544
682
|
viemError,
|
|
545
683
|
logData,
|
|
546
684
|
);
|
|
547
|
-
throw new Error(
|
|
548
|
-
|
|
549
|
-
|
|
685
|
+
throw new Error(
|
|
686
|
+
`Failed to simulate invalidate checkpoint ${checkpointNumber} while it is still in pending chain`,
|
|
687
|
+
{
|
|
688
|
+
cause: viemError,
|
|
689
|
+
},
|
|
690
|
+
);
|
|
550
691
|
}
|
|
551
692
|
}
|
|
552
693
|
|
|
553
|
-
// Otherwise, throw. We cannot build the next
|
|
554
|
-
this.log.error(`Simulation for invalidate
|
|
555
|
-
|
|
694
|
+
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
695
|
+
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
|
|
696
|
+
this.backupFailedTx({
|
|
697
|
+
id: keccak256(request.data!),
|
|
698
|
+
failureType: 'simulation',
|
|
699
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
700
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
701
|
+
error: { message: viemError.message, name: viemError.name },
|
|
702
|
+
context: {
|
|
703
|
+
actions: [`invalidate-${reason}`],
|
|
704
|
+
checkpointNumber,
|
|
705
|
+
sender: this.getSenderAddress().toString(),
|
|
706
|
+
},
|
|
707
|
+
});
|
|
708
|
+
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
|
|
556
709
|
}
|
|
557
710
|
}
|
|
558
711
|
|
|
559
|
-
private
|
|
712
|
+
private buildInvalidateCheckpointRequest(validationResult: ValidateCheckpointResult) {
|
|
560
713
|
if (validationResult.valid) {
|
|
561
|
-
throw new Error('Cannot invalidate a valid
|
|
714
|
+
throw new Error('Cannot invalidate a valid checkpoint');
|
|
562
715
|
}
|
|
563
716
|
|
|
564
|
-
const {
|
|
565
|
-
const logData = { ...
|
|
566
|
-
this.log.debug(`
|
|
717
|
+
const { checkpoint, committee, reason } = validationResult;
|
|
718
|
+
const logData = { ...checkpoint, reason };
|
|
719
|
+
this.log.debug(`Building invalidate checkpoint ${checkpoint.checkpointNumber} request`, logData);
|
|
567
720
|
|
|
568
721
|
const attestationsAndSigners = new CommitteeAttestationsAndSigners(
|
|
569
722
|
validationResult.attestations,
|
|
@@ -571,14 +724,14 @@ export class SequencerPublisher {
|
|
|
571
724
|
|
|
572
725
|
if (reason === 'invalid-attestation') {
|
|
573
726
|
return this.rollupContract.buildInvalidateBadAttestationRequest(
|
|
574
|
-
|
|
727
|
+
checkpoint.checkpointNumber,
|
|
575
728
|
attestationsAndSigners,
|
|
576
729
|
committee,
|
|
577
730
|
validationResult.invalidIndex,
|
|
578
731
|
);
|
|
579
732
|
} else if (reason === 'insufficient-attestations') {
|
|
580
733
|
return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
|
|
581
|
-
|
|
734
|
+
checkpoint.checkpointNumber,
|
|
582
735
|
attestationsAndSigners,
|
|
583
736
|
committee,
|
|
584
737
|
);
|
|
@@ -589,31 +742,16 @@ export class SequencerPublisher {
|
|
|
589
742
|
}
|
|
590
743
|
|
|
591
744
|
/** Simulates `propose` to make sure that the checkpoint is valid for submission */
|
|
745
|
+
@trackSpan('SequencerPublisher.validateCheckpointForSubmission')
|
|
592
746
|
public async validateCheckpointForSubmission(
|
|
593
747
|
checkpoint: Checkpoint,
|
|
594
748
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
595
749
|
attestationsAndSignersSignature: Signature,
|
|
596
|
-
options: {
|
|
750
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
597
751
|
): Promise<bigint> {
|
|
598
752
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
599
|
-
|
|
600
|
-
// TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
|
|
601
|
-
// If we have no attestations, we still need to provide the empty attestations
|
|
602
|
-
// so that the committee is recalculated correctly
|
|
603
|
-
// const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
604
|
-
// if (ignoreSignatures) {
|
|
605
|
-
// const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
|
|
606
|
-
// if (!committee) {
|
|
607
|
-
// this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
608
|
-
// throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
609
|
-
// }
|
|
610
|
-
// attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
611
|
-
// CommitteeAttestation.fromAddress(committeeMember),
|
|
612
|
-
// );
|
|
613
|
-
// }
|
|
614
|
-
|
|
615
753
|
const blobFields = checkpoint.toBlobFields();
|
|
616
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
754
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
617
755
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
618
756
|
|
|
619
757
|
const args = [
|
|
@@ -621,7 +759,7 @@ export class SequencerPublisher {
|
|
|
621
759
|
header: checkpoint.header.toViem(),
|
|
622
760
|
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
623
761
|
oracleInput: {
|
|
624
|
-
feeAssetPriceModifier:
|
|
762
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
625
763
|
},
|
|
626
764
|
},
|
|
627
765
|
attestationsAndSigners.getPackedAttestations(),
|
|
@@ -670,6 +808,32 @@ export class SequencerPublisher {
|
|
|
670
808
|
return false;
|
|
671
809
|
}
|
|
672
810
|
|
|
811
|
+
// Check if payload was already submitted to governance
|
|
812
|
+
const cacheKey = payload.toString();
|
|
813
|
+
if (!this.payloadProposedCache.has(cacheKey)) {
|
|
814
|
+
try {
|
|
815
|
+
const l1StartBlock = await this.rollupContract.getL1StartBlock();
|
|
816
|
+
const proposed = await retry(
|
|
817
|
+
() => base.hasPayloadBeenProposed(payload.toString(), l1StartBlock),
|
|
818
|
+
'Check if payload was proposed',
|
|
819
|
+
makeBackoff([0, 1, 2]),
|
|
820
|
+
this.log,
|
|
821
|
+
true,
|
|
822
|
+
);
|
|
823
|
+
if (proposed) {
|
|
824
|
+
this.payloadProposedCache.add(cacheKey);
|
|
825
|
+
}
|
|
826
|
+
} catch (err) {
|
|
827
|
+
this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
|
|
828
|
+
return false;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
if (this.payloadProposedCache.has(cacheKey)) {
|
|
833
|
+
this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
|
|
834
|
+
return false;
|
|
835
|
+
}
|
|
836
|
+
|
|
673
837
|
const cachedLastVote = this.lastActions[signalType];
|
|
674
838
|
this.lastActions[signalType] = slotNumber;
|
|
675
839
|
const action = signalType;
|
|
@@ -688,11 +852,26 @@ export class SequencerPublisher {
|
|
|
688
852
|
lastValidL2Slot: slotNumber,
|
|
689
853
|
});
|
|
690
854
|
|
|
855
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
856
|
+
|
|
691
857
|
try {
|
|
692
|
-
await this.l1TxUtils.simulate(request, { time: timestamp }, [], ErrorsAbi);
|
|
858
|
+
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
693
859
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
694
860
|
} catch (err) {
|
|
695
|
-
|
|
861
|
+
const viemError = formatViemError(err);
|
|
862
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError);
|
|
863
|
+
this.backupFailedTx({
|
|
864
|
+
id: keccak256(request.data!),
|
|
865
|
+
failureType: 'simulation',
|
|
866
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
867
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
868
|
+
error: { message: viemError.message, name: viemError.name },
|
|
869
|
+
context: {
|
|
870
|
+
actions: [action],
|
|
871
|
+
slot: slotNumber,
|
|
872
|
+
sender: this.getSenderAddress().toString(),
|
|
873
|
+
},
|
|
874
|
+
});
|
|
696
875
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
697
876
|
}
|
|
698
877
|
|
|
@@ -892,19 +1071,20 @@ export class SequencerPublisher {
|
|
|
892
1071
|
checkpoint: Checkpoint,
|
|
893
1072
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
894
1073
|
attestationsAndSignersSignature: Signature,
|
|
895
|
-
opts: { txTimeoutAt?: Date;
|
|
1074
|
+
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
896
1075
|
): Promise<void> {
|
|
897
1076
|
const checkpointHeader = checkpoint.header;
|
|
898
1077
|
|
|
899
1078
|
const blobFields = checkpoint.toBlobFields();
|
|
900
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
1079
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
901
1080
|
|
|
902
|
-
const proposeTxArgs = {
|
|
1081
|
+
const proposeTxArgs: L1ProcessArgs = {
|
|
903
1082
|
header: checkpointHeader,
|
|
904
1083
|
archive: checkpoint.archive.root.toBuffer(),
|
|
905
1084
|
blobs,
|
|
906
1085
|
attestationsAndSigners,
|
|
907
1086
|
attestationsAndSignersSignature,
|
|
1087
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
908
1088
|
};
|
|
909
1089
|
|
|
910
1090
|
let ts: bigint;
|
|
@@ -925,7 +1105,7 @@ export class SequencerPublisher {
|
|
|
925
1105
|
this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
926
1106
|
...checkpoint.getStats(),
|
|
927
1107
|
slotNumber: checkpoint.header.slotNumber,
|
|
928
|
-
|
|
1108
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
929
1109
|
});
|
|
930
1110
|
throw err;
|
|
931
1111
|
}
|
|
@@ -934,7 +1114,10 @@ export class SequencerPublisher {
|
|
|
934
1114
|
await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
|
|
935
1115
|
}
|
|
936
1116
|
|
|
937
|
-
public
|
|
1117
|
+
public enqueueInvalidateCheckpoint(
|
|
1118
|
+
request: InvalidateCheckpointRequest | undefined,
|
|
1119
|
+
opts: { txTimeoutAt?: Date } = {},
|
|
1120
|
+
) {
|
|
938
1121
|
if (!request) {
|
|
939
1122
|
return;
|
|
940
1123
|
}
|
|
@@ -942,9 +1125,9 @@ export class SequencerPublisher {
|
|
|
942
1125
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
943
1126
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(request.gasUsed) * 64) / 63)));
|
|
944
1127
|
|
|
945
|
-
const { gasUsed,
|
|
946
|
-
const logData = { gasUsed,
|
|
947
|
-
this.log.verbose(`Enqueuing invalidate
|
|
1128
|
+
const { gasUsed, checkpointNumber } = request;
|
|
1129
|
+
const logData = { gasUsed, checkpointNumber, gasLimit, opts };
|
|
1130
|
+
this.log.verbose(`Enqueuing invalidate checkpoint request`, logData);
|
|
948
1131
|
this.addRequest({
|
|
949
1132
|
action: `invalidate-by-${request.reason}`,
|
|
950
1133
|
request: request.request,
|
|
@@ -957,9 +1140,9 @@ export class SequencerPublisher {
|
|
|
957
1140
|
result.receipt.status === 'success' &&
|
|
958
1141
|
tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointInvalidated');
|
|
959
1142
|
if (!success) {
|
|
960
|
-
this.log.warn(`Invalidate
|
|
1143
|
+
this.log.warn(`Invalidate checkpoint ${request.checkpointNumber} failed`, { ...result, ...logData });
|
|
961
1144
|
} else {
|
|
962
|
-
this.log.info(`Invalidate
|
|
1145
|
+
this.log.info(`Invalidate checkpoint ${request.checkpointNumber} succeeded`, { ...result, ...logData });
|
|
963
1146
|
}
|
|
964
1147
|
return !!success;
|
|
965
1148
|
},
|
|
@@ -984,13 +1167,30 @@ export class SequencerPublisher {
|
|
|
984
1167
|
|
|
985
1168
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
986
1169
|
|
|
1170
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1171
|
+
|
|
987
1172
|
let gasUsed: bigint;
|
|
1173
|
+
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
988
1174
|
try {
|
|
989
|
-
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [],
|
|
1175
|
+
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi)); // TODO(palla/slash): Check the timestamp logic
|
|
990
1176
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
991
1177
|
} catch (err) {
|
|
992
|
-
const viemError = formatViemError(err);
|
|
1178
|
+
const viemError = formatViemError(err, simulateAbi);
|
|
993
1179
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1180
|
+
|
|
1181
|
+
this.backupFailedTx({
|
|
1182
|
+
id: keccak256(request.data!),
|
|
1183
|
+
failureType: 'simulation',
|
|
1184
|
+
request: { to: request.to!, data: request.data!, value: request.value?.toString() },
|
|
1185
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1186
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1187
|
+
context: {
|
|
1188
|
+
actions: [action],
|
|
1189
|
+
slot: slotNumber,
|
|
1190
|
+
sender: this.getSenderAddress().toString(),
|
|
1191
|
+
},
|
|
1192
|
+
});
|
|
1193
|
+
|
|
994
1194
|
return false;
|
|
995
1195
|
}
|
|
996
1196
|
|
|
@@ -998,10 +1198,14 @@ export class SequencerPublisher {
|
|
|
998
1198
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(gasUsed) * 64) / 63)));
|
|
999
1199
|
logData.gasLimit = gasLimit;
|
|
1000
1200
|
|
|
1201
|
+
// Store the ABI used for simulation on the request so Multicall3.forward can decode errors
|
|
1202
|
+
// when the tx is sent and a revert is diagnosed via simulation.
|
|
1203
|
+
const requestWithAbi = { ...request, abi: simulateAbi };
|
|
1204
|
+
|
|
1001
1205
|
this.log.debug(`Enqueuing ${action}`, logData);
|
|
1002
1206
|
this.addRequest({
|
|
1003
1207
|
action,
|
|
1004
|
-
request,
|
|
1208
|
+
request: requestWithAbi,
|
|
1005
1209
|
gasConfig: { gasLimit },
|
|
1006
1210
|
lastValidL2Slot: slotNumber,
|
|
1007
1211
|
checkSuccess: (_req, result) => {
|
|
@@ -1038,7 +1242,7 @@ export class SequencerPublisher {
|
|
|
1038
1242
|
private async prepareProposeTx(
|
|
1039
1243
|
encodedData: L1ProcessArgs,
|
|
1040
1244
|
timestamp: bigint,
|
|
1041
|
-
options: {
|
|
1245
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1042
1246
|
) {
|
|
1043
1247
|
const kzg = Blob.getViemKzgInstance();
|
|
1044
1248
|
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
@@ -1070,9 +1274,27 @@ export class SequencerPublisher {
|
|
|
1070
1274
|
kzg,
|
|
1071
1275
|
},
|
|
1072
1276
|
)
|
|
1073
|
-
.catch(err => {
|
|
1074
|
-
const
|
|
1075
|
-
this.log.error(`Failed to validate blobs`, message, { metaMessages });
|
|
1277
|
+
.catch(async err => {
|
|
1278
|
+
const viemError = formatViemError(err);
|
|
1279
|
+
this.log.error(`Failed to validate blobs`, viemError.message, { metaMessages: viemError.metaMessages });
|
|
1280
|
+
const validateBlobsData = encodeFunctionData({
|
|
1281
|
+
abi: RollupAbi,
|
|
1282
|
+
functionName: 'validateBlobs',
|
|
1283
|
+
args: [blobInput],
|
|
1284
|
+
});
|
|
1285
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1286
|
+
this.backupFailedTx({
|
|
1287
|
+
id: keccak256(validateBlobsData),
|
|
1288
|
+
failureType: 'simulation',
|
|
1289
|
+
request: { to: this.rollupContract.address as Hex, data: validateBlobsData },
|
|
1290
|
+
blobData: encodedData.blobs.map(b => toHex(b.data)) as Hex[],
|
|
1291
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1292
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1293
|
+
context: {
|
|
1294
|
+
actions: ['validate-blobs'],
|
|
1295
|
+
sender: this.getSenderAddress().toString(),
|
|
1296
|
+
},
|
|
1297
|
+
});
|
|
1076
1298
|
throw new Error('Failed to validate blobs');
|
|
1077
1299
|
});
|
|
1078
1300
|
}
|
|
@@ -1083,8 +1305,7 @@ export class SequencerPublisher {
|
|
|
1083
1305
|
header: encodedData.header.toViem(),
|
|
1084
1306
|
archive: toHex(encodedData.archive),
|
|
1085
1307
|
oracleInput: {
|
|
1086
|
-
|
|
1087
|
-
feeAssetPriceModifier: 0n,
|
|
1308
|
+
feeAssetPriceModifier: encodedData.feeAssetPriceModifier,
|
|
1088
1309
|
},
|
|
1089
1310
|
},
|
|
1090
1311
|
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
@@ -1110,7 +1331,7 @@ export class SequencerPublisher {
|
|
|
1110
1331
|
readonly header: ViemHeader;
|
|
1111
1332
|
readonly archive: `0x${string}`;
|
|
1112
1333
|
readonly oracleInput: {
|
|
1113
|
-
readonly feeAssetPriceModifier:
|
|
1334
|
+
readonly feeAssetPriceModifier: bigint;
|
|
1114
1335
|
};
|
|
1115
1336
|
},
|
|
1116
1337
|
ViemCommitteeAttestations,
|
|
@@ -1119,7 +1340,7 @@ export class SequencerPublisher {
|
|
|
1119
1340
|
`0x${string}`,
|
|
1120
1341
|
],
|
|
1121
1342
|
timestamp: bigint,
|
|
1122
|
-
options: {
|
|
1343
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1123
1344
|
) {
|
|
1124
1345
|
const rollupData = encodeFunctionData({
|
|
1125
1346
|
abi: RollupAbi,
|
|
@@ -1128,13 +1349,9 @@ export class SequencerPublisher {
|
|
|
1128
1349
|
});
|
|
1129
1350
|
|
|
1130
1351
|
// override the pending checkpoint number if requested
|
|
1131
|
-
const optsForcePendingCheckpointNumber =
|
|
1132
|
-
options.forcePendingBlockNumber !== undefined
|
|
1133
|
-
? CheckpointNumber.fromBlockNumber(options.forcePendingBlockNumber)
|
|
1134
|
-
: undefined;
|
|
1135
1352
|
const forcePendingCheckpointNumberStateDiff = (
|
|
1136
|
-
|
|
1137
|
-
? await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
1353
|
+
options.forcePendingCheckpointNumber !== undefined
|
|
1354
|
+
? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber)
|
|
1138
1355
|
: []
|
|
1139
1356
|
).flatMap(override => override.stateDiff ?? []);
|
|
1140
1357
|
|
|
@@ -1156,25 +1373,27 @@ export class SequencerPublisher {
|
|
|
1156
1373
|
});
|
|
1157
1374
|
}
|
|
1158
1375
|
|
|
1376
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1377
|
+
|
|
1159
1378
|
const simulationResult = await this.l1TxUtils
|
|
1160
1379
|
.simulate(
|
|
1161
1380
|
{
|
|
1162
1381
|
to: this.rollupContract.address,
|
|
1163
1382
|
data: rollupData,
|
|
1164
|
-
gas:
|
|
1383
|
+
gas: MAX_L1_TX_LIMIT,
|
|
1165
1384
|
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1166
1385
|
},
|
|
1167
1386
|
{
|
|
1168
1387
|
// @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
|
|
1169
1388
|
time: timestamp + 1n,
|
|
1170
1389
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1171
|
-
gasLimit:
|
|
1390
|
+
gasLimit: MAX_L1_TX_LIMIT * 2n,
|
|
1172
1391
|
},
|
|
1173
1392
|
stateOverrides,
|
|
1174
1393
|
RollupAbi,
|
|
1175
1394
|
{
|
|
1176
1395
|
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
1177
|
-
fallbackGasEstimate:
|
|
1396
|
+
fallbackGasEstimate: MAX_L1_TX_LIMIT,
|
|
1178
1397
|
},
|
|
1179
1398
|
)
|
|
1180
1399
|
.catch(err => {
|
|
@@ -1184,11 +1403,23 @@ export class SequencerPublisher {
|
|
|
1184
1403
|
this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
|
|
1185
1404
|
// Return a minimal simulation result with the fallback gas estimate
|
|
1186
1405
|
return {
|
|
1187
|
-
gasUsed:
|
|
1406
|
+
gasUsed: MAX_L1_TX_LIMIT,
|
|
1188
1407
|
logs: [],
|
|
1189
1408
|
};
|
|
1190
1409
|
}
|
|
1191
1410
|
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1411
|
+
this.backupFailedTx({
|
|
1412
|
+
id: keccak256(rollupData),
|
|
1413
|
+
failureType: 'simulation',
|
|
1414
|
+
request: { to: this.rollupContract.address, data: rollupData },
|
|
1415
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1416
|
+
error: { message: viemError.message, name: viemError.name },
|
|
1417
|
+
context: {
|
|
1418
|
+
actions: ['propose'],
|
|
1419
|
+
slot: Number(args[0].header.slotNumber),
|
|
1420
|
+
sender: this.getSenderAddress().toString(),
|
|
1421
|
+
},
|
|
1422
|
+
});
|
|
1192
1423
|
throw err;
|
|
1193
1424
|
});
|
|
1194
1425
|
|
|
@@ -1198,7 +1429,7 @@ export class SequencerPublisher {
|
|
|
1198
1429
|
private async addProposeTx(
|
|
1199
1430
|
checkpoint: Checkpoint,
|
|
1200
1431
|
encodedData: L1ProcessArgs,
|
|
1201
|
-
opts: { txTimeoutAt?: Date;
|
|
1432
|
+
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
1202
1433
|
timestamp: bigint,
|
|
1203
1434
|
): Promise<void> {
|
|
1204
1435
|
const slot = checkpoint.header.slotNumber;
|