@aztec/sequencer-client 0.0.1-commit.c7c42ec → 0.0.1-commit.cf93bcc56
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 +14 -10
- 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 +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-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 +32 -23
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +522 -88
- 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 +633 -62
- 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 +33 -18
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +508 -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 +28 -11
- 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 +112 -43
- package/src/publisher/sequencer-publisher-factory.ts +23 -6
- package/src/publisher/sequencer-publisher-metrics.ts +17 -69
- package/src/publisher/sequencer-publisher.ts +180 -118
- package/src/sequencer/checkpoint_proposal_job.ts +299 -91
- 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 +152 -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
|
@@ -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,33 +19,35 @@ 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
48
|
import { type StateOverride, type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
|
|
46
49
|
|
|
47
|
-
import type {
|
|
50
|
+
import type { SequencerPublisherConfig } from './config.js';
|
|
48
51
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
49
52
|
|
|
50
53
|
/** Arguments to the process method of the rollup contract */
|
|
@@ -59,6 +62,8 @@ type L1ProcessArgs = {
|
|
|
59
62
|
attestationsAndSigners: CommitteeAttestationsAndSigners;
|
|
60
63
|
/** Attestations and signers signature */
|
|
61
64
|
attestationsAndSignersSignature: Signature;
|
|
65
|
+
/** The fee asset price modifier in basis points (from oracle) */
|
|
66
|
+
feeAssetPriceModifier: bigint;
|
|
62
67
|
};
|
|
63
68
|
|
|
64
69
|
export const Actions = [
|
|
@@ -80,12 +85,12 @@ type GovernanceSignalAction = Extract<Action, 'governance-signal' | 'empire-slas
|
|
|
80
85
|
// Sorting for actions such that invalidations go before proposals, and proposals go before votes
|
|
81
86
|
export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
|
|
82
87
|
|
|
83
|
-
export type
|
|
88
|
+
export type InvalidateCheckpointRequest = {
|
|
84
89
|
request: L1TxRequest;
|
|
85
90
|
reason: 'invalid-attestation' | 'insufficient-attestations';
|
|
86
91
|
gasUsed: bigint;
|
|
87
|
-
|
|
88
|
-
|
|
92
|
+
checkpointNumber: CheckpointNumber;
|
|
93
|
+
forcePendingCheckpointNumber: CheckpointNumber;
|
|
89
94
|
};
|
|
90
95
|
|
|
91
96
|
interface RequestWithExpiry {
|
|
@@ -111,6 +116,7 @@ export class SequencerPublisher {
|
|
|
111
116
|
protected lastActions: Partial<Record<Action, SlotNumber>> = {};
|
|
112
117
|
|
|
113
118
|
private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
|
|
119
|
+
private payloadProposedCache: Set<string> = new Set<string>();
|
|
114
120
|
|
|
115
121
|
protected log: Logger;
|
|
116
122
|
protected ethereumSlotDuration: bigint;
|
|
@@ -122,10 +128,9 @@ export class SequencerPublisher {
|
|
|
122
128
|
|
|
123
129
|
/** L1 fee analyzer for fisherman mode */
|
|
124
130
|
private l1FeeAnalyzer?: L1FeeAnalyzer;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
public static PROPOSE_GAS_GUESS: bigint = 12_000_000n;
|
|
131
|
+
|
|
132
|
+
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */
|
|
133
|
+
private feeAssetPriceOracle: FeeAssetPriceOracle;
|
|
129
134
|
|
|
130
135
|
// A CALL to a cold address is 2700 gas
|
|
131
136
|
public static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
@@ -133,20 +138,23 @@ export class SequencerPublisher {
|
|
|
133
138
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
134
139
|
public static VOTE_GAS_GUESS: bigint = 800_000n;
|
|
135
140
|
|
|
136
|
-
public l1TxUtils:
|
|
141
|
+
public l1TxUtils: L1TxUtils;
|
|
137
142
|
public rollupContract: RollupContract;
|
|
138
143
|
public govProposerContract: GovernanceProposerContract;
|
|
139
144
|
public slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
140
145
|
public slashFactoryContract: SlashFactoryContract;
|
|
141
146
|
|
|
147
|
+
public readonly tracer: Tracer;
|
|
148
|
+
|
|
142
149
|
protected requests: RequestWithExpiry[] = [];
|
|
143
150
|
|
|
144
151
|
constructor(
|
|
145
|
-
private config:
|
|
152
|
+
private config: Pick<SequencerPublisherConfig, 'fishermanMode'> &
|
|
153
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration'> & { l1ChainId: number },
|
|
146
154
|
deps: {
|
|
147
155
|
telemetry?: TelemetryClient;
|
|
148
156
|
blobClient: BlobClientInterface;
|
|
149
|
-
l1TxUtils:
|
|
157
|
+
l1TxUtils: L1TxUtils;
|
|
150
158
|
rollupContract: RollupContract;
|
|
151
159
|
slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
152
160
|
governanceProposerContract: GovernanceProposerContract;
|
|
@@ -167,6 +175,7 @@ export class SequencerPublisher {
|
|
|
167
175
|
|
|
168
176
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
169
177
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
178
|
+
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
170
179
|
this.l1TxUtils = deps.l1TxUtils;
|
|
171
180
|
|
|
172
181
|
this.rollupContract = deps.rollupContract;
|
|
@@ -189,12 +198,27 @@ export class SequencerPublisher {
|
|
|
189
198
|
createLogger('sequencer:publisher:fee-analyzer'),
|
|
190
199
|
);
|
|
191
200
|
}
|
|
201
|
+
|
|
202
|
+
// Initialize fee asset price oracle
|
|
203
|
+
this.feeAssetPriceOracle = new FeeAssetPriceOracle(
|
|
204
|
+
this.l1TxUtils.client,
|
|
205
|
+
this.rollupContract,
|
|
206
|
+
createLogger('sequencer:publisher:price-oracle'),
|
|
207
|
+
);
|
|
192
208
|
}
|
|
193
209
|
|
|
194
210
|
public getRollupContract(): RollupContract {
|
|
195
211
|
return this.rollupContract;
|
|
196
212
|
}
|
|
197
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Gets the fee asset price modifier from the oracle.
|
|
216
|
+
* Returns 0n if the oracle query fails.
|
|
217
|
+
*/
|
|
218
|
+
public getFeeAssetPriceModifier(): Promise<bigint> {
|
|
219
|
+
return this.feeAssetPriceOracle.computePriceModifier();
|
|
220
|
+
}
|
|
221
|
+
|
|
198
222
|
public getSenderAddress() {
|
|
199
223
|
return this.l1TxUtils.getSenderAddress();
|
|
200
224
|
}
|
|
@@ -270,7 +294,7 @@ export class SequencerPublisher {
|
|
|
270
294
|
// Start the analysis
|
|
271
295
|
const analysisId = await this.l1FeeAnalyzer.startAnalysis(
|
|
272
296
|
l2SlotNumber,
|
|
273
|
-
gasLimit > 0n ? gasLimit :
|
|
297
|
+
gasLimit > 0n ? gasLimit : MAX_L1_TX_LIMIT,
|
|
274
298
|
l1Requests,
|
|
275
299
|
blobConfig,
|
|
276
300
|
onComplete,
|
|
@@ -296,6 +320,7 @@ export class SequencerPublisher {
|
|
|
296
320
|
* - a receipt and errorMsg if it failed on L1
|
|
297
321
|
* - undefined if no valid requests are found OR the tx failed to send.
|
|
298
322
|
*/
|
|
323
|
+
@trackSpan('SequencerPublisher.sendRequests')
|
|
299
324
|
public async sendRequests() {
|
|
300
325
|
const requestsToProcess = [...this.requests];
|
|
301
326
|
this.requests = [];
|
|
@@ -342,7 +367,16 @@ export class SequencerPublisher {
|
|
|
342
367
|
|
|
343
368
|
// Merge gasConfigs. Yields the sum of gasLimits, and the earliest txTimeoutAt, or undefined if no gasConfig sets them.
|
|
344
369
|
const gasLimits = gasConfigs.map(g => g?.gasLimit).filter((g): g is bigint => g !== undefined);
|
|
345
|
-
|
|
370
|
+
let gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
|
|
371
|
+
// Cap at L1 block gas limit so the node accepts the tx ("gas limit too high" otherwise).
|
|
372
|
+
const maxGas = MAX_L1_TX_LIMIT;
|
|
373
|
+
if (gasLimit !== undefined && gasLimit > maxGas) {
|
|
374
|
+
this.log.debug('Capping bundled tx gas limit to L1 max', {
|
|
375
|
+
requested: gasLimit,
|
|
376
|
+
capped: maxGas,
|
|
377
|
+
});
|
|
378
|
+
gasLimit = maxGas;
|
|
379
|
+
}
|
|
346
380
|
const txTimeoutAts = gasConfigs.map(g => g?.txTimeoutAt).filter((g): g is Date => g !== undefined);
|
|
347
381
|
const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map(g => g.getTime()))) : undefined; // earliest
|
|
348
382
|
const txConfig: RequestWithExpiry['gasConfig'] = { gasLimit, txTimeoutAt };
|
|
@@ -413,17 +447,14 @@ export class SequencerPublisher {
|
|
|
413
447
|
public canProposeAtNextEthBlock(
|
|
414
448
|
tipArchive: Fr,
|
|
415
449
|
msgSender: EthAddress,
|
|
416
|
-
opts: {
|
|
450
|
+
opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
417
451
|
) {
|
|
418
452
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
419
453
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
420
454
|
|
|
421
455
|
return this.rollupContract
|
|
422
456
|
.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
|
|
423
|
-
forcePendingCheckpointNumber:
|
|
424
|
-
opts.forcePendingBlockNumber !== undefined
|
|
425
|
-
? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber)
|
|
426
|
-
: undefined,
|
|
457
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
427
458
|
})
|
|
428
459
|
.catch(err => {
|
|
429
460
|
if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
|
|
@@ -442,10 +473,11 @@ export class SequencerPublisher {
|
|
|
442
473
|
* It will throw if the block header is invalid.
|
|
443
474
|
* @param header - The block header to validate
|
|
444
475
|
*/
|
|
476
|
+
@trackSpan('SequencerPublisher.validateBlockHeader')
|
|
445
477
|
public async validateBlockHeader(
|
|
446
478
|
header: CheckpointHeader,
|
|
447
|
-
opts?: {
|
|
448
|
-
) {
|
|
479
|
+
opts?: { forcePendingCheckpointNumber: CheckpointNumber | undefined },
|
|
480
|
+
): Promise<void> {
|
|
449
481
|
const flags = { ignoreDA: true, ignoreSignatures: true };
|
|
450
482
|
|
|
451
483
|
const args = [
|
|
@@ -454,17 +486,13 @@ export class SequencerPublisher {
|
|
|
454
486
|
[], // no signers
|
|
455
487
|
Signature.empty().toViemSignature(),
|
|
456
488
|
`0x${'0'.repeat(64)}`, // 32 empty bytes
|
|
457
|
-
header.
|
|
489
|
+
header.blobsHash.toString(),
|
|
458
490
|
flags,
|
|
459
491
|
] as const;
|
|
460
492
|
|
|
461
493
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
462
|
-
const optsForcePendingCheckpointNumber =
|
|
463
|
-
opts?.forcePendingBlockNumber !== undefined
|
|
464
|
-
? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber)
|
|
465
|
-
: undefined;
|
|
466
494
|
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
467
|
-
|
|
495
|
+
opts?.forcePendingCheckpointNumber,
|
|
468
496
|
);
|
|
469
497
|
let balance = 0n;
|
|
470
498
|
if (this.config.fishermanMode) {
|
|
@@ -492,77 +520,95 @@ export class SequencerPublisher {
|
|
|
492
520
|
}
|
|
493
521
|
|
|
494
522
|
/**
|
|
495
|
-
* Simulate making a call to invalidate a
|
|
496
|
-
* @param
|
|
523
|
+
* Simulate making a call to invalidate a checkpoint with invalid attestations. Returns undefined if no need to invalidate.
|
|
524
|
+
* @param validationResult - The validation result indicating which checkpoint to invalidate (as returned by the archiver)
|
|
497
525
|
*/
|
|
498
|
-
public async
|
|
499
|
-
validationResult:
|
|
500
|
-
): Promise<
|
|
526
|
+
public async simulateInvalidateCheckpoint(
|
|
527
|
+
validationResult: ValidateCheckpointResult,
|
|
528
|
+
): Promise<InvalidateCheckpointRequest | undefined> {
|
|
501
529
|
if (validationResult.valid) {
|
|
502
530
|
return undefined;
|
|
503
531
|
}
|
|
504
532
|
|
|
505
|
-
const { reason,
|
|
506
|
-
const
|
|
507
|
-
const logData = { ...
|
|
533
|
+
const { reason, checkpoint } = validationResult;
|
|
534
|
+
const checkpointNumber = checkpoint.checkpointNumber;
|
|
535
|
+
const logData = { ...checkpoint, reason };
|
|
508
536
|
|
|
509
|
-
const
|
|
510
|
-
if (
|
|
537
|
+
const currentCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
538
|
+
if (currentCheckpointNumber < checkpointNumber) {
|
|
511
539
|
this.log.verbose(
|
|
512
|
-
`Skipping
|
|
513
|
-
{
|
|
540
|
+
`Skipping checkpoint ${checkpointNumber} invalidation since it has already been removed from the pending chain`,
|
|
541
|
+
{ currentCheckpointNumber, ...logData },
|
|
514
542
|
);
|
|
515
543
|
return undefined;
|
|
516
544
|
}
|
|
517
545
|
|
|
518
|
-
const request = this.
|
|
519
|
-
this.log.debug(`Simulating invalidate
|
|
546
|
+
const request = this.buildInvalidateCheckpointRequest(validationResult);
|
|
547
|
+
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
|
|
520
548
|
|
|
521
549
|
try {
|
|
522
|
-
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
523
|
-
|
|
550
|
+
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
551
|
+
request,
|
|
552
|
+
undefined,
|
|
553
|
+
undefined,
|
|
554
|
+
mergeAbis([request.abi ?? [], ErrorsAbi]),
|
|
555
|
+
);
|
|
556
|
+
this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} succeeded`, {
|
|
557
|
+
...logData,
|
|
558
|
+
request,
|
|
559
|
+
gasUsed,
|
|
560
|
+
});
|
|
524
561
|
|
|
525
|
-
return {
|
|
562
|
+
return {
|
|
563
|
+
request,
|
|
564
|
+
gasUsed,
|
|
565
|
+
checkpointNumber,
|
|
566
|
+
forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
|
|
567
|
+
reason,
|
|
568
|
+
};
|
|
526
569
|
} catch (err) {
|
|
527
570
|
const viemError = formatViemError(err);
|
|
528
571
|
|
|
529
|
-
// If the error is due to the
|
|
530
|
-
// we can safely ignore it and return undefined so we go ahead with
|
|
531
|
-
if (viemError.message?.includes('
|
|
572
|
+
// If the error is due to the checkpoint not being in the pending chain, and it was indeed removed by someone else,
|
|
573
|
+
// we can safely ignore it and return undefined so we go ahead with checkpoint building.
|
|
574
|
+
if (viemError.message?.includes('Rollup__CheckpointNotInPendingChain')) {
|
|
532
575
|
this.log.verbose(
|
|
533
|
-
`Simulation for invalidate
|
|
576
|
+
`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
|
|
534
577
|
{ ...logData, request, error: viemError.message },
|
|
535
578
|
);
|
|
536
|
-
const
|
|
537
|
-
if (
|
|
538
|
-
this.log.verbose(`
|
|
579
|
+
const latestPendingCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
580
|
+
if (latestPendingCheckpointNumber < checkpointNumber) {
|
|
581
|
+
this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, { ...logData });
|
|
539
582
|
return undefined;
|
|
540
583
|
} else {
|
|
541
584
|
this.log.error(
|
|
542
|
-
`Simulation for invalidate ${
|
|
585
|
+
`Simulation for invalidate checkpoint ${checkpointNumber} failed and it is still in pending chain`,
|
|
543
586
|
viemError,
|
|
544
587
|
logData,
|
|
545
588
|
);
|
|
546
|
-
throw new Error(
|
|
547
|
-
|
|
548
|
-
|
|
589
|
+
throw new Error(
|
|
590
|
+
`Failed to simulate invalidate checkpoint ${checkpointNumber} while it is still in pending chain`,
|
|
591
|
+
{
|
|
592
|
+
cause: viemError,
|
|
593
|
+
},
|
|
594
|
+
);
|
|
549
595
|
}
|
|
550
596
|
}
|
|
551
597
|
|
|
552
|
-
// Otherwise, throw. We cannot build the next
|
|
553
|
-
this.log.error(`Simulation for invalidate
|
|
554
|
-
throw new Error(`Failed to simulate invalidate
|
|
598
|
+
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
599
|
+
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
|
|
600
|
+
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
|
|
555
601
|
}
|
|
556
602
|
}
|
|
557
603
|
|
|
558
|
-
private
|
|
604
|
+
private buildInvalidateCheckpointRequest(validationResult: ValidateCheckpointResult) {
|
|
559
605
|
if (validationResult.valid) {
|
|
560
|
-
throw new Error('Cannot invalidate a valid
|
|
606
|
+
throw new Error('Cannot invalidate a valid checkpoint');
|
|
561
607
|
}
|
|
562
608
|
|
|
563
|
-
const {
|
|
564
|
-
const logData = { ...
|
|
565
|
-
this.log.debug(`
|
|
609
|
+
const { checkpoint, committee, reason } = validationResult;
|
|
610
|
+
const logData = { ...checkpoint, reason };
|
|
611
|
+
this.log.debug(`Building invalidate checkpoint ${checkpoint.checkpointNumber} request`, logData);
|
|
566
612
|
|
|
567
613
|
const attestationsAndSigners = new CommitteeAttestationsAndSigners(
|
|
568
614
|
validationResult.attestations,
|
|
@@ -570,14 +616,14 @@ export class SequencerPublisher {
|
|
|
570
616
|
|
|
571
617
|
if (reason === 'invalid-attestation') {
|
|
572
618
|
return this.rollupContract.buildInvalidateBadAttestationRequest(
|
|
573
|
-
|
|
619
|
+
checkpoint.checkpointNumber,
|
|
574
620
|
attestationsAndSigners,
|
|
575
621
|
committee,
|
|
576
622
|
validationResult.invalidIndex,
|
|
577
623
|
);
|
|
578
624
|
} else if (reason === 'insufficient-attestations') {
|
|
579
625
|
return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
|
|
580
|
-
|
|
626
|
+
checkpoint.checkpointNumber,
|
|
581
627
|
attestationsAndSigners,
|
|
582
628
|
committee,
|
|
583
629
|
);
|
|
@@ -588,31 +634,16 @@ export class SequencerPublisher {
|
|
|
588
634
|
}
|
|
589
635
|
|
|
590
636
|
/** Simulates `propose` to make sure that the checkpoint is valid for submission */
|
|
637
|
+
@trackSpan('SequencerPublisher.validateCheckpointForSubmission')
|
|
591
638
|
public async validateCheckpointForSubmission(
|
|
592
639
|
checkpoint: Checkpoint,
|
|
593
640
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
594
641
|
attestationsAndSignersSignature: Signature,
|
|
595
|
-
options: {
|
|
642
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
596
643
|
): Promise<bigint> {
|
|
597
644
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
598
|
-
|
|
599
|
-
// TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
|
|
600
|
-
// If we have no attestations, we still need to provide the empty attestations
|
|
601
|
-
// so that the committee is recalculated correctly
|
|
602
|
-
// const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
603
|
-
// if (ignoreSignatures) {
|
|
604
|
-
// const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
|
|
605
|
-
// if (!committee) {
|
|
606
|
-
// this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
607
|
-
// throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
608
|
-
// }
|
|
609
|
-
// attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
610
|
-
// CommitteeAttestation.fromAddress(committeeMember),
|
|
611
|
-
// );
|
|
612
|
-
// }
|
|
613
|
-
|
|
614
645
|
const blobFields = checkpoint.toBlobFields();
|
|
615
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
646
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
616
647
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
617
648
|
|
|
618
649
|
const args = [
|
|
@@ -620,7 +651,7 @@ export class SequencerPublisher {
|
|
|
620
651
|
header: checkpoint.header.toViem(),
|
|
621
652
|
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
622
653
|
oracleInput: {
|
|
623
|
-
feeAssetPriceModifier:
|
|
654
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
624
655
|
},
|
|
625
656
|
},
|
|
626
657
|
attestationsAndSigners.getPackedAttestations(),
|
|
@@ -669,6 +700,32 @@ export class SequencerPublisher {
|
|
|
669
700
|
return false;
|
|
670
701
|
}
|
|
671
702
|
|
|
703
|
+
// Check if payload was already submitted to governance
|
|
704
|
+
const cacheKey = payload.toString();
|
|
705
|
+
if (!this.payloadProposedCache.has(cacheKey)) {
|
|
706
|
+
try {
|
|
707
|
+
const l1StartBlock = await this.rollupContract.getL1StartBlock();
|
|
708
|
+
const proposed = await retry(
|
|
709
|
+
() => base.hasPayloadBeenProposed(payload.toString(), l1StartBlock),
|
|
710
|
+
'Check if payload was proposed',
|
|
711
|
+
makeBackoff([0, 1, 2]),
|
|
712
|
+
this.log,
|
|
713
|
+
true,
|
|
714
|
+
);
|
|
715
|
+
if (proposed) {
|
|
716
|
+
this.payloadProposedCache.add(cacheKey);
|
|
717
|
+
}
|
|
718
|
+
} catch (err) {
|
|
719
|
+
this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
if (this.payloadProposedCache.has(cacheKey)) {
|
|
725
|
+
this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
|
|
726
|
+
return false;
|
|
727
|
+
}
|
|
728
|
+
|
|
672
729
|
const cachedLastVote = this.lastActions[signalType];
|
|
673
730
|
this.lastActions[signalType] = slotNumber;
|
|
674
731
|
const action = signalType;
|
|
@@ -688,7 +745,7 @@ export class SequencerPublisher {
|
|
|
688
745
|
});
|
|
689
746
|
|
|
690
747
|
try {
|
|
691
|
-
await this.l1TxUtils.simulate(request, { time: timestamp }, [], ErrorsAbi);
|
|
748
|
+
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
692
749
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
693
750
|
} catch (err) {
|
|
694
751
|
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, err);
|
|
@@ -891,19 +948,20 @@ export class SequencerPublisher {
|
|
|
891
948
|
checkpoint: Checkpoint,
|
|
892
949
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
893
950
|
attestationsAndSignersSignature: Signature,
|
|
894
|
-
opts: { txTimeoutAt?: Date;
|
|
951
|
+
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
895
952
|
): Promise<void> {
|
|
896
953
|
const checkpointHeader = checkpoint.header;
|
|
897
954
|
|
|
898
955
|
const blobFields = checkpoint.toBlobFields();
|
|
899
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
956
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
900
957
|
|
|
901
|
-
const proposeTxArgs = {
|
|
958
|
+
const proposeTxArgs: L1ProcessArgs = {
|
|
902
959
|
header: checkpointHeader,
|
|
903
960
|
archive: checkpoint.archive.root.toBuffer(),
|
|
904
961
|
blobs,
|
|
905
962
|
attestationsAndSigners,
|
|
906
963
|
attestationsAndSignersSignature,
|
|
964
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
907
965
|
};
|
|
908
966
|
|
|
909
967
|
let ts: bigint;
|
|
@@ -924,7 +982,7 @@ export class SequencerPublisher {
|
|
|
924
982
|
this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
925
983
|
...checkpoint.getStats(),
|
|
926
984
|
slotNumber: checkpoint.header.slotNumber,
|
|
927
|
-
|
|
985
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
928
986
|
});
|
|
929
987
|
throw err;
|
|
930
988
|
}
|
|
@@ -933,7 +991,10 @@ export class SequencerPublisher {
|
|
|
933
991
|
await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
|
|
934
992
|
}
|
|
935
993
|
|
|
936
|
-
public
|
|
994
|
+
public enqueueInvalidateCheckpoint(
|
|
995
|
+
request: InvalidateCheckpointRequest | undefined,
|
|
996
|
+
opts: { txTimeoutAt?: Date } = {},
|
|
997
|
+
) {
|
|
937
998
|
if (!request) {
|
|
938
999
|
return;
|
|
939
1000
|
}
|
|
@@ -941,9 +1002,9 @@ export class SequencerPublisher {
|
|
|
941
1002
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
942
1003
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(request.gasUsed) * 64) / 63)));
|
|
943
1004
|
|
|
944
|
-
const { gasUsed,
|
|
945
|
-
const logData = { gasUsed,
|
|
946
|
-
this.log.verbose(`Enqueuing invalidate
|
|
1005
|
+
const { gasUsed, checkpointNumber } = request;
|
|
1006
|
+
const logData = { gasUsed, checkpointNumber, gasLimit, opts };
|
|
1007
|
+
this.log.verbose(`Enqueuing invalidate checkpoint request`, logData);
|
|
947
1008
|
this.addRequest({
|
|
948
1009
|
action: `invalidate-by-${request.reason}`,
|
|
949
1010
|
request: request.request,
|
|
@@ -956,9 +1017,9 @@ export class SequencerPublisher {
|
|
|
956
1017
|
result.receipt.status === 'success' &&
|
|
957
1018
|
tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointInvalidated');
|
|
958
1019
|
if (!success) {
|
|
959
|
-
this.log.warn(`Invalidate
|
|
1020
|
+
this.log.warn(`Invalidate checkpoint ${request.checkpointNumber} failed`, { ...result, ...logData });
|
|
960
1021
|
} else {
|
|
961
|
-
this.log.info(`Invalidate
|
|
1022
|
+
this.log.info(`Invalidate checkpoint ${request.checkpointNumber} succeeded`, { ...result, ...logData });
|
|
962
1023
|
}
|
|
963
1024
|
return !!success;
|
|
964
1025
|
},
|
|
@@ -984,12 +1045,14 @@ export class SequencerPublisher {
|
|
|
984
1045
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
985
1046
|
|
|
986
1047
|
let gasUsed: bigint;
|
|
1048
|
+
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
987
1049
|
try {
|
|
988
|
-
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [],
|
|
1050
|
+
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi)); // TODO(palla/slash): Check the timestamp logic
|
|
989
1051
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
990
1052
|
} catch (err) {
|
|
991
|
-
const viemError = formatViemError(err);
|
|
1053
|
+
const viemError = formatViemError(err, simulateAbi);
|
|
992
1054
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1055
|
+
|
|
993
1056
|
return false;
|
|
994
1057
|
}
|
|
995
1058
|
|
|
@@ -997,10 +1060,14 @@ export class SequencerPublisher {
|
|
|
997
1060
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(gasUsed) * 64) / 63)));
|
|
998
1061
|
logData.gasLimit = gasLimit;
|
|
999
1062
|
|
|
1063
|
+
// Store the ABI used for simulation on the request so Multicall3.forward can decode errors
|
|
1064
|
+
// when the tx is sent and a revert is diagnosed via simulation.
|
|
1065
|
+
const requestWithAbi = { ...request, abi: simulateAbi };
|
|
1066
|
+
|
|
1000
1067
|
this.log.debug(`Enqueuing ${action}`, logData);
|
|
1001
1068
|
this.addRequest({
|
|
1002
1069
|
action,
|
|
1003
|
-
request,
|
|
1070
|
+
request: requestWithAbi,
|
|
1004
1071
|
gasConfig: { gasLimit },
|
|
1005
1072
|
lastValidL2Slot: slotNumber,
|
|
1006
1073
|
checkSuccess: (_req, result) => {
|
|
@@ -1037,7 +1104,7 @@ export class SequencerPublisher {
|
|
|
1037
1104
|
private async prepareProposeTx(
|
|
1038
1105
|
encodedData: L1ProcessArgs,
|
|
1039
1106
|
timestamp: bigint,
|
|
1040
|
-
options: {
|
|
1107
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1041
1108
|
) {
|
|
1042
1109
|
const kzg = Blob.getViemKzgInstance();
|
|
1043
1110
|
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
@@ -1082,8 +1149,7 @@ export class SequencerPublisher {
|
|
|
1082
1149
|
header: encodedData.header.toViem(),
|
|
1083
1150
|
archive: toHex(encodedData.archive),
|
|
1084
1151
|
oracleInput: {
|
|
1085
|
-
|
|
1086
|
-
feeAssetPriceModifier: 0n,
|
|
1152
|
+
feeAssetPriceModifier: encodedData.feeAssetPriceModifier,
|
|
1087
1153
|
},
|
|
1088
1154
|
},
|
|
1089
1155
|
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
@@ -1109,7 +1175,7 @@ export class SequencerPublisher {
|
|
|
1109
1175
|
readonly header: ViemHeader;
|
|
1110
1176
|
readonly archive: `0x${string}`;
|
|
1111
1177
|
readonly oracleInput: {
|
|
1112
|
-
readonly feeAssetPriceModifier:
|
|
1178
|
+
readonly feeAssetPriceModifier: bigint;
|
|
1113
1179
|
};
|
|
1114
1180
|
},
|
|
1115
1181
|
ViemCommitteeAttestations,
|
|
@@ -1118,7 +1184,7 @@ export class SequencerPublisher {
|
|
|
1118
1184
|
`0x${string}`,
|
|
1119
1185
|
],
|
|
1120
1186
|
timestamp: bigint,
|
|
1121
|
-
options: {
|
|
1187
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1122
1188
|
) {
|
|
1123
1189
|
const rollupData = encodeFunctionData({
|
|
1124
1190
|
abi: RollupAbi,
|
|
@@ -1127,13 +1193,9 @@ export class SequencerPublisher {
|
|
|
1127
1193
|
});
|
|
1128
1194
|
|
|
1129
1195
|
// override the pending checkpoint number if requested
|
|
1130
|
-
const optsForcePendingCheckpointNumber =
|
|
1131
|
-
options.forcePendingBlockNumber !== undefined
|
|
1132
|
-
? CheckpointNumber.fromBlockNumber(options.forcePendingBlockNumber)
|
|
1133
|
-
: undefined;
|
|
1134
1196
|
const forcePendingCheckpointNumberStateDiff = (
|
|
1135
|
-
|
|
1136
|
-
? await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
1197
|
+
options.forcePendingCheckpointNumber !== undefined
|
|
1198
|
+
? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber)
|
|
1137
1199
|
: []
|
|
1138
1200
|
).flatMap(override => override.stateDiff ?? []);
|
|
1139
1201
|
|
|
@@ -1160,20 +1222,20 @@ export class SequencerPublisher {
|
|
|
1160
1222
|
{
|
|
1161
1223
|
to: this.rollupContract.address,
|
|
1162
1224
|
data: rollupData,
|
|
1163
|
-
gas:
|
|
1225
|
+
gas: MAX_L1_TX_LIMIT,
|
|
1164
1226
|
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1165
1227
|
},
|
|
1166
1228
|
{
|
|
1167
1229
|
// @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
|
|
1168
1230
|
time: timestamp + 1n,
|
|
1169
1231
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1170
|
-
gasLimit:
|
|
1232
|
+
gasLimit: MAX_L1_TX_LIMIT * 2n,
|
|
1171
1233
|
},
|
|
1172
1234
|
stateOverrides,
|
|
1173
1235
|
RollupAbi,
|
|
1174
1236
|
{
|
|
1175
1237
|
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
1176
|
-
fallbackGasEstimate:
|
|
1238
|
+
fallbackGasEstimate: MAX_L1_TX_LIMIT,
|
|
1177
1239
|
},
|
|
1178
1240
|
)
|
|
1179
1241
|
.catch(err => {
|
|
@@ -1183,7 +1245,7 @@ export class SequencerPublisher {
|
|
|
1183
1245
|
this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
|
|
1184
1246
|
// Return a minimal simulation result with the fallback gas estimate
|
|
1185
1247
|
return {
|
|
1186
|
-
gasUsed:
|
|
1248
|
+
gasUsed: MAX_L1_TX_LIMIT,
|
|
1187
1249
|
logs: [],
|
|
1188
1250
|
};
|
|
1189
1251
|
}
|
|
@@ -1197,7 +1259,7 @@ export class SequencerPublisher {
|
|
|
1197
1259
|
private async addProposeTx(
|
|
1198
1260
|
checkpoint: Checkpoint,
|
|
1199
1261
|
encodedData: L1ProcessArgs,
|
|
1200
|
-
opts: { txTimeoutAt?: Date;
|
|
1262
|
+
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
1201
1263
|
timestamp: bigint,
|
|
1202
1264
|
): Promise<void> {
|
|
1203
1265
|
const slot = checkpoint.header.slotNumber;
|