@aztec/sequencer-client 0.0.1-commit.54489865 → 0.0.1-commit.5914bae
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 +16 -7
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +56 -23
- package/dest/config.d.ts +25 -6
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +46 -28
- package/dest/global_variable_builder/global_builder.d.ts +14 -10
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +22 -21
- package/dest/global_variable_builder/index.d.ts +2 -2
- package/dest/global_variable_builder/index.d.ts.map +1 -1
- package/dest/publisher/config.d.ts +31 -17
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +101 -42
- 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 +13 -2
- package/dest/publisher/sequencer-publisher.d.ts +29 -14
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +144 -74
- package/dest/sequencer/checkpoint_proposal_job.d.ts +30 -7
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +191 -113
- package/dest/sequencer/checkpoint_voter.d.ts +1 -2
- package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_voter.js +2 -5
- 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 +29 -13
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +47 -43
- 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 +12 -12
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +47 -36
- 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 +78 -21
- package/src/config.ts +61 -38
- package/src/global_variable_builder/global_builder.ts +23 -24
- package/src/global_variable_builder/index.ts +1 -1
- package/src/publisher/config.ts +112 -43
- package/src/publisher/sequencer-publisher-factory.ts +23 -6
- package/src/publisher/sequencer-publisher.ts +155 -87
- package/src/sequencer/checkpoint_proposal_job.ts +285 -159
- package/src/sequencer/checkpoint_voter.ts +1 -12
- package/src/sequencer/metrics.ts +92 -18
- package/src/sequencer/sequencer.ts +60 -49
- 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 +67 -53
- 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,25 +19,29 @@ 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
30
|
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
31
|
+
import { trimmedBytesLength } from '@aztec/foundation/buffer';
|
|
29
32
|
import { pick } from '@aztec/foundation/collection';
|
|
30
33
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
31
34
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
32
35
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
33
36
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
37
|
+
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
34
38
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
35
39
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
36
40
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
37
41
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
38
42
|
import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
39
43
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
44
|
+
import { getLastL1SlotTimestampForL2Slot, getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
40
45
|
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
41
46
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
42
47
|
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
@@ -44,7 +49,7 @@ import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from
|
|
|
44
49
|
|
|
45
50
|
import { type StateOverride, type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
|
|
46
51
|
|
|
47
|
-
import type {
|
|
52
|
+
import type { SequencerPublisherConfig } from './config.js';
|
|
48
53
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
49
54
|
|
|
50
55
|
/** Arguments to the process method of the rollup contract */
|
|
@@ -59,6 +64,8 @@ type L1ProcessArgs = {
|
|
|
59
64
|
attestationsAndSigners: CommitteeAttestationsAndSigners;
|
|
60
65
|
/** Attestations and signers signature */
|
|
61
66
|
attestationsAndSignersSignature: Signature;
|
|
67
|
+
/** The fee asset price modifier in basis points (from oracle) */
|
|
68
|
+
feeAssetPriceModifier: bigint;
|
|
62
69
|
};
|
|
63
70
|
|
|
64
71
|
export const Actions = [
|
|
@@ -111,9 +118,12 @@ export class SequencerPublisher {
|
|
|
111
118
|
protected lastActions: Partial<Record<Action, SlotNumber>> = {};
|
|
112
119
|
|
|
113
120
|
private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
|
|
121
|
+
private payloadProposedCache: Set<string> = new Set<string>();
|
|
114
122
|
|
|
115
123
|
protected log: Logger;
|
|
116
124
|
protected ethereumSlotDuration: bigint;
|
|
125
|
+
protected aztecSlotDuration: bigint;
|
|
126
|
+
private dateProvider: DateProvider;
|
|
117
127
|
|
|
118
128
|
private blobClient: BlobClientInterface;
|
|
119
129
|
|
|
@@ -122,10 +132,9 @@ export class SequencerPublisher {
|
|
|
122
132
|
|
|
123
133
|
/** L1 fee analyzer for fisherman mode */
|
|
124
134
|
private l1FeeAnalyzer?: L1FeeAnalyzer;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
public static PROPOSE_GAS_GUESS: bigint = 12_000_000n;
|
|
135
|
+
|
|
136
|
+
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */
|
|
137
|
+
private feeAssetPriceOracle: FeeAssetPriceOracle;
|
|
129
138
|
|
|
130
139
|
// A CALL to a cold address is 2700 gas
|
|
131
140
|
public static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
@@ -133,7 +142,7 @@ export class SequencerPublisher {
|
|
|
133
142
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
134
143
|
public static VOTE_GAS_GUESS: bigint = 800_000n;
|
|
135
144
|
|
|
136
|
-
public l1TxUtils:
|
|
145
|
+
public l1TxUtils: L1TxUtils;
|
|
137
146
|
public rollupContract: RollupContract;
|
|
138
147
|
public govProposerContract: GovernanceProposerContract;
|
|
139
148
|
public slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
@@ -144,11 +153,12 @@ export class SequencerPublisher {
|
|
|
144
153
|
protected requests: RequestWithExpiry[] = [];
|
|
145
154
|
|
|
146
155
|
constructor(
|
|
147
|
-
private config:
|
|
156
|
+
private config: Pick<SequencerPublisherConfig, 'fishermanMode'> &
|
|
157
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration'> & { l1ChainId: number },
|
|
148
158
|
deps: {
|
|
149
159
|
telemetry?: TelemetryClient;
|
|
150
160
|
blobClient: BlobClientInterface;
|
|
151
|
-
l1TxUtils:
|
|
161
|
+
l1TxUtils: L1TxUtils;
|
|
152
162
|
rollupContract: RollupContract;
|
|
153
163
|
slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
154
164
|
governanceProposerContract: GovernanceProposerContract;
|
|
@@ -162,6 +172,8 @@ export class SequencerPublisher {
|
|
|
162
172
|
) {
|
|
163
173
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
164
174
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
175
|
+
this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
|
|
176
|
+
this.dateProvider = deps.dateProvider;
|
|
165
177
|
this.epochCache = deps.epochCache;
|
|
166
178
|
this.lastActions = deps.lastActions;
|
|
167
179
|
|
|
@@ -192,12 +204,27 @@ export class SequencerPublisher {
|
|
|
192
204
|
createLogger('sequencer:publisher:fee-analyzer'),
|
|
193
205
|
);
|
|
194
206
|
}
|
|
207
|
+
|
|
208
|
+
// Initialize fee asset price oracle
|
|
209
|
+
this.feeAssetPriceOracle = new FeeAssetPriceOracle(
|
|
210
|
+
this.l1TxUtils.client,
|
|
211
|
+
this.rollupContract,
|
|
212
|
+
createLogger('sequencer:publisher:price-oracle'),
|
|
213
|
+
);
|
|
195
214
|
}
|
|
196
215
|
|
|
197
216
|
public getRollupContract(): RollupContract {
|
|
198
217
|
return this.rollupContract;
|
|
199
218
|
}
|
|
200
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Gets the fee asset price modifier from the oracle.
|
|
222
|
+
* Returns 0n if the oracle query fails.
|
|
223
|
+
*/
|
|
224
|
+
public getFeeAssetPriceModifier(): Promise<bigint> {
|
|
225
|
+
return this.feeAssetPriceOracle.computePriceModifier();
|
|
226
|
+
}
|
|
227
|
+
|
|
201
228
|
public getSenderAddress() {
|
|
202
229
|
return this.l1TxUtils.getSenderAddress();
|
|
203
230
|
}
|
|
@@ -273,7 +300,7 @@ export class SequencerPublisher {
|
|
|
273
300
|
// Start the analysis
|
|
274
301
|
const analysisId = await this.l1FeeAnalyzer.startAnalysis(
|
|
275
302
|
l2SlotNumber,
|
|
276
|
-
gasLimit > 0n ? gasLimit :
|
|
303
|
+
gasLimit > 0n ? gasLimit : MAX_L1_TX_LIMIT,
|
|
277
304
|
l1Requests,
|
|
278
305
|
blobConfig,
|
|
279
306
|
onComplete,
|
|
@@ -335,8 +362,8 @@ export class SequencerPublisher {
|
|
|
335
362
|
// @note - we can only have one blob config per bundle
|
|
336
363
|
// find requests with gas and blob configs
|
|
337
364
|
// See https://github.com/AztecProtocol/aztec-packages/issues/11513
|
|
338
|
-
const gasConfigs =
|
|
339
|
-
const blobConfigs =
|
|
365
|
+
const gasConfigs = validRequests.filter(request => request.gasConfig).map(request => request.gasConfig);
|
|
366
|
+
const blobConfigs = validRequests.filter(request => request.blobConfig).map(request => request.blobConfig);
|
|
340
367
|
|
|
341
368
|
if (blobConfigs.length > 1) {
|
|
342
369
|
throw new Error('Multiple blob configs found');
|
|
@@ -346,7 +373,16 @@ export class SequencerPublisher {
|
|
|
346
373
|
|
|
347
374
|
// Merge gasConfigs. Yields the sum of gasLimits, and the earliest txTimeoutAt, or undefined if no gasConfig sets them.
|
|
348
375
|
const gasLimits = gasConfigs.map(g => g?.gasLimit).filter((g): g is bigint => g !== undefined);
|
|
349
|
-
|
|
376
|
+
let gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
|
|
377
|
+
// Cap at L1 block gas limit so the node accepts the tx ("gas limit too high" otherwise).
|
|
378
|
+
const maxGas = MAX_L1_TX_LIMIT;
|
|
379
|
+
if (gasLimit !== undefined && gasLimit > maxGas) {
|
|
380
|
+
this.log.debug('Capping bundled tx gas limit to L1 max', {
|
|
381
|
+
requested: gasLimit,
|
|
382
|
+
capped: maxGas,
|
|
383
|
+
});
|
|
384
|
+
gasLimit = maxGas;
|
|
385
|
+
}
|
|
350
386
|
const txTimeoutAts = gasConfigs.map(g => g?.txTimeoutAt).filter((g): g is Date => g !== undefined);
|
|
351
387
|
const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map(g => g.getTime()))) : undefined; // earliest
|
|
352
388
|
const txConfig: RequestWithExpiry['gasConfig'] = { gasLimit, txTimeoutAt };
|
|
@@ -395,7 +431,16 @@ export class SequencerPublisher {
|
|
|
395
431
|
this.log.error(`Failed to publish bundled transactions (${actionsListStr})`, result);
|
|
396
432
|
return { failedActions: requests.map(r => r.action) };
|
|
397
433
|
} else {
|
|
398
|
-
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
434
|
+
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
435
|
+
result,
|
|
436
|
+
requests: requests.map(r => ({
|
|
437
|
+
...r,
|
|
438
|
+
// Avoid logging large blob data
|
|
439
|
+
blobConfig: r.blobConfig
|
|
440
|
+
? { ...r.blobConfig, blobs: r.blobConfig.blobs.map(b => ({ size: trimmedBytesLength(b) })) }
|
|
441
|
+
: undefined,
|
|
442
|
+
})),
|
|
443
|
+
});
|
|
399
444
|
const successfulActions: Action[] = [];
|
|
400
445
|
const failedActions: Action[] = [];
|
|
401
446
|
for (const request of requests) {
|
|
@@ -410,20 +455,24 @@ export class SequencerPublisher {
|
|
|
410
455
|
}
|
|
411
456
|
|
|
412
457
|
/**
|
|
413
|
-
* @notice Will call `
|
|
458
|
+
* @notice Will call `canProposeAt` to make sure that it is possible to propose
|
|
414
459
|
* @param tipArchive - The archive to check
|
|
415
460
|
* @returns The slot and block number if it is possible to propose, undefined otherwise
|
|
416
461
|
*/
|
|
417
|
-
public
|
|
462
|
+
public canProposeAt(
|
|
418
463
|
tipArchive: Fr,
|
|
419
464
|
msgSender: EthAddress,
|
|
420
|
-
opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
465
|
+
opts: { forcePendingCheckpointNumber?: CheckpointNumber; pipelined?: boolean } = {},
|
|
421
466
|
) {
|
|
422
467
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
423
468
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
424
469
|
|
|
470
|
+
const pipelined = opts.pipelined ?? false;
|
|
471
|
+
const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
|
|
472
|
+
const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
|
|
473
|
+
|
|
425
474
|
return this.rollupContract
|
|
426
|
-
.
|
|
475
|
+
.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
|
|
427
476
|
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
428
477
|
})
|
|
429
478
|
.catch(err => {
|
|
@@ -460,7 +509,7 @@ export class SequencerPublisher {
|
|
|
460
509
|
flags,
|
|
461
510
|
] as const;
|
|
462
511
|
|
|
463
|
-
const ts =
|
|
512
|
+
const ts = this.getSimulationTimestamp(header.slotNumber);
|
|
464
513
|
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
465
514
|
opts?.forcePendingCheckpointNumber,
|
|
466
515
|
);
|
|
@@ -483,7 +532,7 @@ export class SequencerPublisher {
|
|
|
483
532
|
data: encodeFunctionData({ abi: RollupAbi, functionName: 'validateHeaderWithAttestations', args }),
|
|
484
533
|
from: MULTI_CALL_3_ADDRESS,
|
|
485
534
|
},
|
|
486
|
-
{ time: ts
|
|
535
|
+
{ time: ts },
|
|
487
536
|
stateOverrides,
|
|
488
537
|
);
|
|
489
538
|
this.log.debug(`Simulated validateHeader`);
|
|
@@ -517,7 +566,12 @@ export class SequencerPublisher {
|
|
|
517
566
|
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
|
|
518
567
|
|
|
519
568
|
try {
|
|
520
|
-
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
569
|
+
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
570
|
+
request,
|
|
571
|
+
undefined,
|
|
572
|
+
undefined,
|
|
573
|
+
mergeAbis([request.abi ?? [], ErrorsAbi]),
|
|
574
|
+
);
|
|
521
575
|
this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} succeeded`, {
|
|
522
576
|
...logData,
|
|
523
577
|
request,
|
|
@@ -536,7 +590,7 @@ export class SequencerPublisher {
|
|
|
536
590
|
|
|
537
591
|
// If the error is due to the checkpoint not being in the pending chain, and it was indeed removed by someone else,
|
|
538
592
|
// we can safely ignore it and return undefined so we go ahead with checkpoint building.
|
|
539
|
-
if (viemError.message?.includes('
|
|
593
|
+
if (viemError.message?.includes('Rollup__CheckpointNotInPendingChain')) {
|
|
540
594
|
this.log.verbose(
|
|
541
595
|
`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
|
|
542
596
|
{ ...logData, request, error: viemError.message },
|
|
@@ -605,26 +659,9 @@ export class SequencerPublisher {
|
|
|
605
659
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
606
660
|
attestationsAndSignersSignature: Signature,
|
|
607
661
|
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
608
|
-
): Promise<
|
|
609
|
-
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
610
|
-
|
|
611
|
-
// TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
|
|
612
|
-
// If we have no attestations, we still need to provide the empty attestations
|
|
613
|
-
// so that the committee is recalculated correctly
|
|
614
|
-
// const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
615
|
-
// if (ignoreSignatures) {
|
|
616
|
-
// const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
|
|
617
|
-
// if (!committee) {
|
|
618
|
-
// this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
619
|
-
// throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
620
|
-
// }
|
|
621
|
-
// attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
622
|
-
// CommitteeAttestation.fromAddress(committeeMember),
|
|
623
|
-
// );
|
|
624
|
-
// }
|
|
625
|
-
|
|
662
|
+
): Promise<void> {
|
|
626
663
|
const blobFields = checkpoint.toBlobFields();
|
|
627
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
664
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
628
665
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
629
666
|
|
|
630
667
|
const args = [
|
|
@@ -632,7 +669,7 @@ export class SequencerPublisher {
|
|
|
632
669
|
header: checkpoint.header.toViem(),
|
|
633
670
|
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
634
671
|
oracleInput: {
|
|
635
|
-
feeAssetPriceModifier:
|
|
672
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
636
673
|
},
|
|
637
674
|
},
|
|
638
675
|
attestationsAndSigners.getPackedAttestations(),
|
|
@@ -641,13 +678,11 @@ export class SequencerPublisher {
|
|
|
641
678
|
blobInput,
|
|
642
679
|
] as const;
|
|
643
680
|
|
|
644
|
-
await this.simulateProposeTx(args,
|
|
645
|
-
return ts;
|
|
681
|
+
await this.simulateProposeTx(args, options);
|
|
646
682
|
}
|
|
647
683
|
|
|
648
684
|
private async enqueueCastSignalHelper(
|
|
649
685
|
slotNumber: SlotNumber,
|
|
650
|
-
timestamp: bigint,
|
|
651
686
|
signalType: GovernanceSignalAction,
|
|
652
687
|
payload: EthAddress,
|
|
653
688
|
base: IEmpireBase,
|
|
@@ -681,6 +716,32 @@ export class SequencerPublisher {
|
|
|
681
716
|
return false;
|
|
682
717
|
}
|
|
683
718
|
|
|
719
|
+
// Check if payload was already submitted to governance
|
|
720
|
+
const cacheKey = payload.toString();
|
|
721
|
+
if (!this.payloadProposedCache.has(cacheKey)) {
|
|
722
|
+
try {
|
|
723
|
+
const l1StartBlock = await this.rollupContract.getL1StartBlock();
|
|
724
|
+
const proposed = await retry(
|
|
725
|
+
() => base.hasPayloadBeenProposed(payload.toString(), l1StartBlock),
|
|
726
|
+
'Check if payload was proposed',
|
|
727
|
+
makeBackoff([0, 1, 2]),
|
|
728
|
+
this.log,
|
|
729
|
+
true,
|
|
730
|
+
);
|
|
731
|
+
if (proposed) {
|
|
732
|
+
this.payloadProposedCache.add(cacheKey);
|
|
733
|
+
}
|
|
734
|
+
} catch (err) {
|
|
735
|
+
this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
|
|
736
|
+
return false;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (this.payloadProposedCache.has(cacheKey)) {
|
|
741
|
+
this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
|
|
742
|
+
return false;
|
|
743
|
+
}
|
|
744
|
+
|
|
684
745
|
const cachedLastVote = this.lastActions[signalType];
|
|
685
746
|
this.lastActions[signalType] = slotNumber;
|
|
686
747
|
const action = signalType;
|
|
@@ -699,11 +760,16 @@ export class SequencerPublisher {
|
|
|
699
760
|
lastValidL2Slot: slotNumber,
|
|
700
761
|
});
|
|
701
762
|
|
|
763
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
764
|
+
|
|
702
765
|
try {
|
|
703
|
-
await this.l1TxUtils.simulate(request, { time: timestamp }, [], ErrorsAbi);
|
|
766
|
+
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
704
767
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
705
768
|
} catch (err) {
|
|
706
|
-
|
|
769
|
+
const viemError = formatViemError(err);
|
|
770
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError, {
|
|
771
|
+
simulationTimestamp: timestamp,
|
|
772
|
+
});
|
|
707
773
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
708
774
|
}
|
|
709
775
|
|
|
@@ -754,19 +820,16 @@ export class SequencerPublisher {
|
|
|
754
820
|
/**
|
|
755
821
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
756
822
|
* @param slotNumber - The slot number to cast a signal for.
|
|
757
|
-
* @param timestamp - The timestamp of the slot to cast a signal for.
|
|
758
823
|
* @returns True if the signal was successfully enqueued, false otherwise.
|
|
759
824
|
*/
|
|
760
825
|
public enqueueGovernanceCastSignal(
|
|
761
826
|
governancePayload: EthAddress,
|
|
762
827
|
slotNumber: SlotNumber,
|
|
763
|
-
timestamp: bigint,
|
|
764
828
|
signerAddress: EthAddress,
|
|
765
829
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
766
830
|
): Promise<boolean> {
|
|
767
831
|
return this.enqueueCastSignalHelper(
|
|
768
832
|
slotNumber,
|
|
769
|
-
timestamp,
|
|
770
833
|
'governance-signal',
|
|
771
834
|
governancePayload,
|
|
772
835
|
this.govProposerContract,
|
|
@@ -779,7 +842,6 @@ export class SequencerPublisher {
|
|
|
779
842
|
public async enqueueSlashingActions(
|
|
780
843
|
actions: ProposerSlashAction[],
|
|
781
844
|
slotNumber: SlotNumber,
|
|
782
|
-
timestamp: bigint,
|
|
783
845
|
signerAddress: EthAddress,
|
|
784
846
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
785
847
|
): Promise<boolean> {
|
|
@@ -800,7 +862,6 @@ export class SequencerPublisher {
|
|
|
800
862
|
});
|
|
801
863
|
await this.enqueueCastSignalHelper(
|
|
802
864
|
slotNumber,
|
|
803
|
-
timestamp,
|
|
804
865
|
'empire-slashing-signal',
|
|
805
866
|
action.payload,
|
|
806
867
|
this.slashingProposerContract,
|
|
@@ -819,7 +880,6 @@ export class SequencerPublisher {
|
|
|
819
880
|
(receipt: TransactionReceipt) =>
|
|
820
881
|
!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs),
|
|
821
882
|
slotNumber,
|
|
822
|
-
timestamp,
|
|
823
883
|
);
|
|
824
884
|
break;
|
|
825
885
|
}
|
|
@@ -837,7 +897,6 @@ export class SequencerPublisher {
|
|
|
837
897
|
request,
|
|
838
898
|
(receipt: TransactionReceipt) => !!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs),
|
|
839
899
|
slotNumber,
|
|
840
|
-
timestamp,
|
|
841
900
|
);
|
|
842
901
|
break;
|
|
843
902
|
}
|
|
@@ -861,7 +920,6 @@ export class SequencerPublisher {
|
|
|
861
920
|
request,
|
|
862
921
|
(receipt: TransactionReceipt) => !!tallySlashingProposer.tryExtractVoteCastEvent(receipt.logs),
|
|
863
922
|
slotNumber,
|
|
864
|
-
timestamp,
|
|
865
923
|
);
|
|
866
924
|
break;
|
|
867
925
|
}
|
|
@@ -883,7 +941,6 @@ export class SequencerPublisher {
|
|
|
883
941
|
request,
|
|
884
942
|
(receipt: TransactionReceipt) => !!tallySlashingProposer.tryExtractRoundExecutedEvent(receipt.logs),
|
|
885
943
|
slotNumber,
|
|
886
|
-
timestamp,
|
|
887
944
|
);
|
|
888
945
|
break;
|
|
889
946
|
}
|
|
@@ -908,25 +965,24 @@ export class SequencerPublisher {
|
|
|
908
965
|
const checkpointHeader = checkpoint.header;
|
|
909
966
|
|
|
910
967
|
const blobFields = checkpoint.toBlobFields();
|
|
911
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
968
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
912
969
|
|
|
913
|
-
const proposeTxArgs = {
|
|
970
|
+
const proposeTxArgs: L1ProcessArgs = {
|
|
914
971
|
header: checkpointHeader,
|
|
915
972
|
archive: checkpoint.archive.root.toBuffer(),
|
|
916
973
|
blobs,
|
|
917
974
|
attestationsAndSigners,
|
|
918
975
|
attestationsAndSignersSignature,
|
|
976
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
919
977
|
};
|
|
920
978
|
|
|
921
|
-
let ts: bigint;
|
|
922
|
-
|
|
923
979
|
try {
|
|
924
980
|
// @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
|
|
925
981
|
// This means that we can avoid the simulation issues in later checks.
|
|
926
982
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
927
983
|
// make time consistency checks break.
|
|
928
984
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
929
|
-
|
|
985
|
+
await this.validateCheckpointForSubmission(
|
|
930
986
|
checkpoint,
|
|
931
987
|
attestationsAndSigners,
|
|
932
988
|
attestationsAndSignersSignature,
|
|
@@ -942,7 +998,7 @@ export class SequencerPublisher {
|
|
|
942
998
|
}
|
|
943
999
|
|
|
944
1000
|
this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
|
|
945
|
-
await this.addProposeTx(checkpoint, proposeTxArgs, opts
|
|
1001
|
+
await this.addProposeTx(checkpoint, proposeTxArgs, opts);
|
|
946
1002
|
}
|
|
947
1003
|
|
|
948
1004
|
public enqueueInvalidateCheckpoint(
|
|
@@ -985,8 +1041,8 @@ export class SequencerPublisher {
|
|
|
985
1041
|
request: L1TxRequest,
|
|
986
1042
|
checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
|
|
987
1043
|
slotNumber: SlotNumber,
|
|
988
|
-
timestamp: bigint,
|
|
989
1044
|
) {
|
|
1045
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
990
1046
|
const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
|
|
991
1047
|
if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
|
|
992
1048
|
this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
|
|
@@ -999,12 +1055,15 @@ export class SequencerPublisher {
|
|
|
999
1055
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
1000
1056
|
|
|
1001
1057
|
let gasUsed: bigint;
|
|
1058
|
+
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
1059
|
+
|
|
1002
1060
|
try {
|
|
1003
|
-
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [],
|
|
1061
|
+
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi));
|
|
1004
1062
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
1005
1063
|
} catch (err) {
|
|
1006
|
-
const viemError = formatViemError(err);
|
|
1064
|
+
const viemError = formatViemError(err, simulateAbi);
|
|
1007
1065
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1066
|
+
|
|
1008
1067
|
return false;
|
|
1009
1068
|
}
|
|
1010
1069
|
|
|
@@ -1012,10 +1071,14 @@ export class SequencerPublisher {
|
|
|
1012
1071
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(gasUsed) * 64) / 63)));
|
|
1013
1072
|
logData.gasLimit = gasLimit;
|
|
1014
1073
|
|
|
1074
|
+
// Store the ABI used for simulation on the request so Multicall3.forward can decode errors
|
|
1075
|
+
// when the tx is sent and a revert is diagnosed via simulation.
|
|
1076
|
+
const requestWithAbi = { ...request, abi: simulateAbi };
|
|
1077
|
+
|
|
1015
1078
|
this.log.debug(`Enqueuing ${action}`, logData);
|
|
1016
1079
|
this.addRequest({
|
|
1017
1080
|
action,
|
|
1018
|
-
request,
|
|
1081
|
+
request: requestWithAbi,
|
|
1019
1082
|
gasConfig: { gasLimit },
|
|
1020
1083
|
lastValidL2Slot: slotNumber,
|
|
1021
1084
|
checkSuccess: (_req, result) => {
|
|
@@ -1051,7 +1114,6 @@ export class SequencerPublisher {
|
|
|
1051
1114
|
|
|
1052
1115
|
private async prepareProposeTx(
|
|
1053
1116
|
encodedData: L1ProcessArgs,
|
|
1054
|
-
timestamp: bigint,
|
|
1055
1117
|
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1056
1118
|
) {
|
|
1057
1119
|
const kzg = Blob.getViemKzgInstance();
|
|
@@ -1097,8 +1159,7 @@ export class SequencerPublisher {
|
|
|
1097
1159
|
header: encodedData.header.toViem(),
|
|
1098
1160
|
archive: toHex(encodedData.archive),
|
|
1099
1161
|
oracleInput: {
|
|
1100
|
-
|
|
1101
|
-
feeAssetPriceModifier: 0n,
|
|
1162
|
+
feeAssetPriceModifier: encodedData.feeAssetPriceModifier,
|
|
1102
1163
|
},
|
|
1103
1164
|
},
|
|
1104
1165
|
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
@@ -1107,7 +1168,7 @@ export class SequencerPublisher {
|
|
|
1107
1168
|
blobInput,
|
|
1108
1169
|
] as const;
|
|
1109
1170
|
|
|
1110
|
-
const { rollupData, simulationResult } = await this.simulateProposeTx(args,
|
|
1171
|
+
const { rollupData, simulationResult } = await this.simulateProposeTx(args, options);
|
|
1111
1172
|
|
|
1112
1173
|
return { args, blobEvaluationGas, rollupData, simulationResult };
|
|
1113
1174
|
}
|
|
@@ -1115,7 +1176,6 @@ export class SequencerPublisher {
|
|
|
1115
1176
|
/**
|
|
1116
1177
|
* Simulates the propose tx with eth_simulateV1
|
|
1117
1178
|
* @param args - The propose tx args
|
|
1118
|
-
* @param timestamp - The timestamp to simulate proposal at
|
|
1119
1179
|
* @returns The simulation result
|
|
1120
1180
|
*/
|
|
1121
1181
|
private async simulateProposeTx(
|
|
@@ -1124,7 +1184,7 @@ export class SequencerPublisher {
|
|
|
1124
1184
|
readonly header: ViemHeader;
|
|
1125
1185
|
readonly archive: `0x${string}`;
|
|
1126
1186
|
readonly oracleInput: {
|
|
1127
|
-
readonly feeAssetPriceModifier:
|
|
1187
|
+
readonly feeAssetPriceModifier: bigint;
|
|
1128
1188
|
};
|
|
1129
1189
|
},
|
|
1130
1190
|
ViemCommitteeAttestations,
|
|
@@ -1132,7 +1192,6 @@ export class SequencerPublisher {
|
|
|
1132
1192
|
ViemSignature,
|
|
1133
1193
|
`0x${string}`,
|
|
1134
1194
|
],
|
|
1135
|
-
timestamp: bigint,
|
|
1136
1195
|
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1137
1196
|
) {
|
|
1138
1197
|
const rollupData = encodeFunctionData({
|
|
@@ -1166,25 +1225,26 @@ export class SequencerPublisher {
|
|
|
1166
1225
|
});
|
|
1167
1226
|
}
|
|
1168
1227
|
|
|
1228
|
+
const simTs = this.getSimulationTimestamp(SlotNumber.fromBigInt(args[0].header.slotNumber));
|
|
1229
|
+
|
|
1169
1230
|
const simulationResult = await this.l1TxUtils
|
|
1170
1231
|
.simulate(
|
|
1171
1232
|
{
|
|
1172
1233
|
to: this.rollupContract.address,
|
|
1173
1234
|
data: rollupData,
|
|
1174
|
-
gas:
|
|
1235
|
+
gas: MAX_L1_TX_LIMIT,
|
|
1175
1236
|
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1176
1237
|
},
|
|
1177
1238
|
{
|
|
1178
|
-
|
|
1179
|
-
time: timestamp + 1n,
|
|
1239
|
+
time: simTs,
|
|
1180
1240
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1181
|
-
gasLimit:
|
|
1241
|
+
gasLimit: MAX_L1_TX_LIMIT * 2n,
|
|
1182
1242
|
},
|
|
1183
1243
|
stateOverrides,
|
|
1184
1244
|
RollupAbi,
|
|
1185
1245
|
{
|
|
1186
1246
|
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
1187
|
-
fallbackGasEstimate:
|
|
1247
|
+
fallbackGasEstimate: MAX_L1_TX_LIMIT,
|
|
1188
1248
|
},
|
|
1189
1249
|
)
|
|
1190
1250
|
.catch(err => {
|
|
@@ -1194,11 +1254,11 @@ export class SequencerPublisher {
|
|
|
1194
1254
|
this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
|
|
1195
1255
|
// Return a minimal simulation result with the fallback gas estimate
|
|
1196
1256
|
return {
|
|
1197
|
-
gasUsed:
|
|
1257
|
+
gasUsed: MAX_L1_TX_LIMIT,
|
|
1198
1258
|
logs: [],
|
|
1199
1259
|
};
|
|
1200
1260
|
}
|
|
1201
|
-
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1261
|
+
this.log.error(`Failed to simulate propose tx`, viemError, { simulationTimestamp: simTs });
|
|
1202
1262
|
throw err;
|
|
1203
1263
|
});
|
|
1204
1264
|
|
|
@@ -1209,16 +1269,11 @@ export class SequencerPublisher {
|
|
|
1209
1269
|
checkpoint: Checkpoint,
|
|
1210
1270
|
encodedData: L1ProcessArgs,
|
|
1211
1271
|
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
1212
|
-
timestamp: bigint,
|
|
1213
1272
|
): Promise<void> {
|
|
1214
1273
|
const slot = checkpoint.header.slotNumber;
|
|
1215
1274
|
const timer = new Timer();
|
|
1216
1275
|
const kzg = Blob.getViemKzgInstance();
|
|
1217
|
-
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
1218
|
-
encodedData,
|
|
1219
|
-
timestamp,
|
|
1220
|
-
opts,
|
|
1221
|
-
);
|
|
1276
|
+
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, opts);
|
|
1222
1277
|
const startBlock = await this.l1TxUtils.getBlockNumber();
|
|
1223
1278
|
const gasLimit = this.l1TxUtils.bumpGasLimit(
|
|
1224
1279
|
BigInt(Math.ceil((Number(simulationResult.gasUsed) * 64) / 63)) +
|
|
@@ -1294,4 +1349,17 @@ export class SequencerPublisher {
|
|
|
1294
1349
|
},
|
|
1295
1350
|
});
|
|
1296
1351
|
}
|
|
1352
|
+
|
|
1353
|
+
/** Returns the timestamp of the last L1 slot within a given L2 slot. Used as the simulation timestamp
|
|
1354
|
+
* for eth_simulateV1 calls, since it's guaranteed to be greater than any L1 block produced during the slot. */
|
|
1355
|
+
private getSimulationTimestamp(slot: SlotNumber): bigint {
|
|
1356
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1357
|
+
return getLastL1SlotTimestampForL2Slot(slot, l1Constants);
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
/** Returns the timestamp of the next L1 slot boundary after now. */
|
|
1361
|
+
private getNextL1SlotTimestamp(): bigint {
|
|
1362
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1363
|
+
return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
|
|
1364
|
+
}
|
|
1297
1365
|
}
|