@aztec/sequencer-client 0.0.1-commit.f504929 → 0.0.1-commit.f5d02921e
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 +4 -1
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +46 -23
- package/dest/config.d.ts +25 -5
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +21 -12
- package/dest/global_variable_builder/global_builder.d.ts +15 -9
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +29 -25
- 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 +13 -1
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +17 -2
- package/dest/publisher/sequencer-publisher-factory.d.ts +3 -3
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +2 -2
- package/dest/publisher/sequencer-publisher.d.ts +52 -25
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +98 -42
- package/dest/sequencer/checkpoint_proposal_job.d.ts +33 -8
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +284 -158
- 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/events.d.ts +2 -1
- package/dest/sequencer/events.d.ts.map +1 -1
- package/dest/sequencer/metrics.d.ts +5 -1
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +11 -0
- package/dest/sequencer/sequencer.d.ts +23 -10
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +123 -68
- package/dest/sequencer/timetable.d.ts +4 -3
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +6 -7
- package/dest/sequencer/types.d.ts +2 -2
- package/dest/sequencer/types.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts +7 -9
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +39 -30
- package/package.json +27 -28
- package/src/client/sequencer-client.ts +56 -21
- package/src/config.ts +28 -14
- package/src/global_variable_builder/global_builder.ts +37 -26
- package/src/global_variable_builder/index.ts +1 -1
- package/src/publisher/config.ts +32 -0
- package/src/publisher/sequencer-publisher-factory.ts +3 -3
- package/src/publisher/sequencer-publisher.ts +144 -54
- package/src/sequencer/checkpoint_proposal_job.ts +367 -175
- package/src/sequencer/checkpoint_voter.ts +1 -12
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/metrics.ts +14 -0
- package/src/sequencer/sequencer.ts +178 -79
- package/src/sequencer/timetable.ts +7 -7
- package/src/sequencer/types.ts +1 -1
- package/src/test/mock_checkpoint_builder.ts +51 -48
package/src/publisher/config.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { type L1TxUtilsConfig, l1TxUtilsConfigMappings } from '@aztec/ethereum/l
|
|
|
4
4
|
import { type ConfigMappingsType, SecretValue, booleanConfigHelper } from '@aztec/foundation/config';
|
|
5
5
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
6
6
|
|
|
7
|
+
import { parseEther } from 'viem';
|
|
8
|
+
|
|
7
9
|
/** Configuration of the transaction publisher. */
|
|
8
10
|
export type TxSenderConfig = L1ReaderConfig & {
|
|
9
11
|
/** The private key to be used by the publisher. */
|
|
@@ -50,13 +52,37 @@ export type PublisherConfig = L1TxUtilsConfig &
|
|
|
50
52
|
publisherForwarderAddress?: EthAddress;
|
|
51
53
|
/** Store for failed L1 transaction inputs (test networks only). Format: gs://bucket/path */
|
|
52
54
|
l1TxFailedStore?: string;
|
|
55
|
+
/** Min ETH balance below which a publisher gets funded. Undefined = funding disabled. */
|
|
56
|
+
publisherFundingThreshold?: bigint;
|
|
57
|
+
/** Amount of ETH to send when funding a publisher. Undefined = funding disabled. */
|
|
58
|
+
publisherFundingAmount?: bigint;
|
|
53
59
|
};
|
|
54
60
|
|
|
61
|
+
/** Shared config mappings for publisher funding, used by both sequencer and prover publisher configs. */
|
|
62
|
+
const publisherFundingConfigMappings = {
|
|
63
|
+
publisherFundingThreshold: {
|
|
64
|
+
env: 'PUBLISHER_FUNDING_THRESHOLD' as const,
|
|
65
|
+
description:
|
|
66
|
+
'Min ETH balance below which a publisher gets funded. Specified in ether (e.g. 0.1). Unset = funding disabled.',
|
|
67
|
+
parseEnv: (val: string) => parseEther(val),
|
|
68
|
+
},
|
|
69
|
+
publisherFundingAmount: {
|
|
70
|
+
env: 'PUBLISHER_FUNDING_AMOUNT' as const,
|
|
71
|
+
description:
|
|
72
|
+
'Amount of ETH to send when funding a publisher. Specified in ether (e.g. 0.5). Unset = funding disabled.',
|
|
73
|
+
parseEnv: (val: string) => parseEther(val),
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
55
77
|
export type ProverPublisherConfig = L1TxUtilsConfig &
|
|
56
78
|
BlobClientConfig & {
|
|
57
79
|
fishermanMode?: boolean;
|
|
58
80
|
proverPublisherAllowInvalidStates?: boolean;
|
|
59
81
|
proverPublisherForwarderAddress?: EthAddress;
|
|
82
|
+
/** Min ETH balance below which a publisher gets funded. Undefined = funding disabled. */
|
|
83
|
+
publisherFundingThreshold?: bigint;
|
|
84
|
+
/** Amount of ETH to send when funding a publisher. Undefined = funding disabled. */
|
|
85
|
+
publisherFundingAmount?: bigint;
|
|
60
86
|
};
|
|
61
87
|
|
|
62
88
|
export type SequencerPublisherConfig = L1TxUtilsConfig &
|
|
@@ -66,6 +92,10 @@ export type SequencerPublisherConfig = L1TxUtilsConfig &
|
|
|
66
92
|
sequencerPublisherForwarderAddress?: EthAddress;
|
|
67
93
|
/** Store for failed L1 transaction inputs (test networks only). Format: gs://bucket/path */
|
|
68
94
|
l1TxFailedStore?: string;
|
|
95
|
+
/** Min ETH balance below which a publisher gets funded. Undefined = funding disabled. */
|
|
96
|
+
publisherFundingThreshold?: bigint;
|
|
97
|
+
/** Amount of ETH to send when funding a publisher. Undefined = funding disabled. */
|
|
98
|
+
publisherFundingAmount?: bigint;
|
|
69
99
|
};
|
|
70
100
|
|
|
71
101
|
export function getPublisherConfigFromProverConfig(config: ProverPublisherConfig): PublisherConfig {
|
|
@@ -142,6 +172,7 @@ export const sequencerPublisherConfigMappings: ConfigMappingsType<SequencerPubli
|
|
|
142
172
|
env: 'L1_TX_FAILED_STORE',
|
|
143
173
|
description: 'Store for failed L1 transaction inputs (test networks only). Format: gs://bucket/path',
|
|
144
174
|
},
|
|
175
|
+
...publisherFundingConfigMappings,
|
|
145
176
|
};
|
|
146
177
|
|
|
147
178
|
export const proverPublisherConfigMappings: ConfigMappingsType<ProverPublisherConfig & L1TxUtilsConfig> = {
|
|
@@ -163,4 +194,5 @@ export const proverPublisherConfigMappings: ConfigMappingsType<ProverPublisherCo
|
|
|
163
194
|
description: 'Address of the forwarder contract to wrap all L1 transactions through (for testing purposes only)',
|
|
164
195
|
parseEnv: (val: string) => (val ? EthAddress.fromString(val) : undefined),
|
|
165
196
|
},
|
|
197
|
+
...publisherFundingConfigMappings,
|
|
166
198
|
};
|
|
@@ -117,8 +117,8 @@ export class SequencerPublisherFactory {
|
|
|
117
117
|
};
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
/**
|
|
121
|
-
public
|
|
122
|
-
this.deps.publisherManager.
|
|
120
|
+
/** Stops all publishers managed by this factory. Used during sequencer shutdown. */
|
|
121
|
+
public async stopAll(): Promise<void> {
|
|
122
|
+
await this.deps.publisherManager.stop();
|
|
123
123
|
}
|
|
124
124
|
}
|
|
@@ -5,6 +5,7 @@ import type { L1ContractsConfig } from '@aztec/ethereum/config';
|
|
|
5
5
|
import {
|
|
6
6
|
type EmpireSlashingProposerContract,
|
|
7
7
|
FeeAssetPriceOracle,
|
|
8
|
+
type FeeHeader,
|
|
8
9
|
type GovernanceProposerContract,
|
|
9
10
|
type IEmpireBase,
|
|
10
11
|
MULTI_CALL_3_ADDRESS,
|
|
@@ -28,6 +29,7 @@ import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from
|
|
|
28
29
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
29
30
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
30
31
|
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
32
|
+
import { trimmedBytesLength } from '@aztec/foundation/buffer';
|
|
31
33
|
import { pick } from '@aztec/foundation/collection';
|
|
32
34
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
33
35
|
import { TimeoutError } from '@aztec/foundation/error';
|
|
@@ -35,12 +37,14 @@ import { EthAddress } from '@aztec/foundation/eth-address';
|
|
|
35
37
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
36
38
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
37
39
|
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
40
|
+
import { InterruptibleSleep } from '@aztec/foundation/sleep';
|
|
38
41
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
39
|
-
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
42
|
+
import { type DateProvider, Timer } from '@aztec/foundation/timer';
|
|
40
43
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
41
44
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
42
45
|
import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
43
46
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
47
|
+
import { getLastL1SlotTimestampForL2Slot, getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
44
48
|
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
45
49
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
46
50
|
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
@@ -61,6 +65,20 @@ import type { SequencerPublisherConfig } from './config.js';
|
|
|
61
65
|
import { type FailedL1Tx, type L1TxFailedStore, createL1TxFailedStore } from './l1_tx_failed_store/index.js';
|
|
62
66
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
63
67
|
|
|
68
|
+
/** Result of a sendRequests call, returned by both sendRequests() and sendRequestsAt(). */
|
|
69
|
+
export type SendRequestsResult = {
|
|
70
|
+
/** The L1 transaction receipt or error from the bundled multicall. */
|
|
71
|
+
result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError;
|
|
72
|
+
/** Actions that expired (past their deadline) before the request was sent. */
|
|
73
|
+
expiredActions: Action[];
|
|
74
|
+
/** Actions that were included in the sent L1 transaction. */
|
|
75
|
+
sentActions: Action[];
|
|
76
|
+
/** Actions whose L1 simulation succeeded (subset of sentActions). */
|
|
77
|
+
successfulActions: Action[];
|
|
78
|
+
/** Actions whose L1 simulation failed (subset of sentActions). */
|
|
79
|
+
failedActions: Action[];
|
|
80
|
+
};
|
|
81
|
+
|
|
64
82
|
/** Arguments to the process method of the rollup contract */
|
|
65
83
|
type L1ProcessArgs = {
|
|
66
84
|
/** The L2 block header. */
|
|
@@ -102,6 +120,8 @@ export type InvalidateCheckpointRequest = {
|
|
|
102
120
|
gasUsed: bigint;
|
|
103
121
|
checkpointNumber: CheckpointNumber;
|
|
104
122
|
forcePendingCheckpointNumber: CheckpointNumber;
|
|
123
|
+
/** Archive at the rollback target checkpoint (checkpoint N-1). */
|
|
124
|
+
lastArchive: Fr;
|
|
105
125
|
};
|
|
106
126
|
|
|
107
127
|
interface RequestWithExpiry {
|
|
@@ -132,6 +152,10 @@ export class SequencerPublisher {
|
|
|
132
152
|
|
|
133
153
|
protected log: Logger;
|
|
134
154
|
protected ethereumSlotDuration: bigint;
|
|
155
|
+
protected aztecSlotDuration: bigint;
|
|
156
|
+
|
|
157
|
+
/** Date provider for wall-clock time. */
|
|
158
|
+
private readonly dateProvider: DateProvider;
|
|
135
159
|
|
|
136
160
|
private blobClient: BlobClientInterface;
|
|
137
161
|
|
|
@@ -147,6 +171,9 @@ export class SequencerPublisher {
|
|
|
147
171
|
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */
|
|
148
172
|
private feeAssetPriceOracle: FeeAssetPriceOracle;
|
|
149
173
|
|
|
174
|
+
/** Interruptible sleep used by sendRequestsAt to wait until a target timestamp. */
|
|
175
|
+
private readonly interruptibleSleep = new InterruptibleSleep();
|
|
176
|
+
|
|
150
177
|
// A CALL to a cold address is 2700 gas
|
|
151
178
|
public static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
152
179
|
|
|
@@ -165,7 +192,7 @@ export class SequencerPublisher {
|
|
|
165
192
|
|
|
166
193
|
constructor(
|
|
167
194
|
private config: Pick<SequencerPublisherConfig, 'fishermanMode' | 'l1TxFailedStore'> &
|
|
168
|
-
Pick<L1ContractsConfig, 'ethereumSlotDuration'> & { l1ChainId: number },
|
|
195
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration'> & { l1ChainId: number },
|
|
169
196
|
deps: {
|
|
170
197
|
telemetry?: TelemetryClient;
|
|
171
198
|
blobClient: BlobClientInterface;
|
|
@@ -184,10 +211,13 @@ export class SequencerPublisher {
|
|
|
184
211
|
) {
|
|
185
212
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
186
213
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
214
|
+
this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
|
|
215
|
+
this.dateProvider = deps.dateProvider;
|
|
187
216
|
this.epochCache = deps.epochCache;
|
|
188
217
|
this.lastActions = deps.lastActions;
|
|
189
218
|
|
|
190
219
|
this.blobClient = deps.blobClient;
|
|
220
|
+
this.dateProvider = deps.dateProvider;
|
|
191
221
|
|
|
192
222
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
193
223
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
@@ -285,7 +315,7 @@ export class SequencerPublisher {
|
|
|
285
315
|
}
|
|
286
316
|
|
|
287
317
|
public getCurrentL2Slot(): SlotNumber {
|
|
288
|
-
return this.epochCache.
|
|
318
|
+
return this.epochCache.getSlotNow();
|
|
289
319
|
}
|
|
290
320
|
|
|
291
321
|
/**
|
|
@@ -363,9 +393,10 @@ export class SequencerPublisher {
|
|
|
363
393
|
* - undefined if no valid requests are found OR the tx failed to send.
|
|
364
394
|
*/
|
|
365
395
|
@trackSpan('SequencerPublisher.sendRequests')
|
|
366
|
-
public async sendRequests() {
|
|
396
|
+
public async sendRequests(): Promise<SendRequestsResult | undefined> {
|
|
367
397
|
const requestsToProcess = [...this.requests];
|
|
368
398
|
this.requests = [];
|
|
399
|
+
|
|
369
400
|
if (this.interrupted || requestsToProcess.length === 0) {
|
|
370
401
|
return undefined;
|
|
371
402
|
}
|
|
@@ -398,8 +429,8 @@ export class SequencerPublisher {
|
|
|
398
429
|
// @note - we can only have one blob config per bundle
|
|
399
430
|
// find requests with gas and blob configs
|
|
400
431
|
// See https://github.com/AztecProtocol/aztec-packages/issues/11513
|
|
401
|
-
const gasConfigs =
|
|
402
|
-
const blobConfigs =
|
|
432
|
+
const gasConfigs = validRequests.filter(request => request.gasConfig).map(request => request.gasConfig);
|
|
433
|
+
const blobConfigs = validRequests.filter(request => request.blobConfig).map(request => request.blobConfig);
|
|
403
434
|
|
|
404
435
|
if (blobConfigs.length > 1) {
|
|
405
436
|
throw new Error('Multiple blob configs found');
|
|
@@ -524,6 +555,23 @@ export class SequencerPublisher {
|
|
|
524
555
|
}
|
|
525
556
|
}
|
|
526
557
|
|
|
558
|
+
/*
|
|
559
|
+
* Schedules sending all enqueued requests at (or after) the given timestamp.
|
|
560
|
+
* Uses InterruptibleSleep so it can be cancelled via interrupt().
|
|
561
|
+
* Returns the promise for the L1 response (caller should NOT await this in the work loop).
|
|
562
|
+
*/
|
|
563
|
+
public async sendRequestsAt(submitAfter: Date): Promise<SendRequestsResult | undefined> {
|
|
564
|
+
const ms = submitAfter.getTime() - this.dateProvider.now();
|
|
565
|
+
if (ms > 0) {
|
|
566
|
+
this.log.debug(`Sleeping ${ms}ms before sending requests`, { submitAfter });
|
|
567
|
+
await this.interruptibleSleep.sleep(ms);
|
|
568
|
+
}
|
|
569
|
+
if (this.interrupted) {
|
|
570
|
+
return undefined;
|
|
571
|
+
}
|
|
572
|
+
return this.sendRequests();
|
|
573
|
+
}
|
|
574
|
+
|
|
527
575
|
private callbackBundledTransactions(
|
|
528
576
|
requests: RequestWithExpiry[],
|
|
529
577
|
result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError | undefined,
|
|
@@ -547,7 +595,16 @@ export class SequencerPublisher {
|
|
|
547
595
|
});
|
|
548
596
|
return { failedActions: requests.map(r => r.action) };
|
|
549
597
|
} else {
|
|
550
|
-
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
598
|
+
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
599
|
+
result,
|
|
600
|
+
requests: requests.map(r => ({
|
|
601
|
+
...r,
|
|
602
|
+
// Avoid logging large blob data
|
|
603
|
+
blobConfig: r.blobConfig
|
|
604
|
+
? { ...r.blobConfig, blobs: r.blobConfig.blobs.map(b => ({ size: trimmedBytesLength(b) })) }
|
|
605
|
+
: undefined,
|
|
606
|
+
})),
|
|
607
|
+
});
|
|
551
608
|
const successfulActions: Action[] = [];
|
|
552
609
|
const failedActions: Action[] = [];
|
|
553
610
|
for (const request of requests) {
|
|
@@ -586,21 +643,30 @@ export class SequencerPublisher {
|
|
|
586
643
|
}
|
|
587
644
|
|
|
588
645
|
/**
|
|
589
|
-
* @notice Will call `
|
|
646
|
+
* @notice Will call `canProposeAt` to make sure that it is possible to propose
|
|
590
647
|
* @param tipArchive - The archive to check
|
|
591
648
|
* @returns The slot and block number if it is possible to propose, undefined otherwise
|
|
592
649
|
*/
|
|
593
|
-
public
|
|
650
|
+
public canProposeAt(
|
|
594
651
|
tipArchive: Fr,
|
|
595
652
|
msgSender: EthAddress,
|
|
596
|
-
opts: {
|
|
653
|
+
opts: {
|
|
654
|
+
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
655
|
+
forceArchive?: { checkpointNumber: CheckpointNumber; archive: Fr };
|
|
656
|
+
pipelined?: boolean;
|
|
657
|
+
} = {},
|
|
597
658
|
) {
|
|
598
659
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
599
660
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
600
661
|
|
|
662
|
+
const pipelined = opts.pipelined ?? this.epochCache.isProposerPipeliningEnabled();
|
|
663
|
+
const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
|
|
664
|
+
const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
|
|
665
|
+
|
|
601
666
|
return this.rollupContract
|
|
602
|
-
.
|
|
667
|
+
.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
|
|
603
668
|
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
669
|
+
forceArchive: opts.forceArchive,
|
|
604
670
|
})
|
|
605
671
|
.catch(err => {
|
|
606
672
|
if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
|
|
@@ -613,6 +679,7 @@ export class SequencerPublisher {
|
|
|
613
679
|
return undefined;
|
|
614
680
|
});
|
|
615
681
|
}
|
|
682
|
+
|
|
616
683
|
/**
|
|
617
684
|
* @notice Will simulate `validateHeader` to make sure that the block header is valid
|
|
618
685
|
* @dev This is a convenience function that can be used by the sequencer to validate a "partial" header.
|
|
@@ -636,7 +703,7 @@ export class SequencerPublisher {
|
|
|
636
703
|
flags,
|
|
637
704
|
] as const;
|
|
638
705
|
|
|
639
|
-
const ts =
|
|
706
|
+
const ts = this.getSimulationTimestamp(header.slotNumber);
|
|
640
707
|
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
641
708
|
opts?.forcePendingCheckpointNumber,
|
|
642
709
|
);
|
|
@@ -659,7 +726,7 @@ export class SequencerPublisher {
|
|
|
659
726
|
data: encodeFunctionData({ abi: RollupAbi, functionName: 'validateHeaderWithAttestations', args }),
|
|
660
727
|
from: MULTI_CALL_3_ADDRESS,
|
|
661
728
|
},
|
|
662
|
-
{ time: ts
|
|
729
|
+
{ time: ts },
|
|
663
730
|
stateOverrides,
|
|
664
731
|
);
|
|
665
732
|
this.log.debug(`Simulated validateHeader`);
|
|
@@ -712,6 +779,7 @@ export class SequencerPublisher {
|
|
|
712
779
|
gasUsed,
|
|
713
780
|
checkpointNumber,
|
|
714
781
|
forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
|
|
782
|
+
lastArchive: validationResult.checkpoint.lastArchive,
|
|
715
783
|
reason,
|
|
716
784
|
};
|
|
717
785
|
} catch (err) {
|
|
@@ -724,8 +792,8 @@ export class SequencerPublisher {
|
|
|
724
792
|
`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
|
|
725
793
|
{ ...logData, request, error: viemError.message },
|
|
726
794
|
);
|
|
727
|
-
const
|
|
728
|
-
if (
|
|
795
|
+
const latestProposedCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
796
|
+
if (latestProposedCheckpointNumber < checkpointNumber) {
|
|
729
797
|
this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, { ...logData });
|
|
730
798
|
return undefined;
|
|
731
799
|
} else {
|
|
@@ -799,9 +867,11 @@ export class SequencerPublisher {
|
|
|
799
867
|
checkpoint: Checkpoint,
|
|
800
868
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
801
869
|
attestationsAndSignersSignature: Signature,
|
|
802
|
-
options: {
|
|
803
|
-
|
|
804
|
-
|
|
870
|
+
options: {
|
|
871
|
+
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
872
|
+
forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
|
|
873
|
+
},
|
|
874
|
+
): Promise<void> {
|
|
805
875
|
const blobFields = checkpoint.toBlobFields();
|
|
806
876
|
const blobs = await getBlobsPerL1Block(blobFields);
|
|
807
877
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
@@ -820,13 +890,11 @@ export class SequencerPublisher {
|
|
|
820
890
|
blobInput,
|
|
821
891
|
] as const;
|
|
822
892
|
|
|
823
|
-
await this.simulateProposeTx(args,
|
|
824
|
-
return ts;
|
|
893
|
+
await this.simulateProposeTx(args, options);
|
|
825
894
|
}
|
|
826
895
|
|
|
827
896
|
private async enqueueCastSignalHelper(
|
|
828
897
|
slotNumber: SlotNumber,
|
|
829
|
-
timestamp: bigint,
|
|
830
898
|
signalType: GovernanceSignalAction,
|
|
831
899
|
payload: EthAddress,
|
|
832
900
|
base: IEmpireBase,
|
|
@@ -905,13 +973,17 @@ export class SequencerPublisher {
|
|
|
905
973
|
});
|
|
906
974
|
|
|
907
975
|
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
976
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
908
977
|
|
|
909
978
|
try {
|
|
910
979
|
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
911
980
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
912
981
|
} catch (err) {
|
|
913
982
|
const viemError = formatViemError(err);
|
|
914
|
-
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError
|
|
983
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError, {
|
|
984
|
+
simulationTimestamp: timestamp,
|
|
985
|
+
l1BlockNumber,
|
|
986
|
+
});
|
|
915
987
|
this.backupFailedTx({
|
|
916
988
|
id: keccak256(request.data!),
|
|
917
989
|
failureType: 'simulation',
|
|
@@ -974,19 +1046,16 @@ export class SequencerPublisher {
|
|
|
974
1046
|
/**
|
|
975
1047
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
976
1048
|
* @param slotNumber - The slot number to cast a signal for.
|
|
977
|
-
* @param timestamp - The timestamp of the slot to cast a signal for.
|
|
978
1049
|
* @returns True if the signal was successfully enqueued, false otherwise.
|
|
979
1050
|
*/
|
|
980
1051
|
public enqueueGovernanceCastSignal(
|
|
981
1052
|
governancePayload: EthAddress,
|
|
982
1053
|
slotNumber: SlotNumber,
|
|
983
|
-
timestamp: bigint,
|
|
984
1054
|
signerAddress: EthAddress,
|
|
985
1055
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
986
1056
|
): Promise<boolean> {
|
|
987
1057
|
return this.enqueueCastSignalHelper(
|
|
988
1058
|
slotNumber,
|
|
989
|
-
timestamp,
|
|
990
1059
|
'governance-signal',
|
|
991
1060
|
governancePayload,
|
|
992
1061
|
this.govProposerContract,
|
|
@@ -999,7 +1068,6 @@ export class SequencerPublisher {
|
|
|
999
1068
|
public async enqueueSlashingActions(
|
|
1000
1069
|
actions: ProposerSlashAction[],
|
|
1001
1070
|
slotNumber: SlotNumber,
|
|
1002
|
-
timestamp: bigint,
|
|
1003
1071
|
signerAddress: EthAddress,
|
|
1004
1072
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
1005
1073
|
): Promise<boolean> {
|
|
@@ -1020,7 +1088,6 @@ export class SequencerPublisher {
|
|
|
1020
1088
|
});
|
|
1021
1089
|
await this.enqueueCastSignalHelper(
|
|
1022
1090
|
slotNumber,
|
|
1023
|
-
timestamp,
|
|
1024
1091
|
'empire-slashing-signal',
|
|
1025
1092
|
action.payload,
|
|
1026
1093
|
this.slashingProposerContract,
|
|
@@ -1039,7 +1106,6 @@ export class SequencerPublisher {
|
|
|
1039
1106
|
(receipt: TransactionReceipt) =>
|
|
1040
1107
|
!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs),
|
|
1041
1108
|
slotNumber,
|
|
1042
|
-
timestamp,
|
|
1043
1109
|
);
|
|
1044
1110
|
break;
|
|
1045
1111
|
}
|
|
@@ -1057,7 +1123,6 @@ export class SequencerPublisher {
|
|
|
1057
1123
|
request,
|
|
1058
1124
|
(receipt: TransactionReceipt) => !!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs),
|
|
1059
1125
|
slotNumber,
|
|
1060
|
-
timestamp,
|
|
1061
1126
|
);
|
|
1062
1127
|
break;
|
|
1063
1128
|
}
|
|
@@ -1081,7 +1146,6 @@ export class SequencerPublisher {
|
|
|
1081
1146
|
request,
|
|
1082
1147
|
(receipt: TransactionReceipt) => !!tallySlashingProposer.tryExtractVoteCastEvent(receipt.logs),
|
|
1083
1148
|
slotNumber,
|
|
1084
|
-
timestamp,
|
|
1085
1149
|
);
|
|
1086
1150
|
break;
|
|
1087
1151
|
}
|
|
@@ -1103,7 +1167,6 @@ export class SequencerPublisher {
|
|
|
1103
1167
|
request,
|
|
1104
1168
|
(receipt: TransactionReceipt) => !!tallySlashingProposer.tryExtractRoundExecutedEvent(receipt.logs),
|
|
1105
1169
|
slotNumber,
|
|
1106
|
-
timestamp,
|
|
1107
1170
|
);
|
|
1108
1171
|
break;
|
|
1109
1172
|
}
|
|
@@ -1123,7 +1186,11 @@ export class SequencerPublisher {
|
|
|
1123
1186
|
checkpoint: Checkpoint,
|
|
1124
1187
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
1125
1188
|
attestationsAndSignersSignature: Signature,
|
|
1126
|
-
opts: {
|
|
1189
|
+
opts: {
|
|
1190
|
+
txTimeoutAt?: Date;
|
|
1191
|
+
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
1192
|
+
forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
|
|
1193
|
+
} = {},
|
|
1127
1194
|
): Promise<void> {
|
|
1128
1195
|
const checkpointHeader = checkpoint.header;
|
|
1129
1196
|
|
|
@@ -1139,15 +1206,13 @@ export class SequencerPublisher {
|
|
|
1139
1206
|
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
1140
1207
|
};
|
|
1141
1208
|
|
|
1142
|
-
let ts: bigint;
|
|
1143
|
-
|
|
1144
1209
|
try {
|
|
1145
1210
|
// @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
|
|
1146
1211
|
// This means that we can avoid the simulation issues in later checks.
|
|
1147
1212
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
1148
1213
|
// make time consistency checks break.
|
|
1149
1214
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
1150
|
-
|
|
1215
|
+
await this.validateCheckpointForSubmission(
|
|
1151
1216
|
checkpoint,
|
|
1152
1217
|
attestationsAndSigners,
|
|
1153
1218
|
attestationsAndSignersSignature,
|
|
@@ -1163,7 +1228,7 @@ export class SequencerPublisher {
|
|
|
1163
1228
|
}
|
|
1164
1229
|
|
|
1165
1230
|
this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
|
|
1166
|
-
await this.addProposeTx(checkpoint, proposeTxArgs, opts
|
|
1231
|
+
await this.addProposeTx(checkpoint, proposeTxArgs, opts);
|
|
1167
1232
|
}
|
|
1168
1233
|
|
|
1169
1234
|
public enqueueInvalidateCheckpoint(
|
|
@@ -1206,8 +1271,8 @@ export class SequencerPublisher {
|
|
|
1206
1271
|
request: L1TxRequest,
|
|
1207
1272
|
checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
|
|
1208
1273
|
slotNumber: SlotNumber,
|
|
1209
|
-
timestamp: bigint,
|
|
1210
1274
|
) {
|
|
1275
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
1211
1276
|
const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
|
|
1212
1277
|
if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
|
|
1213
1278
|
this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
|
|
@@ -1223,8 +1288,9 @@ export class SequencerPublisher {
|
|
|
1223
1288
|
|
|
1224
1289
|
let gasUsed: bigint;
|
|
1225
1290
|
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
1291
|
+
|
|
1226
1292
|
try {
|
|
1227
|
-
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi));
|
|
1293
|
+
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi));
|
|
1228
1294
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
1229
1295
|
} catch (err) {
|
|
1230
1296
|
const viemError = formatViemError(err, simulateAbi);
|
|
@@ -1282,6 +1348,7 @@ export class SequencerPublisher {
|
|
|
1282
1348
|
*/
|
|
1283
1349
|
public interrupt() {
|
|
1284
1350
|
this.interrupted = true;
|
|
1351
|
+
this.interruptibleSleep.interrupt();
|
|
1285
1352
|
this.l1TxUtils.interrupt();
|
|
1286
1353
|
}
|
|
1287
1354
|
|
|
@@ -1293,7 +1360,6 @@ export class SequencerPublisher {
|
|
|
1293
1360
|
|
|
1294
1361
|
private async prepareProposeTx(
|
|
1295
1362
|
encodedData: L1ProcessArgs,
|
|
1296
|
-
timestamp: bigint,
|
|
1297
1363
|
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1298
1364
|
) {
|
|
1299
1365
|
const kzg = Blob.getViemKzgInstance();
|
|
@@ -1366,7 +1432,7 @@ export class SequencerPublisher {
|
|
|
1366
1432
|
blobInput,
|
|
1367
1433
|
] as const;
|
|
1368
1434
|
|
|
1369
|
-
const { rollupData, simulationResult } = await this.simulateProposeTx(args,
|
|
1435
|
+
const { rollupData, simulationResult } = await this.simulateProposeTx(args, options);
|
|
1370
1436
|
|
|
1371
1437
|
return { args, blobEvaluationGas, rollupData, simulationResult };
|
|
1372
1438
|
}
|
|
@@ -1374,7 +1440,6 @@ export class SequencerPublisher {
|
|
|
1374
1440
|
/**
|
|
1375
1441
|
* Simulates the propose tx with eth_simulateV1
|
|
1376
1442
|
* @param args - The propose tx args
|
|
1377
|
-
* @param timestamp - The timestamp to simulate proposal at
|
|
1378
1443
|
* @returns The simulation result
|
|
1379
1444
|
*/
|
|
1380
1445
|
private async simulateProposeTx(
|
|
@@ -1391,8 +1456,10 @@ export class SequencerPublisher {
|
|
|
1391
1456
|
ViemSignature,
|
|
1392
1457
|
`0x${string}`,
|
|
1393
1458
|
],
|
|
1394
|
-
|
|
1395
|
-
|
|
1459
|
+
options: {
|
|
1460
|
+
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
1461
|
+
forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
|
|
1462
|
+
},
|
|
1396
1463
|
) {
|
|
1397
1464
|
const rollupData = encodeFunctionData({
|
|
1398
1465
|
abi: RollupAbi,
|
|
@@ -1400,13 +1467,23 @@ export class SequencerPublisher {
|
|
|
1400
1467
|
args,
|
|
1401
1468
|
});
|
|
1402
1469
|
|
|
1403
|
-
// override the
|
|
1470
|
+
// override the proposed checkpoint number if requested
|
|
1404
1471
|
const forcePendingCheckpointNumberStateDiff = (
|
|
1405
1472
|
options.forcePendingCheckpointNumber !== undefined
|
|
1406
1473
|
? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber)
|
|
1407
1474
|
: []
|
|
1408
1475
|
).flatMap(override => override.stateDiff ?? []);
|
|
1409
1476
|
|
|
1477
|
+
// override the fee header for a specific checkpoint number if requested (used when pipelining)
|
|
1478
|
+
const forceProposedFeeHeaderStateDiff = (
|
|
1479
|
+
options.forceProposedFeeHeader !== undefined
|
|
1480
|
+
? await this.rollupContract.makeFeeHeaderOverride(
|
|
1481
|
+
options.forceProposedFeeHeader.checkpointNumber,
|
|
1482
|
+
options.forceProposedFeeHeader.feeHeader,
|
|
1483
|
+
)
|
|
1484
|
+
: []
|
|
1485
|
+
).flatMap(override => override.stateDiff ?? []);
|
|
1486
|
+
|
|
1410
1487
|
const stateOverrides: StateOverride = [
|
|
1411
1488
|
{
|
|
1412
1489
|
address: this.rollupContract.address,
|
|
@@ -1414,6 +1491,7 @@ export class SequencerPublisher {
|
|
|
1414
1491
|
stateDiff: [
|
|
1415
1492
|
{ slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
|
|
1416
1493
|
...forcePendingCheckpointNumberStateDiff,
|
|
1494
|
+
...forceProposedFeeHeaderStateDiff,
|
|
1417
1495
|
],
|
|
1418
1496
|
},
|
|
1419
1497
|
];
|
|
@@ -1426,6 +1504,7 @@ export class SequencerPublisher {
|
|
|
1426
1504
|
}
|
|
1427
1505
|
|
|
1428
1506
|
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1507
|
+
const simTs = this.getSimulationTimestamp(SlotNumber.fromBigInt(args[0].header.slotNumber));
|
|
1429
1508
|
|
|
1430
1509
|
const simulationResult = await this.l1TxUtils
|
|
1431
1510
|
.simulate(
|
|
@@ -1436,8 +1515,7 @@ export class SequencerPublisher {
|
|
|
1436
1515
|
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1437
1516
|
},
|
|
1438
1517
|
{
|
|
1439
|
-
|
|
1440
|
-
time: timestamp + 1n,
|
|
1518
|
+
time: simTs,
|
|
1441
1519
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1442
1520
|
gasLimit: MAX_L1_TX_LIMIT * 2n,
|
|
1443
1521
|
},
|
|
@@ -1459,7 +1537,7 @@ export class SequencerPublisher {
|
|
|
1459
1537
|
logs: [],
|
|
1460
1538
|
};
|
|
1461
1539
|
}
|
|
1462
|
-
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1540
|
+
this.log.error(`Failed to simulate propose tx`, viemError, { simulationTimestamp: simTs });
|
|
1463
1541
|
this.backupFailedTx({
|
|
1464
1542
|
id: keccak256(rollupData),
|
|
1465
1543
|
failureType: 'simulation',
|
|
@@ -1481,17 +1559,16 @@ export class SequencerPublisher {
|
|
|
1481
1559
|
private async addProposeTx(
|
|
1482
1560
|
checkpoint: Checkpoint,
|
|
1483
1561
|
encodedData: L1ProcessArgs,
|
|
1484
|
-
opts: {
|
|
1485
|
-
|
|
1562
|
+
opts: {
|
|
1563
|
+
txTimeoutAt?: Date;
|
|
1564
|
+
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
1565
|
+
forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
|
|
1566
|
+
} = {},
|
|
1486
1567
|
): Promise<void> {
|
|
1487
1568
|
const slot = checkpoint.header.slotNumber;
|
|
1488
1569
|
const timer = new Timer();
|
|
1489
1570
|
const kzg = Blob.getViemKzgInstance();
|
|
1490
|
-
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
1491
|
-
encodedData,
|
|
1492
|
-
timestamp,
|
|
1493
|
-
opts,
|
|
1494
|
-
);
|
|
1571
|
+
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, opts);
|
|
1495
1572
|
const startBlock = await this.l1TxUtils.getBlockNumber();
|
|
1496
1573
|
const gasLimit = this.l1TxUtils.bumpGasLimit(
|
|
1497
1574
|
BigInt(Math.ceil((Number(simulationResult.gasUsed) * 64) / 63)) +
|
|
@@ -1567,4 +1644,17 @@ export class SequencerPublisher {
|
|
|
1567
1644
|
},
|
|
1568
1645
|
});
|
|
1569
1646
|
}
|
|
1647
|
+
|
|
1648
|
+
/** Returns the timestamp of the last L1 slot within a given L2 slot. Used as the simulation timestamp
|
|
1649
|
+
* for eth_simulateV1 calls, since it's guaranteed to be greater than any L1 block produced during the slot. */
|
|
1650
|
+
private getSimulationTimestamp(slot: SlotNumber): bigint {
|
|
1651
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1652
|
+
return getLastL1SlotTimestampForL2Slot(slot, l1Constants);
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
/** Returns the timestamp of the next L1 slot boundary after now. */
|
|
1656
|
+
private getNextL1SlotTimestamp(): bigint {
|
|
1657
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1658
|
+
return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
|
|
1659
|
+
}
|
|
1570
1660
|
}
|