@aztec/sequencer-client 0.0.1-commit.934299a21 → 0.0.1-commit.949a33fd8
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 +6 -1
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +49 -27
- package/dest/config.d.ts +25 -5
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +41 -26
- package/dest/global_variable_builder/fee_predictor.d.ts +37 -0
- package/dest/global_variable_builder/fee_predictor.d.ts.map +1 -0
- package/dest/global_variable_builder/fee_predictor.js +128 -0
- package/dest/global_variable_builder/fee_provider.d.ts +21 -0
- package/dest/global_variable_builder/fee_provider.d.ts.map +1 -0
- package/dest/global_variable_builder/fee_provider.js +58 -0
- package/dest/global_variable_builder/global_builder.d.ts +15 -14
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +16 -50
- package/dest/global_variable_builder/index.d.ts +4 -2
- package/dest/global_variable_builder/index.d.ts.map +1 -1
- package/dest/global_variable_builder/index.js +2 -0
- package/dest/publisher/config.d.ts +13 -1
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +19 -4
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +3 -4
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.d.ts +3 -5
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +16 -3
- package/dest/publisher/sequencer-publisher.d.ts +55 -43
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +182 -118
- package/dest/sequencer/chain_state_overrides.d.ts +25 -0
- package/dest/sequencer/chain_state_overrides.d.ts.map +1 -0
- package/dest/sequencer/chain_state_overrides.js +39 -0
- package/dest/sequencer/checkpoint_proposal_job.d.ts +46 -10
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +671 -225
- package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts +34 -0
- package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_proposal_job_metrics.js +72 -0
- 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 +7 -1
- package/dest/sequencer/events.d.ts.map +1 -1
- package/dest/sequencer/metrics.d.ts +13 -10
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +45 -20
- package/dest/sequencer/sequencer.d.ts +38 -13
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +167 -79
- package/dest/sequencer/timetable.d.ts +17 -3
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +51 -43
- 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/dest/test/utils.d.ts +1 -1
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +7 -6
- package/package.json +27 -28
- package/src/client/sequencer-client.ts +61 -28
- package/src/config.ts +49 -27
- package/src/global_variable_builder/README.md +44 -0
- package/src/global_variable_builder/fee_predictor.ts +172 -0
- package/src/global_variable_builder/fee_provider.ts +75 -0
- package/src/global_variable_builder/global_builder.ts +26 -62
- package/src/global_variable_builder/index.ts +3 -1
- package/src/publisher/config.ts +38 -4
- package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +3 -1
- package/src/publisher/sequencer-publisher-factory.ts +18 -6
- package/src/publisher/sequencer-publisher.ts +265 -171
- package/src/sequencer/README.md +82 -13
- package/src/sequencer/chain_state_overrides.ts +87 -0
- package/src/sequencer/checkpoint_proposal_job.ts +823 -251
- package/src/sequencer/checkpoint_proposal_job_metrics.ts +128 -0
- package/src/sequencer/checkpoint_voter.ts +1 -12
- package/src/sequencer/events.ts +6 -1
- package/src/sequencer/metrics.ts +57 -24
- package/src/sequencer/sequencer.ts +233 -88
- package/src/sequencer/timetable.ts +64 -52
- package/src/sequencer/types.ts +1 -1
- package/src/test/mock_checkpoint_builder.ts +51 -48
- package/src/test/utils.ts +28 -10
|
@@ -3,16 +3,18 @@ import { Blob, getBlobsPerL1Block, getPrefixedEthBlobCommitments } from '@aztec/
|
|
|
3
3
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
4
4
|
import type { L1ContractsConfig } from '@aztec/ethereum/config';
|
|
5
5
|
import {
|
|
6
|
-
type EmpireSlashingProposerContract,
|
|
7
6
|
FeeAssetPriceOracle,
|
|
8
7
|
type GovernanceProposerContract,
|
|
9
8
|
type IEmpireBase,
|
|
10
9
|
MULTI_CALL_3_ADDRESS,
|
|
11
10
|
Multicall3,
|
|
12
11
|
RollupContract,
|
|
13
|
-
|
|
12
|
+
SimulationOverridesBuilder,
|
|
13
|
+
type SimulationOverridesPlan,
|
|
14
|
+
type SlashingProposerContract,
|
|
14
15
|
type ViemCommitteeAttestations,
|
|
15
16
|
type ViemHeader,
|
|
17
|
+
buildSimulationOverridesStateOverride,
|
|
16
18
|
} from '@aztec/ethereum/contracts';
|
|
17
19
|
import { type L1FeeAnalysisResult, L1FeeAnalyzer } from '@aztec/ethereum/l1-fee-analysis';
|
|
18
20
|
import {
|
|
@@ -26,28 +28,29 @@ import {
|
|
|
26
28
|
} from '@aztec/ethereum/l1-tx-utils';
|
|
27
29
|
import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
28
30
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
29
|
-
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';
|
|
35
|
+
import { TimeoutError } from '@aztec/foundation/error';
|
|
33
36
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
34
37
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
35
38
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
36
39
|
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
40
|
+
import { InterruptibleSleep } from '@aztec/foundation/sleep';
|
|
37
41
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
38
|
-
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
42
|
+
import { type DateProvider, Timer } from '@aztec/foundation/timer';
|
|
39
43
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
40
44
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
41
45
|
import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
42
46
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
43
|
-
import {
|
|
47
|
+
import { getLastL1SlotTimestampForL2Slot, getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
44
48
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
45
49
|
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
46
50
|
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
47
51
|
|
|
48
52
|
import {
|
|
49
53
|
type Hex,
|
|
50
|
-
type StateOverride,
|
|
51
54
|
type TransactionReceipt,
|
|
52
55
|
type TypedDataDefinition,
|
|
53
56
|
encodeFunctionData,
|
|
@@ -60,6 +63,20 @@ import type { SequencerPublisherConfig } from './config.js';
|
|
|
60
63
|
import { type FailedL1Tx, type L1TxFailedStore, createL1TxFailedStore } from './l1_tx_failed_store/index.js';
|
|
61
64
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
62
65
|
|
|
66
|
+
/** Result of a sendRequests call, returned by both sendRequests() and sendRequestsAt(). */
|
|
67
|
+
export type SendRequestsResult = {
|
|
68
|
+
/** The L1 transaction receipt or error from the bundled multicall. */
|
|
69
|
+
result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError;
|
|
70
|
+
/** Actions that expired (past their deadline) before the request was sent. */
|
|
71
|
+
expiredActions: Action[];
|
|
72
|
+
/** Actions that were included in the sent L1 transaction. */
|
|
73
|
+
sentActions: Action[];
|
|
74
|
+
/** Actions whose L1 simulation succeeded (subset of sentActions). */
|
|
75
|
+
successfulActions: Action[];
|
|
76
|
+
/** Actions whose L1 simulation failed (subset of sentActions). */
|
|
77
|
+
failedActions: Action[];
|
|
78
|
+
};
|
|
79
|
+
|
|
63
80
|
/** Arguments to the process method of the rollup contract */
|
|
64
81
|
type L1ProcessArgs = {
|
|
65
82
|
/** The L2 block header. */
|
|
@@ -81,16 +98,13 @@ export const Actions = [
|
|
|
81
98
|
'invalidate-by-insufficient-attestations',
|
|
82
99
|
'propose',
|
|
83
100
|
'governance-signal',
|
|
84
|
-
'empire-slashing-signal',
|
|
85
|
-
'create-empire-payload',
|
|
86
|
-
'execute-empire-payload',
|
|
87
101
|
'vote-offenses',
|
|
88
102
|
'execute-slash',
|
|
89
103
|
] as const;
|
|
90
104
|
|
|
91
105
|
export type Action = (typeof Actions)[number];
|
|
92
106
|
|
|
93
|
-
type GovernanceSignalAction = Extract<Action, 'governance-signal'
|
|
107
|
+
type GovernanceSignalAction = Extract<Action, 'governance-signal'>;
|
|
94
108
|
|
|
95
109
|
// Sorting for actions such that invalidations go before proposals, and proposals go before votes
|
|
96
110
|
export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
|
|
@@ -101,6 +115,13 @@ export type InvalidateCheckpointRequest = {
|
|
|
101
115
|
gasUsed: bigint;
|
|
102
116
|
checkpointNumber: CheckpointNumber;
|
|
103
117
|
forcePendingCheckpointNumber: CheckpointNumber;
|
|
118
|
+
/** Archive at the rollback target checkpoint (checkpoint N-1). */
|
|
119
|
+
lastArchive: Fr;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
type EnqueueProposeCheckpointOpts = {
|
|
123
|
+
txTimeoutAt?: Date;
|
|
124
|
+
simulationOverridesPlan?: SimulationOverridesPlan;
|
|
104
125
|
};
|
|
105
126
|
|
|
106
127
|
interface RequestWithExpiry {
|
|
@@ -109,6 +130,8 @@ interface RequestWithExpiry {
|
|
|
109
130
|
lastValidL2Slot: SlotNumber;
|
|
110
131
|
gasConfig?: Pick<L1TxConfig, 'txTimeoutAt' | 'gasLimit'>;
|
|
111
132
|
blobConfig?: L1BlobInputs;
|
|
133
|
+
/** Optional pre-send validation. If it rejects, the request is discarded. */
|
|
134
|
+
preCheck?: () => Promise<void>;
|
|
112
135
|
checkSuccess: (
|
|
113
136
|
request: L1TxRequest,
|
|
114
137
|
result?: { receipt: TransactionReceipt; stats?: TransactionStats; errorMsg?: string },
|
|
@@ -131,18 +154,28 @@ export class SequencerPublisher {
|
|
|
131
154
|
|
|
132
155
|
protected log: Logger;
|
|
133
156
|
protected ethereumSlotDuration: bigint;
|
|
157
|
+
protected aztecSlotDuration: bigint;
|
|
158
|
+
|
|
159
|
+
/** Date provider for wall-clock time. */
|
|
160
|
+
private readonly dateProvider: DateProvider;
|
|
134
161
|
|
|
135
162
|
private blobClient: BlobClientInterface;
|
|
136
163
|
|
|
137
164
|
/** Address to use for simulations in fisherman mode (actual proposer's address) */
|
|
138
165
|
private proposerAddressForSimulation?: EthAddress;
|
|
139
166
|
|
|
167
|
+
/** Optional callback to obtain a replacement publisher when the current one fails to send. */
|
|
168
|
+
private getNextPublisher?: (excludeAddresses: EthAddress[]) => Promise<L1TxUtils | undefined>;
|
|
169
|
+
|
|
140
170
|
/** L1 fee analyzer for fisherman mode */
|
|
141
171
|
private l1FeeAnalyzer?: L1FeeAnalyzer;
|
|
142
172
|
|
|
143
173
|
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */
|
|
144
174
|
private feeAssetPriceOracle: FeeAssetPriceOracle;
|
|
145
175
|
|
|
176
|
+
/** Interruptible sleep used by sendRequestsAt to wait until a target timestamp. */
|
|
177
|
+
private readonly interruptibleSleep = new InterruptibleSleep();
|
|
178
|
+
|
|
146
179
|
// A CALL to a cold address is 2700 gas
|
|
147
180
|
public static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
148
181
|
|
|
@@ -152,8 +185,7 @@ export class SequencerPublisher {
|
|
|
152
185
|
public l1TxUtils: L1TxUtils;
|
|
153
186
|
public rollupContract: RollupContract;
|
|
154
187
|
public govProposerContract: GovernanceProposerContract;
|
|
155
|
-
public slashingProposerContract:
|
|
156
|
-
public slashFactoryContract: SlashFactoryContract;
|
|
188
|
+
public slashingProposerContract: SlashingProposerContract | undefined;
|
|
157
189
|
|
|
158
190
|
public readonly tracer: Tracer;
|
|
159
191
|
|
|
@@ -161,33 +193,37 @@ export class SequencerPublisher {
|
|
|
161
193
|
|
|
162
194
|
constructor(
|
|
163
195
|
private config: Pick<SequencerPublisherConfig, 'fishermanMode' | 'l1TxFailedStore'> &
|
|
164
|
-
Pick<L1ContractsConfig, 'ethereumSlotDuration'> & { l1ChainId: number },
|
|
196
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration'> & { l1ChainId: number },
|
|
165
197
|
deps: {
|
|
166
198
|
telemetry?: TelemetryClient;
|
|
167
199
|
blobClient: BlobClientInterface;
|
|
168
200
|
l1TxUtils: L1TxUtils;
|
|
169
201
|
rollupContract: RollupContract;
|
|
170
|
-
slashingProposerContract:
|
|
202
|
+
slashingProposerContract: SlashingProposerContract | undefined;
|
|
171
203
|
governanceProposerContract: GovernanceProposerContract;
|
|
172
|
-
slashFactoryContract: SlashFactoryContract;
|
|
173
204
|
epochCache: EpochCache;
|
|
174
205
|
dateProvider: DateProvider;
|
|
175
206
|
metrics: SequencerPublisherMetrics;
|
|
176
207
|
lastActions: Partial<Record<Action, SlotNumber>>;
|
|
177
208
|
log?: Logger;
|
|
209
|
+
getNextPublisher?: (excludeAddresses: EthAddress[]) => Promise<L1TxUtils | undefined>;
|
|
178
210
|
},
|
|
179
211
|
) {
|
|
180
212
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
181
213
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
214
|
+
this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
|
|
215
|
+
this.dateProvider = deps.dateProvider;
|
|
182
216
|
this.epochCache = deps.epochCache;
|
|
183
217
|
this.lastActions = deps.lastActions;
|
|
184
218
|
|
|
185
219
|
this.blobClient = deps.blobClient;
|
|
220
|
+
this.dateProvider = deps.dateProvider;
|
|
186
221
|
|
|
187
222
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
188
223
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
189
224
|
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
190
225
|
this.l1TxUtils = deps.l1TxUtils;
|
|
226
|
+
this.getNextPublisher = deps.getNextPublisher;
|
|
191
227
|
|
|
192
228
|
this.rollupContract = deps.rollupContract;
|
|
193
229
|
|
|
@@ -199,8 +235,6 @@ export class SequencerPublisher {
|
|
|
199
235
|
const newSlashingProposer = await this.rollupContract.getSlashingProposer();
|
|
200
236
|
this.slashingProposerContract = newSlashingProposer;
|
|
201
237
|
});
|
|
202
|
-
this.slashFactoryContract = deps.slashFactoryContract;
|
|
203
|
-
|
|
204
238
|
// Initialize L1 fee analyzer for fisherman mode
|
|
205
239
|
if (config.fishermanMode) {
|
|
206
240
|
this.l1FeeAnalyzer = new L1FeeAnalyzer(
|
|
@@ -279,7 +313,7 @@ export class SequencerPublisher {
|
|
|
279
313
|
}
|
|
280
314
|
|
|
281
315
|
public getCurrentL2Slot(): SlotNumber {
|
|
282
|
-
return this.epochCache.
|
|
316
|
+
return this.epochCache.getSlotNow();
|
|
283
317
|
}
|
|
284
318
|
|
|
285
319
|
/**
|
|
@@ -357,9 +391,10 @@ export class SequencerPublisher {
|
|
|
357
391
|
* - undefined if no valid requests are found OR the tx failed to send.
|
|
358
392
|
*/
|
|
359
393
|
@trackSpan('SequencerPublisher.sendRequests')
|
|
360
|
-
public async sendRequests() {
|
|
394
|
+
public async sendRequests(): Promise<SendRequestsResult | undefined> {
|
|
361
395
|
const requestsToProcess = [...this.requests];
|
|
362
396
|
this.requests = [];
|
|
397
|
+
|
|
363
398
|
if (this.interrupted || requestsToProcess.length === 0) {
|
|
364
399
|
return undefined;
|
|
365
400
|
}
|
|
@@ -392,8 +427,8 @@ export class SequencerPublisher {
|
|
|
392
427
|
// @note - we can only have one blob config per bundle
|
|
393
428
|
// find requests with gas and blob configs
|
|
394
429
|
// See https://github.com/AztecProtocol/aztec-packages/issues/11513
|
|
395
|
-
const gasConfigs =
|
|
396
|
-
const blobConfigs =
|
|
430
|
+
const gasConfigs = validRequests.filter(request => request.gasConfig).map(request => request.gasConfig);
|
|
431
|
+
const blobConfigs = validRequests.filter(request => request.blobConfig).map(request => request.blobConfig);
|
|
397
432
|
|
|
398
433
|
if (blobConfigs.length > 1) {
|
|
399
434
|
throw new Error('Multiple blob configs found');
|
|
@@ -437,19 +472,16 @@ export class SequencerPublisher {
|
|
|
437
472
|
});
|
|
438
473
|
const blobDataHex = blobConfig?.blobs?.map(b => toHex(b)) as Hex[] | undefined;
|
|
439
474
|
|
|
475
|
+
const txContext = { multicallData, blobData: blobDataHex, l1BlockNumber };
|
|
476
|
+
|
|
440
477
|
this.log.debug('Forwarding transactions', {
|
|
441
478
|
validRequests: validRequests.map(request => request.action),
|
|
442
479
|
txConfig,
|
|
443
480
|
});
|
|
444
|
-
const result = await
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
blobConfig,
|
|
449
|
-
this.rollupContract.address,
|
|
450
|
-
this.log,
|
|
451
|
-
);
|
|
452
|
-
const txContext = { multicallData, blobData: blobDataHex, l1BlockNumber };
|
|
481
|
+
const result = await this.forwardWithPublisherRotation(validRequests, txConfig, blobConfig);
|
|
482
|
+
if (result === undefined) {
|
|
483
|
+
return undefined;
|
|
484
|
+
}
|
|
453
485
|
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(
|
|
454
486
|
validRequests,
|
|
455
487
|
result,
|
|
@@ -472,6 +504,94 @@ export class SequencerPublisher {
|
|
|
472
504
|
}
|
|
473
505
|
}
|
|
474
506
|
|
|
507
|
+
/**
|
|
508
|
+
* Forwards transactions via Multicall3, rotating to the next available publisher if a send
|
|
509
|
+
* failure occurs (i.e. the tx never reached the chain).
|
|
510
|
+
* On-chain reverts and simulation errors are returned as-is without rotation.
|
|
511
|
+
*/
|
|
512
|
+
private async forwardWithPublisherRotation(
|
|
513
|
+
validRequests: RequestWithExpiry[],
|
|
514
|
+
txConfig: RequestWithExpiry['gasConfig'],
|
|
515
|
+
blobConfig: L1BlobInputs | undefined,
|
|
516
|
+
) {
|
|
517
|
+
const triedAddresses: EthAddress[] = [];
|
|
518
|
+
let currentPublisher = this.l1TxUtils;
|
|
519
|
+
|
|
520
|
+
while (true) {
|
|
521
|
+
triedAddresses.push(currentPublisher.getSenderAddress());
|
|
522
|
+
try {
|
|
523
|
+
const result = await Multicall3.forward(
|
|
524
|
+
validRequests.map(r => r.request),
|
|
525
|
+
currentPublisher,
|
|
526
|
+
txConfig,
|
|
527
|
+
blobConfig,
|
|
528
|
+
this.rollupContract.address,
|
|
529
|
+
this.log,
|
|
530
|
+
);
|
|
531
|
+
this.l1TxUtils = currentPublisher;
|
|
532
|
+
return result;
|
|
533
|
+
} catch (err) {
|
|
534
|
+
if (err instanceof TimeoutError) {
|
|
535
|
+
throw err;
|
|
536
|
+
}
|
|
537
|
+
const viemError = formatViemError(err);
|
|
538
|
+
if (!this.getNextPublisher) {
|
|
539
|
+
this.log.error('Failed to publish bundled transactions', viemError);
|
|
540
|
+
return undefined;
|
|
541
|
+
}
|
|
542
|
+
this.log.warn(
|
|
543
|
+
`Publisher ${currentPublisher.getSenderAddress()} failed to send, rotating to next publisher`,
|
|
544
|
+
viemError,
|
|
545
|
+
);
|
|
546
|
+
const nextPublisher = await this.getNextPublisher([...triedAddresses]);
|
|
547
|
+
if (!nextPublisher) {
|
|
548
|
+
this.log.error('All available publishers exhausted, failed to publish bundled transactions');
|
|
549
|
+
return undefined;
|
|
550
|
+
}
|
|
551
|
+
currentPublisher = nextPublisher;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/*
|
|
557
|
+
* Schedules sending all enqueued requests at (or after) the given timestamp.
|
|
558
|
+
* Uses InterruptibleSleep so it can be cancelled via interrupt().
|
|
559
|
+
* Returns the promise for the L1 response (caller should NOT await this in the work loop).
|
|
560
|
+
*/
|
|
561
|
+
public async sendRequestsAt(submitAfter: Date): Promise<SendRequestsResult | undefined> {
|
|
562
|
+
const ms = submitAfter.getTime() - this.dateProvider.now();
|
|
563
|
+
if (ms > 0) {
|
|
564
|
+
this.log.debug(`Sleeping ${ms}ms before sending requests`, { submitAfter });
|
|
565
|
+
await this.interruptibleSleep.sleep(ms);
|
|
566
|
+
}
|
|
567
|
+
if (this.interrupted) {
|
|
568
|
+
return undefined;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Re-validate enqueued requests after the sleep (state may have changed, e.g. prune or L1 reorg)
|
|
572
|
+
const validRequests: RequestWithExpiry[] = [];
|
|
573
|
+
for (const request of this.requests) {
|
|
574
|
+
if (!request.preCheck) {
|
|
575
|
+
validRequests.push(request);
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
try {
|
|
580
|
+
await request.preCheck();
|
|
581
|
+
validRequests.push(request);
|
|
582
|
+
} catch (err) {
|
|
583
|
+
this.log.warn(`Pre-send validation failed for ${request.action}, discarding request`, err);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
this.requests = validRequests;
|
|
588
|
+
if (this.requests.length === 0) {
|
|
589
|
+
return undefined;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
return this.sendRequests();
|
|
593
|
+
}
|
|
594
|
+
|
|
475
595
|
private callbackBundledTransactions(
|
|
476
596
|
requests: RequestWithExpiry[],
|
|
477
597
|
result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError | undefined,
|
|
@@ -495,7 +615,16 @@ export class SequencerPublisher {
|
|
|
495
615
|
});
|
|
496
616
|
return { failedActions: requests.map(r => r.action) };
|
|
497
617
|
} else {
|
|
498
|
-
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
618
|
+
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
619
|
+
result,
|
|
620
|
+
requests: requests.map(r => ({
|
|
621
|
+
...r,
|
|
622
|
+
// Avoid logging large blob data
|
|
623
|
+
blobConfig: r.blobConfig
|
|
624
|
+
? { ...r.blobConfig, blobs: r.blobConfig.blobs.map(b => ({ size: trimmedBytesLength(b) })) }
|
|
625
|
+
: undefined,
|
|
626
|
+
})),
|
|
627
|
+
});
|
|
499
628
|
const successfulActions: Action[] = [];
|
|
500
629
|
const failedActions: Action[] = [];
|
|
501
630
|
for (const request of requests) {
|
|
@@ -534,22 +663,25 @@ export class SequencerPublisher {
|
|
|
534
663
|
}
|
|
535
664
|
|
|
536
665
|
/**
|
|
537
|
-
* @notice Will call `
|
|
666
|
+
* @notice Will call `canProposeAt` to make sure that it is possible to propose
|
|
538
667
|
* @param tipArchive - The archive to check
|
|
539
668
|
* @returns The slot and block number if it is possible to propose, undefined otherwise
|
|
540
669
|
*/
|
|
541
|
-
public
|
|
542
|
-
tipArchive: Fr,
|
|
543
|
-
msgSender: EthAddress,
|
|
544
|
-
opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
545
|
-
) {
|
|
670
|
+
public async canProposeAt(tipArchive: Fr, msgSender: EthAddress, simulationOverridesPlan?: SimulationOverridesPlan) {
|
|
546
671
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
547
672
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
548
673
|
|
|
674
|
+
const pipelined = this.epochCache.isProposerPipeliningEnabled();
|
|
675
|
+
const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
|
|
676
|
+
const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
|
|
677
|
+
|
|
549
678
|
return this.rollupContract
|
|
550
|
-
.
|
|
551
|
-
|
|
552
|
-
|
|
679
|
+
.canProposeAt(
|
|
680
|
+
tipArchive.toBuffer(),
|
|
681
|
+
msgSender.toString(),
|
|
682
|
+
nextL1SlotTs,
|
|
683
|
+
await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan),
|
|
684
|
+
)
|
|
553
685
|
.catch(err => {
|
|
554
686
|
if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
|
|
555
687
|
this.log.warn(`Failed canProposeAtTime check with ${ignoredErrors.find(e => err.message.includes(e))}`, {
|
|
@@ -561,6 +693,7 @@ export class SequencerPublisher {
|
|
|
561
693
|
return undefined;
|
|
562
694
|
});
|
|
563
695
|
}
|
|
696
|
+
|
|
564
697
|
/**
|
|
565
698
|
* @notice Will simulate `validateHeader` to make sure that the block header is valid
|
|
566
699
|
* @dev This is a convenience function that can be used by the sequencer to validate a "partial" header.
|
|
@@ -570,13 +703,13 @@ export class SequencerPublisher {
|
|
|
570
703
|
@trackSpan('SequencerPublisher.validateBlockHeader')
|
|
571
704
|
public async validateBlockHeader(
|
|
572
705
|
header: CheckpointHeader,
|
|
573
|
-
|
|
706
|
+
simulationOverridesPlan?: SimulationOverridesPlan,
|
|
574
707
|
): Promise<void> {
|
|
575
708
|
const flags = { ignoreDA: true, ignoreSignatures: true };
|
|
576
709
|
|
|
577
710
|
const args = [
|
|
578
711
|
header.toViem(),
|
|
579
|
-
CommitteeAttestationsAndSigners.
|
|
712
|
+
CommitteeAttestationsAndSigners.packAttestations([]),
|
|
580
713
|
[], // no signers
|
|
581
714
|
Signature.empty().toViemSignature(),
|
|
582
715
|
`0x${'0'.repeat(64)}`, // 32 empty bytes
|
|
@@ -584,10 +717,8 @@ export class SequencerPublisher {
|
|
|
584
717
|
flags,
|
|
585
718
|
] as const;
|
|
586
719
|
|
|
587
|
-
const ts =
|
|
588
|
-
const stateOverrides = await this.rollupContract
|
|
589
|
-
opts?.forcePendingCheckpointNumber,
|
|
590
|
-
);
|
|
720
|
+
const ts = this.getSimulationTimestamp(header.slotNumber);
|
|
721
|
+
const stateOverrides = await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan);
|
|
591
722
|
let balance = 0n;
|
|
592
723
|
if (this.config.fishermanMode) {
|
|
593
724
|
// In fisherman mode, we can't know where the proposer is publishing from
|
|
@@ -607,7 +738,7 @@ export class SequencerPublisher {
|
|
|
607
738
|
data: encodeFunctionData({ abi: RollupAbi, functionName: 'validateHeaderWithAttestations', args }),
|
|
608
739
|
from: MULTI_CALL_3_ADDRESS,
|
|
609
740
|
},
|
|
610
|
-
{ time: ts
|
|
741
|
+
{ time: ts },
|
|
611
742
|
stateOverrides,
|
|
612
743
|
);
|
|
613
744
|
this.log.debug(`Simulated validateHeader`);
|
|
@@ -660,6 +791,7 @@ export class SequencerPublisher {
|
|
|
660
791
|
gasUsed,
|
|
661
792
|
checkpointNumber,
|
|
662
793
|
forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
|
|
794
|
+
lastArchive: validationResult.checkpoint.lastArchive,
|
|
663
795
|
reason,
|
|
664
796
|
};
|
|
665
797
|
} catch (err) {
|
|
@@ -672,8 +804,8 @@ export class SequencerPublisher {
|
|
|
672
804
|
`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
|
|
673
805
|
{ ...logData, request, error: viemError.message },
|
|
674
806
|
);
|
|
675
|
-
const
|
|
676
|
-
if (
|
|
807
|
+
const latestProposedCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
808
|
+
if (latestProposedCheckpointNumber < checkpointNumber) {
|
|
677
809
|
this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, { ...logData });
|
|
678
810
|
return undefined;
|
|
679
811
|
} else {
|
|
@@ -718,9 +850,7 @@ export class SequencerPublisher {
|
|
|
718
850
|
const logData = { ...checkpoint, reason };
|
|
719
851
|
this.log.debug(`Building invalidate checkpoint ${checkpoint.checkpointNumber} request`, logData);
|
|
720
852
|
|
|
721
|
-
const attestationsAndSigners =
|
|
722
|
-
validationResult.attestations,
|
|
723
|
-
).getPackedAttestations();
|
|
853
|
+
const attestationsAndSigners = CommitteeAttestationsAndSigners.packAttestations(validationResult.attestations);
|
|
724
854
|
|
|
725
855
|
if (reason === 'invalid-attestation') {
|
|
726
856
|
return this.rollupContract.buildInvalidateBadAttestationRequest(
|
|
@@ -747,9 +877,8 @@ export class SequencerPublisher {
|
|
|
747
877
|
checkpoint: Checkpoint,
|
|
748
878
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
749
879
|
attestationsAndSignersSignature: Signature,
|
|
750
|
-
|
|
751
|
-
): Promise<
|
|
752
|
-
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
880
|
+
simulationOverridesPlan?: SimulationOverridesPlan,
|
|
881
|
+
): Promise<void> {
|
|
753
882
|
const blobFields = checkpoint.toBlobFields();
|
|
754
883
|
const blobs = await getBlobsPerL1Block(blobFields);
|
|
755
884
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
@@ -768,13 +897,11 @@ export class SequencerPublisher {
|
|
|
768
897
|
blobInput,
|
|
769
898
|
] as const;
|
|
770
899
|
|
|
771
|
-
await this.simulateProposeTx(args,
|
|
772
|
-
return ts;
|
|
900
|
+
await this.simulateProposeTx(args, simulationOverridesPlan);
|
|
773
901
|
}
|
|
774
902
|
|
|
775
903
|
private async enqueueCastSignalHelper(
|
|
776
904
|
slotNumber: SlotNumber,
|
|
777
|
-
timestamp: bigint,
|
|
778
905
|
signalType: GovernanceSignalAction,
|
|
779
906
|
payload: EthAddress,
|
|
780
907
|
base: IEmpireBase,
|
|
@@ -853,13 +980,17 @@ export class SequencerPublisher {
|
|
|
853
980
|
});
|
|
854
981
|
|
|
855
982
|
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
983
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
856
984
|
|
|
857
985
|
try {
|
|
858
986
|
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
859
987
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
860
988
|
} catch (err) {
|
|
861
989
|
const viemError = formatViemError(err);
|
|
862
|
-
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError
|
|
990
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError, {
|
|
991
|
+
simulationTimestamp: timestamp,
|
|
992
|
+
l1BlockNumber,
|
|
993
|
+
});
|
|
863
994
|
this.backupFailedTx({
|
|
864
995
|
id: keccak256(request.data!),
|
|
865
996
|
failureType: 'simulation',
|
|
@@ -922,19 +1053,16 @@ export class SequencerPublisher {
|
|
|
922
1053
|
/**
|
|
923
1054
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
924
1055
|
* @param slotNumber - The slot number to cast a signal for.
|
|
925
|
-
* @param timestamp - The timestamp of the slot to cast a signal for.
|
|
926
1056
|
* @returns True if the signal was successfully enqueued, false otherwise.
|
|
927
1057
|
*/
|
|
928
1058
|
public enqueueGovernanceCastSignal(
|
|
929
1059
|
governancePayload: EthAddress,
|
|
930
1060
|
slotNumber: SlotNumber,
|
|
931
|
-
timestamp: bigint,
|
|
932
1061
|
signerAddress: EthAddress,
|
|
933
1062
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
934
1063
|
): Promise<boolean> {
|
|
935
1064
|
return this.enqueueCastSignalHelper(
|
|
936
1065
|
slotNumber,
|
|
937
|
-
timestamp,
|
|
938
1066
|
'governance-signal',
|
|
939
1067
|
governancePayload,
|
|
940
1068
|
this.govProposerContract,
|
|
@@ -947,7 +1075,6 @@ export class SequencerPublisher {
|
|
|
947
1075
|
public async enqueueSlashingActions(
|
|
948
1076
|
actions: ProposerSlashAction[],
|
|
949
1077
|
slotNumber: SlotNumber,
|
|
950
|
-
timestamp: bigint,
|
|
951
1078
|
signerAddress: EthAddress,
|
|
952
1079
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
953
1080
|
): Promise<boolean> {
|
|
@@ -958,58 +1085,6 @@ export class SequencerPublisher {
|
|
|
958
1085
|
|
|
959
1086
|
for (const action of actions) {
|
|
960
1087
|
switch (action.type) {
|
|
961
|
-
case 'vote-empire-payload': {
|
|
962
|
-
if (this.slashingProposerContract?.type !== 'empire') {
|
|
963
|
-
this.log.error('Cannot vote for empire payload on non-empire slashing contract');
|
|
964
|
-
break;
|
|
965
|
-
}
|
|
966
|
-
this.log.debug(`Enqueuing slashing vote for payload ${action.payload} at slot ${slotNumber}`, {
|
|
967
|
-
signerAddress,
|
|
968
|
-
});
|
|
969
|
-
await this.enqueueCastSignalHelper(
|
|
970
|
-
slotNumber,
|
|
971
|
-
timestamp,
|
|
972
|
-
'empire-slashing-signal',
|
|
973
|
-
action.payload,
|
|
974
|
-
this.slashingProposerContract,
|
|
975
|
-
signerAddress,
|
|
976
|
-
signer,
|
|
977
|
-
);
|
|
978
|
-
break;
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
case 'create-empire-payload': {
|
|
982
|
-
this.log.debug(`Enqueuing slashing create payload at slot ${slotNumber}`, { slotNumber, signerAddress });
|
|
983
|
-
const request = this.slashFactoryContract.buildCreatePayloadRequest(action.data);
|
|
984
|
-
await this.simulateAndEnqueueRequest(
|
|
985
|
-
'create-empire-payload',
|
|
986
|
-
request,
|
|
987
|
-
(receipt: TransactionReceipt) =>
|
|
988
|
-
!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs),
|
|
989
|
-
slotNumber,
|
|
990
|
-
timestamp,
|
|
991
|
-
);
|
|
992
|
-
break;
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
case 'execute-empire-payload': {
|
|
996
|
-
this.log.debug(`Enqueuing slashing execute payload at slot ${slotNumber}`, { slotNumber, signerAddress });
|
|
997
|
-
if (this.slashingProposerContract?.type !== 'empire') {
|
|
998
|
-
this.log.error('Cannot execute slashing payload on non-empire slashing contract');
|
|
999
|
-
return false;
|
|
1000
|
-
}
|
|
1001
|
-
const empireSlashingProposer = this.slashingProposerContract as EmpireSlashingProposerContract;
|
|
1002
|
-
const request = empireSlashingProposer.buildExecuteRoundRequest(action.round);
|
|
1003
|
-
await this.simulateAndEnqueueRequest(
|
|
1004
|
-
'execute-empire-payload',
|
|
1005
|
-
request,
|
|
1006
|
-
(receipt: TransactionReceipt) => !!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs),
|
|
1007
|
-
slotNumber,
|
|
1008
|
-
timestamp,
|
|
1009
|
-
);
|
|
1010
|
-
break;
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
1088
|
case 'vote-offenses': {
|
|
1014
1089
|
this.log.debug(`Enqueuing slashing vote for ${action.votes.length} votes at slot ${slotNumber}`, {
|
|
1015
1090
|
slotNumber,
|
|
@@ -1017,19 +1092,17 @@ export class SequencerPublisher {
|
|
|
1017
1092
|
votesCount: action.votes.length,
|
|
1018
1093
|
signerAddress,
|
|
1019
1094
|
});
|
|
1020
|
-
if (this.slashingProposerContract
|
|
1021
|
-
this.log.error('
|
|
1095
|
+
if (!this.slashingProposerContract) {
|
|
1096
|
+
this.log.error('No slashing proposer contract available');
|
|
1022
1097
|
return false;
|
|
1023
1098
|
}
|
|
1024
|
-
const tallySlashingProposer = this.slashingProposerContract as TallySlashingProposerContract;
|
|
1025
1099
|
const votes = bufferToHex(encodeSlashConsensusVotes(action.votes));
|
|
1026
|
-
const request = await
|
|
1100
|
+
const request = await this.slashingProposerContract.buildVoteRequestFromSigner(votes, slotNumber, signer);
|
|
1027
1101
|
await this.simulateAndEnqueueRequest(
|
|
1028
1102
|
'vote-offenses',
|
|
1029
1103
|
request,
|
|
1030
|
-
(receipt: TransactionReceipt) => !!
|
|
1104
|
+
(receipt: TransactionReceipt) => !!this.slashingProposerContract!.tryExtractVoteCastEvent(receipt.logs),
|
|
1031
1105
|
slotNumber,
|
|
1032
|
-
timestamp,
|
|
1033
1106
|
);
|
|
1034
1107
|
break;
|
|
1035
1108
|
}
|
|
@@ -1040,18 +1113,20 @@ export class SequencerPublisher {
|
|
|
1040
1113
|
round: action.round,
|
|
1041
1114
|
signerAddress,
|
|
1042
1115
|
});
|
|
1043
|
-
if (this.slashingProposerContract
|
|
1044
|
-
this.log.error('
|
|
1116
|
+
if (!this.slashingProposerContract) {
|
|
1117
|
+
this.log.error('No slashing proposer contract available');
|
|
1045
1118
|
return false;
|
|
1046
1119
|
}
|
|
1047
|
-
const
|
|
1048
|
-
|
|
1120
|
+
const executeRequest = this.slashingProposerContract.buildExecuteRoundRequest(
|
|
1121
|
+
action.round,
|
|
1122
|
+
action.committees,
|
|
1123
|
+
);
|
|
1049
1124
|
await this.simulateAndEnqueueRequest(
|
|
1050
1125
|
'execute-slash',
|
|
1051
|
-
|
|
1052
|
-
(receipt: TransactionReceipt) =>
|
|
1126
|
+
executeRequest,
|
|
1127
|
+
(receipt: TransactionReceipt) =>
|
|
1128
|
+
!!this.slashingProposerContract!.tryExtractRoundExecutedEvent(receipt.logs),
|
|
1053
1129
|
slotNumber,
|
|
1054
|
-
timestamp,
|
|
1055
1130
|
);
|
|
1056
1131
|
break;
|
|
1057
1132
|
}
|
|
@@ -1071,7 +1146,7 @@ export class SequencerPublisher {
|
|
|
1071
1146
|
checkpoint: Checkpoint,
|
|
1072
1147
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
1073
1148
|
attestationsAndSignersSignature: Signature,
|
|
1074
|
-
opts:
|
|
1149
|
+
opts: EnqueueProposeCheckpointOpts = {},
|
|
1075
1150
|
): Promise<void> {
|
|
1076
1151
|
const checkpointHeader = checkpoint.header;
|
|
1077
1152
|
|
|
@@ -1087,7 +1162,9 @@ export class SequencerPublisher {
|
|
|
1087
1162
|
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
1088
1163
|
};
|
|
1089
1164
|
|
|
1090
|
-
|
|
1165
|
+
const simulationOverridesPlan = SimulationOverridesBuilder.from(opts.simulationOverridesPlan)
|
|
1166
|
+
.withoutBlobCheck()
|
|
1167
|
+
.build();
|
|
1091
1168
|
|
|
1092
1169
|
try {
|
|
1093
1170
|
// @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
|
|
@@ -1095,23 +1172,47 @@ export class SequencerPublisher {
|
|
|
1095
1172
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
1096
1173
|
// make time consistency checks break.
|
|
1097
1174
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
1098
|
-
|
|
1175
|
+
await this.validateCheckpointForSubmission(
|
|
1099
1176
|
checkpoint,
|
|
1100
1177
|
attestationsAndSigners,
|
|
1101
1178
|
attestationsAndSignersSignature,
|
|
1102
|
-
|
|
1179
|
+
simulationOverridesPlan,
|
|
1103
1180
|
);
|
|
1104
1181
|
} catch (err: any) {
|
|
1105
1182
|
this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
1106
1183
|
...checkpoint.getStats(),
|
|
1107
1184
|
slotNumber: checkpoint.header.slotNumber,
|
|
1108
|
-
|
|
1185
|
+
simulationOverridesPlan,
|
|
1109
1186
|
});
|
|
1110
1187
|
throw err;
|
|
1111
1188
|
}
|
|
1112
1189
|
|
|
1113
|
-
|
|
1114
|
-
|
|
1190
|
+
// Build a pre-check callback that re-validates the checkpoint before L1 submission.
|
|
1191
|
+
// During pipelining this catches stale proposals due to prunes or L1 reorgs that occur during the pipeline sleep.
|
|
1192
|
+
let preCheck = undefined;
|
|
1193
|
+
if (this.epochCache.isProposerPipeliningEnabled()) {
|
|
1194
|
+
preCheck = async () => {
|
|
1195
|
+
this.log.debug(`Re-validating checkpoint ${checkpoint.number} before L1 submission`);
|
|
1196
|
+
await this.validateCheckpointForSubmission(
|
|
1197
|
+
checkpoint,
|
|
1198
|
+
attestationsAndSigners,
|
|
1199
|
+
attestationsAndSignersSignature,
|
|
1200
|
+
simulationOverridesPlan,
|
|
1201
|
+
);
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
this.log.verbose(`Enqueuing checkpoint propose transaction`, {
|
|
1206
|
+
...checkpoint.toCheckpointInfo(),
|
|
1207
|
+
txTimeoutAt: opts.txTimeoutAt,
|
|
1208
|
+
simulationOverridesPlan,
|
|
1209
|
+
});
|
|
1210
|
+
await this.addProposeTx(
|
|
1211
|
+
checkpoint,
|
|
1212
|
+
proposeTxArgs,
|
|
1213
|
+
{ txTimeoutAt: opts.txTimeoutAt, simulationOverridesPlan },
|
|
1214
|
+
preCheck,
|
|
1215
|
+
);
|
|
1115
1216
|
}
|
|
1116
1217
|
|
|
1117
1218
|
public enqueueInvalidateCheckpoint(
|
|
@@ -1154,8 +1255,8 @@ export class SequencerPublisher {
|
|
|
1154
1255
|
request: L1TxRequest,
|
|
1155
1256
|
checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
|
|
1156
1257
|
slotNumber: SlotNumber,
|
|
1157
|
-
timestamp: bigint,
|
|
1158
1258
|
) {
|
|
1259
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
1159
1260
|
const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
|
|
1160
1261
|
if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
|
|
1161
1262
|
this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
|
|
@@ -1171,8 +1272,9 @@ export class SequencerPublisher {
|
|
|
1171
1272
|
|
|
1172
1273
|
let gasUsed: bigint;
|
|
1173
1274
|
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
1275
|
+
|
|
1174
1276
|
try {
|
|
1175
|
-
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi));
|
|
1277
|
+
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi));
|
|
1176
1278
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
1177
1279
|
} catch (err) {
|
|
1178
1280
|
const viemError = formatViemError(err, simulateAbi);
|
|
@@ -1230,6 +1332,7 @@ export class SequencerPublisher {
|
|
|
1230
1332
|
*/
|
|
1231
1333
|
public interrupt() {
|
|
1232
1334
|
this.interrupted = true;
|
|
1335
|
+
this.interruptibleSleep.interrupt();
|
|
1233
1336
|
this.l1TxUtils.interrupt();
|
|
1234
1337
|
}
|
|
1235
1338
|
|
|
@@ -1239,11 +1342,7 @@ export class SequencerPublisher {
|
|
|
1239
1342
|
this.l1TxUtils.restart();
|
|
1240
1343
|
}
|
|
1241
1344
|
|
|
1242
|
-
private async prepareProposeTx(
|
|
1243
|
-
encodedData: L1ProcessArgs,
|
|
1244
|
-
timestamp: bigint,
|
|
1245
|
-
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1246
|
-
) {
|
|
1345
|
+
private async prepareProposeTx(encodedData: L1ProcessArgs, simulationOverridesPlan?: SimulationOverridesPlan) {
|
|
1247
1346
|
const kzg = Blob.getViemKzgInstance();
|
|
1248
1347
|
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
1249
1348
|
this.log.debug('Validating blob input', { blobInput });
|
|
@@ -1314,7 +1413,7 @@ export class SequencerPublisher {
|
|
|
1314
1413
|
blobInput,
|
|
1315
1414
|
] as const;
|
|
1316
1415
|
|
|
1317
|
-
const { rollupData, simulationResult } = await this.simulateProposeTx(args,
|
|
1416
|
+
const { rollupData, simulationResult } = await this.simulateProposeTx(args, simulationOverridesPlan);
|
|
1318
1417
|
|
|
1319
1418
|
return { args, blobEvaluationGas, rollupData, simulationResult };
|
|
1320
1419
|
}
|
|
@@ -1322,7 +1421,6 @@ export class SequencerPublisher {
|
|
|
1322
1421
|
/**
|
|
1323
1422
|
* Simulates the propose tx with eth_simulateV1
|
|
1324
1423
|
* @param args - The propose tx args
|
|
1325
|
-
* @param timestamp - The timestamp to simulate proposal at
|
|
1326
1424
|
* @returns The simulation result
|
|
1327
1425
|
*/
|
|
1328
1426
|
private async simulateProposeTx(
|
|
@@ -1339,8 +1437,7 @@ export class SequencerPublisher {
|
|
|
1339
1437
|
ViemSignature,
|
|
1340
1438
|
`0x${string}`,
|
|
1341
1439
|
],
|
|
1342
|
-
|
|
1343
|
-
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1440
|
+
simulationOverridesPlan?: SimulationOverridesPlan,
|
|
1344
1441
|
) {
|
|
1345
1442
|
const rollupData = encodeFunctionData({
|
|
1346
1443
|
abi: RollupAbi,
|
|
@@ -1348,23 +1445,7 @@ export class SequencerPublisher {
|
|
|
1348
1445
|
args,
|
|
1349
1446
|
});
|
|
1350
1447
|
|
|
1351
|
-
|
|
1352
|
-
const forcePendingCheckpointNumberStateDiff = (
|
|
1353
|
-
options.forcePendingCheckpointNumber !== undefined
|
|
1354
|
-
? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber)
|
|
1355
|
-
: []
|
|
1356
|
-
).flatMap(override => override.stateDiff ?? []);
|
|
1357
|
-
|
|
1358
|
-
const stateOverrides: StateOverride = [
|
|
1359
|
-
{
|
|
1360
|
-
address: this.rollupContract.address,
|
|
1361
|
-
// @note we override checkBlob to false since blobs are not part simulate()
|
|
1362
|
-
stateDiff: [
|
|
1363
|
-
{ slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
|
|
1364
|
-
...forcePendingCheckpointNumberStateDiff,
|
|
1365
|
-
],
|
|
1366
|
-
},
|
|
1367
|
-
];
|
|
1448
|
+
const stateOverrides = await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan);
|
|
1368
1449
|
// In fisherman mode, simulate as the proposer but with sufficient balance
|
|
1369
1450
|
if (this.proposerAddressForSimulation) {
|
|
1370
1451
|
stateOverrides.push({
|
|
@@ -1374,6 +1455,7 @@ export class SequencerPublisher {
|
|
|
1374
1455
|
}
|
|
1375
1456
|
|
|
1376
1457
|
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1458
|
+
const simTs = this.getSimulationTimestamp(SlotNumber.fromBigInt(args[0].header.slotNumber));
|
|
1377
1459
|
|
|
1378
1460
|
const simulationResult = await this.l1TxUtils
|
|
1379
1461
|
.simulate(
|
|
@@ -1384,8 +1466,7 @@ export class SequencerPublisher {
|
|
|
1384
1466
|
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1385
1467
|
},
|
|
1386
1468
|
{
|
|
1387
|
-
|
|
1388
|
-
time: timestamp + 1n,
|
|
1469
|
+
time: simTs,
|
|
1389
1470
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1390
1471
|
gasLimit: MAX_L1_TX_LIMIT * 2n,
|
|
1391
1472
|
},
|
|
@@ -1407,7 +1488,7 @@ export class SequencerPublisher {
|
|
|
1407
1488
|
logs: [],
|
|
1408
1489
|
};
|
|
1409
1490
|
}
|
|
1410
|
-
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1491
|
+
this.log.error(`Failed to simulate propose tx`, viemError, { simulationTimestamp: simTs });
|
|
1411
1492
|
this.backupFailedTx({
|
|
1412
1493
|
id: keccak256(rollupData),
|
|
1413
1494
|
failureType: 'simulation',
|
|
@@ -1429,16 +1510,15 @@ export class SequencerPublisher {
|
|
|
1429
1510
|
private async addProposeTx(
|
|
1430
1511
|
checkpoint: Checkpoint,
|
|
1431
1512
|
encodedData: L1ProcessArgs,
|
|
1432
|
-
opts:
|
|
1433
|
-
|
|
1513
|
+
opts: EnqueueProposeCheckpointOpts = {},
|
|
1514
|
+
preCheck?: () => Promise<void>,
|
|
1434
1515
|
): Promise<void> {
|
|
1435
1516
|
const slot = checkpoint.header.slotNumber;
|
|
1436
1517
|
const timer = new Timer();
|
|
1437
1518
|
const kzg = Blob.getViemKzgInstance();
|
|
1438
1519
|
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
1439
1520
|
encodedData,
|
|
1440
|
-
|
|
1441
|
-
opts,
|
|
1521
|
+
opts.simulationOverridesPlan,
|
|
1442
1522
|
);
|
|
1443
1523
|
const startBlock = await this.l1TxUtils.getBlockNumber();
|
|
1444
1524
|
const gasLimit = this.l1TxUtils.bumpGasLimit(
|
|
@@ -1462,7 +1542,8 @@ export class SequencerPublisher {
|
|
|
1462
1542
|
data: rollupData,
|
|
1463
1543
|
},
|
|
1464
1544
|
lastValidL2Slot: checkpoint.header.slotNumber,
|
|
1465
|
-
gasConfig: {
|
|
1545
|
+
gasConfig: { txTimeoutAt: opts.txTimeoutAt, gasLimit },
|
|
1546
|
+
preCheck,
|
|
1466
1547
|
blobConfig: {
|
|
1467
1548
|
blobs: encodedData.blobs.map(b => b.data),
|
|
1468
1549
|
kzg,
|
|
@@ -1515,4 +1596,17 @@ export class SequencerPublisher {
|
|
|
1515
1596
|
},
|
|
1516
1597
|
});
|
|
1517
1598
|
}
|
|
1599
|
+
|
|
1600
|
+
/** Returns the timestamp of the last L1 slot within a given L2 slot. Used as the simulation timestamp
|
|
1601
|
+
* for eth_simulateV1 calls, since it's guaranteed to be greater than any L1 block produced during the slot. */
|
|
1602
|
+
private getSimulationTimestamp(slot: SlotNumber): bigint {
|
|
1603
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1604
|
+
return getLastL1SlotTimestampForL2Slot(slot, l1Constants);
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
/** Returns the timestamp of the next L1 slot boundary after now. */
|
|
1608
|
+
private getNextL1SlotTimestamp(): bigint {
|
|
1609
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1610
|
+
return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
|
|
1611
|
+
}
|
|
1518
1612
|
}
|