@aztec/sequencer-client 0.0.1-commit.5476d83 → 0.0.1-commit.5914bae
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/client/sequencer-client.d.ts +25 -16
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +69 -29
- package/dest/config.d.ts +34 -8
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +105 -44
- package/dest/global_variable_builder/global_builder.d.ts +27 -14
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +65 -54
- package/dest/global_variable_builder/index.d.ts +2 -2
- package/dest/global_variable_builder/index.d.ts.map +1 -1
- 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 +37 -20
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +104 -39
- package/dest/publisher/sequencer-publisher-factory.d.ts +15 -6
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +14 -3
- package/dest/publisher/sequencer-publisher-metrics.d.ts +3 -3
- 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 +76 -53
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +691 -169
- package/dest/sequencer/checkpoint_proposal_job.d.ts +102 -0
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_proposal_job.js +1247 -0
- package/dest/sequencer/checkpoint_voter.d.ts +34 -0
- package/dest/sequencer/checkpoint_voter.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_voter.js +106 -0
- package/dest/sequencer/config.d.ts +3 -2
- package/dest/sequencer/config.d.ts.map +1 -1
- package/dest/sequencer/events.d.ts +46 -0
- package/dest/sequencer/events.d.ts.map +1 -0
- package/dest/sequencer/events.js +1 -0
- package/dest/sequencer/index.d.ts +4 -2
- package/dest/sequencer/index.d.ts.map +1 -1
- package/dest/sequencer/index.js +3 -1
- package/dest/sequencer/metrics.d.ts +38 -6
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +216 -72
- package/dest/sequencer/sequencer.d.ts +125 -133
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +733 -640
- package/dest/sequencer/timetable.d.ts +54 -16
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +147 -62
- package/dest/sequencer/types.d.ts +3 -0
- package/dest/sequencer/types.d.ts.map +1 -0
- package/dest/sequencer/types.js +1 -0
- package/dest/sequencer/utils.d.ts +14 -8
- package/dest/sequencer/utils.d.ts.map +1 -1
- package/dest/sequencer/utils.js +7 -4
- package/dest/test/index.d.ts +6 -7
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts +95 -0
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -0
- package/dest/test/mock_checkpoint_builder.js +233 -0
- package/dest/test/utils.d.ts +53 -0
- package/dest/test/utils.d.ts.map +1 -0
- package/dest/test/utils.js +104 -0
- package/package.json +32 -30
- package/src/client/sequencer-client.ts +99 -53
- package/src/config.ts +121 -54
- package/src/global_variable_builder/global_builder.ts +76 -73
- package/src/global_variable_builder/index.ts +1 -1
- package/src/index.ts +1 -7
- package/src/publisher/config.ts +122 -50
- package/src/publisher/sequencer-publisher-factory.ts +28 -10
- package/src/publisher/sequencer-publisher-metrics.ts +19 -71
- package/src/publisher/sequencer-publisher.ts +406 -215
- package/src/sequencer/README.md +531 -0
- package/src/sequencer/checkpoint_proposal_job.ts +976 -0
- package/src/sequencer/checkpoint_voter.ts +119 -0
- package/src/sequencer/config.ts +2 -1
- package/src/sequencer/events.ts +27 -0
- package/src/sequencer/index.ts +3 -1
- package/src/sequencer/metrics.ts +268 -82
- package/src/sequencer/sequencer.ts +481 -845
- package/src/sequencer/timetable.ts +178 -83
- package/src/sequencer/types.ts +6 -0
- package/src/sequencer/utils.ts +18 -9
- package/src/test/index.ts +5 -6
- package/src/test/mock_checkpoint_builder.ts +325 -0
- package/src/test/utils.ts +167 -0
- package/dest/sequencer/block_builder.d.ts +0 -27
- package/dest/sequencer/block_builder.d.ts.map +0 -1
- package/dest/sequencer/block_builder.js +0 -134
- 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 -17
- 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 -222
- package/src/tx_validator/nullifier_cache.ts +0 -30
- package/src/tx_validator/tx_validator_factory.ts +0 -132
|
@@ -1,48 +1,55 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { BlobClientInterface } from '@aztec/blob-client/client';
|
|
2
2
|
import { Blob, getBlobsPerL1Block, getPrefixedEthBlobCommitments } from '@aztec/blob-lib';
|
|
3
|
-
import { type BlobSinkClientInterface, createBlobSinkClient } from '@aztec/blob-sink/client';
|
|
4
3
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
4
|
+
import type { L1ContractsConfig } from '@aztec/ethereum/config';
|
|
5
5
|
import {
|
|
6
6
|
type EmpireSlashingProposerContract,
|
|
7
|
-
|
|
7
|
+
FeeAssetPriceOracle,
|
|
8
8
|
type GovernanceProposerContract,
|
|
9
9
|
type IEmpireBase,
|
|
10
|
-
type L1BlobInputs,
|
|
11
|
-
type L1ContractsConfig,
|
|
12
|
-
type L1TxConfig,
|
|
13
|
-
type L1TxRequest,
|
|
14
10
|
MULTI_CALL_3_ADDRESS,
|
|
15
11
|
Multicall3,
|
|
16
12
|
RollupContract,
|
|
17
13
|
type TallySlashingProposerContract,
|
|
18
|
-
type TransactionStats,
|
|
19
14
|
type ViemCommitteeAttestations,
|
|
20
15
|
type ViemHeader,
|
|
16
|
+
} from '@aztec/ethereum/contracts';
|
|
17
|
+
import { type L1FeeAnalysisResult, L1FeeAnalyzer } from '@aztec/ethereum/l1-fee-analysis';
|
|
18
|
+
import {
|
|
19
|
+
type L1BlobInputs,
|
|
20
|
+
type L1TxConfig,
|
|
21
|
+
type L1TxRequest,
|
|
22
|
+
type L1TxUtils,
|
|
23
|
+
MAX_L1_TX_LIMIT,
|
|
24
|
+
type TransactionStats,
|
|
21
25
|
WEI_CONST,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
} from '@aztec/ethereum';
|
|
25
|
-
import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs';
|
|
26
|
+
} from '@aztec/ethereum/l1-tx-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 { SlotNumber } from '@aztec/foundation/branded-types';
|
|
30
|
+
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
31
|
+
import { trimmedBytesLength } from '@aztec/foundation/buffer';
|
|
32
|
+
import { pick } from '@aztec/foundation/collection';
|
|
33
|
+
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
29
34
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
30
35
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
31
|
-
import type { Fr } from '@aztec/foundation/fields';
|
|
32
36
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
37
|
+
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
33
38
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
34
39
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
35
40
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
36
41
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
37
|
-
import {
|
|
42
|
+
import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
43
|
+
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
44
|
+
import { getLastL1SlotTimestampForL2Slot, getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
38
45
|
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
39
46
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
40
|
-
import type {
|
|
41
|
-
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
47
|
+
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
48
|
+
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
42
49
|
|
|
43
50
|
import { type StateOverride, type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
|
|
44
51
|
|
|
45
|
-
import type {
|
|
52
|
+
import type { SequencerPublisherConfig } from './config.js';
|
|
46
53
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
47
54
|
|
|
48
55
|
/** Arguments to the process method of the rollup contract */
|
|
@@ -57,6 +64,8 @@ type L1ProcessArgs = {
|
|
|
57
64
|
attestationsAndSigners: CommitteeAttestationsAndSigners;
|
|
58
65
|
/** Attestations and signers signature */
|
|
59
66
|
attestationsAndSignersSignature: Signature;
|
|
67
|
+
/** The fee asset price modifier in basis points (from oracle) */
|
|
68
|
+
feeAssetPriceModifier: bigint;
|
|
60
69
|
};
|
|
61
70
|
|
|
62
71
|
export const Actions = [
|
|
@@ -78,12 +87,12 @@ type GovernanceSignalAction = Extract<Action, 'governance-signal' | 'empire-slas
|
|
|
78
87
|
// Sorting for actions such that invalidations go before proposals, and proposals go before votes
|
|
79
88
|
export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
|
|
80
89
|
|
|
81
|
-
export type
|
|
90
|
+
export type InvalidateCheckpointRequest = {
|
|
82
91
|
request: L1TxRequest;
|
|
83
92
|
reason: 'invalid-attestation' | 'insufficient-attestations';
|
|
84
93
|
gasUsed: bigint;
|
|
85
|
-
|
|
86
|
-
|
|
94
|
+
checkpointNumber: CheckpointNumber;
|
|
95
|
+
forcePendingCheckpointNumber: CheckpointNumber;
|
|
87
96
|
};
|
|
88
97
|
|
|
89
98
|
interface RequestWithExpiry {
|
|
@@ -108,17 +117,24 @@ export class SequencerPublisher {
|
|
|
108
117
|
|
|
109
118
|
protected lastActions: Partial<Record<Action, SlotNumber>> = {};
|
|
110
119
|
|
|
120
|
+
private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
|
|
121
|
+
private payloadProposedCache: Set<string> = new Set<string>();
|
|
122
|
+
|
|
111
123
|
protected log: Logger;
|
|
112
124
|
protected ethereumSlotDuration: bigint;
|
|
125
|
+
protected aztecSlotDuration: bigint;
|
|
126
|
+
private dateProvider: DateProvider;
|
|
113
127
|
|
|
114
|
-
private
|
|
128
|
+
private blobClient: BlobClientInterface;
|
|
115
129
|
|
|
116
130
|
/** Address to use for simulations in fisherman mode (actual proposer's address) */
|
|
117
131
|
private proposerAddressForSimulation?: EthAddress;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
132
|
+
|
|
133
|
+
/** L1 fee analyzer for fisherman mode */
|
|
134
|
+
private l1FeeAnalyzer?: L1FeeAnalyzer;
|
|
135
|
+
|
|
136
|
+
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */
|
|
137
|
+
private feeAssetPriceOracle: FeeAssetPriceOracle;
|
|
122
138
|
|
|
123
139
|
// A CALL to a cold address is 2700 gas
|
|
124
140
|
public static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
@@ -126,20 +142,23 @@ export class SequencerPublisher {
|
|
|
126
142
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
127
143
|
public static VOTE_GAS_GUESS: bigint = 800_000n;
|
|
128
144
|
|
|
129
|
-
public l1TxUtils:
|
|
145
|
+
public l1TxUtils: L1TxUtils;
|
|
130
146
|
public rollupContract: RollupContract;
|
|
131
147
|
public govProposerContract: GovernanceProposerContract;
|
|
132
148
|
public slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
133
149
|
public slashFactoryContract: SlashFactoryContract;
|
|
134
150
|
|
|
151
|
+
public readonly tracer: Tracer;
|
|
152
|
+
|
|
135
153
|
protected requests: RequestWithExpiry[] = [];
|
|
136
154
|
|
|
137
155
|
constructor(
|
|
138
|
-
private config:
|
|
156
|
+
private config: Pick<SequencerPublisherConfig, 'fishermanMode'> &
|
|
157
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration'> & { l1ChainId: number },
|
|
139
158
|
deps: {
|
|
140
159
|
telemetry?: TelemetryClient;
|
|
141
|
-
|
|
142
|
-
l1TxUtils:
|
|
160
|
+
blobClient: BlobClientInterface;
|
|
161
|
+
l1TxUtils: L1TxUtils;
|
|
143
162
|
rollupContract: RollupContract;
|
|
144
163
|
slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
145
164
|
governanceProposerContract: GovernanceProposerContract;
|
|
@@ -153,14 +172,16 @@ export class SequencerPublisher {
|
|
|
153
172
|
) {
|
|
154
173
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
155
174
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
175
|
+
this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
|
|
176
|
+
this.dateProvider = deps.dateProvider;
|
|
156
177
|
this.epochCache = deps.epochCache;
|
|
157
178
|
this.lastActions = deps.lastActions;
|
|
158
179
|
|
|
159
|
-
this.
|
|
160
|
-
deps.blobSinkClient ?? createBlobSinkClient(config, { logger: createLogger('sequencer:blob-sink:client') });
|
|
180
|
+
this.blobClient = deps.blobClient;
|
|
161
181
|
|
|
162
182
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
163
183
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
184
|
+
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
164
185
|
this.l1TxUtils = deps.l1TxUtils;
|
|
165
186
|
|
|
166
187
|
this.rollupContract = deps.rollupContract;
|
|
@@ -174,16 +195,47 @@ export class SequencerPublisher {
|
|
|
174
195
|
this.slashingProposerContract = newSlashingProposer;
|
|
175
196
|
});
|
|
176
197
|
this.slashFactoryContract = deps.slashFactoryContract;
|
|
198
|
+
|
|
199
|
+
// Initialize L1 fee analyzer for fisherman mode
|
|
200
|
+
if (config.fishermanMode) {
|
|
201
|
+
this.l1FeeAnalyzer = new L1FeeAnalyzer(
|
|
202
|
+
this.l1TxUtils.client,
|
|
203
|
+
deps.dateProvider,
|
|
204
|
+
createLogger('sequencer:publisher:fee-analyzer'),
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Initialize fee asset price oracle
|
|
209
|
+
this.feeAssetPriceOracle = new FeeAssetPriceOracle(
|
|
210
|
+
this.l1TxUtils.client,
|
|
211
|
+
this.rollupContract,
|
|
212
|
+
createLogger('sequencer:publisher:price-oracle'),
|
|
213
|
+
);
|
|
177
214
|
}
|
|
178
215
|
|
|
179
216
|
public getRollupContract(): RollupContract {
|
|
180
217
|
return this.rollupContract;
|
|
181
218
|
}
|
|
182
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Gets the fee asset price modifier from the oracle.
|
|
222
|
+
* Returns 0n if the oracle query fails.
|
|
223
|
+
*/
|
|
224
|
+
public getFeeAssetPriceModifier(): Promise<bigint> {
|
|
225
|
+
return this.feeAssetPriceOracle.computePriceModifier();
|
|
226
|
+
}
|
|
227
|
+
|
|
183
228
|
public getSenderAddress() {
|
|
184
229
|
return this.l1TxUtils.getSenderAddress();
|
|
185
230
|
}
|
|
186
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Gets the L1 fee analyzer instance (only available in fisherman mode)
|
|
234
|
+
*/
|
|
235
|
+
public getL1FeeAnalyzer(): L1FeeAnalyzer | undefined {
|
|
236
|
+
return this.l1FeeAnalyzer;
|
|
237
|
+
}
|
|
238
|
+
|
|
187
239
|
/**
|
|
188
240
|
* Sets the proposer address to use for simulations in fisherman mode.
|
|
189
241
|
* @param proposerAddress - The actual proposer's address to use for balance lookups in simulations
|
|
@@ -211,6 +263,62 @@ export class SequencerPublisher {
|
|
|
211
263
|
}
|
|
212
264
|
}
|
|
213
265
|
|
|
266
|
+
/**
|
|
267
|
+
* Analyzes L1 fees for the pending requests without sending them.
|
|
268
|
+
* This is used in fisherman mode to validate fee calculations.
|
|
269
|
+
* @param l2SlotNumber - The L2 slot number for this analysis
|
|
270
|
+
* @param onComplete - Optional callback to invoke when analysis completes (after block is mined)
|
|
271
|
+
* @returns The analysis result (incomplete until block mines), or undefined if no requests
|
|
272
|
+
*/
|
|
273
|
+
public async analyzeL1Fees(
|
|
274
|
+
l2SlotNumber: SlotNumber,
|
|
275
|
+
onComplete?: (analysis: L1FeeAnalysisResult) => void,
|
|
276
|
+
): Promise<L1FeeAnalysisResult | undefined> {
|
|
277
|
+
if (!this.l1FeeAnalyzer) {
|
|
278
|
+
this.log.warn('L1 fee analyzer not available (not in fisherman mode)');
|
|
279
|
+
return undefined;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const requestsToAnalyze = [...this.requests];
|
|
283
|
+
if (requestsToAnalyze.length === 0) {
|
|
284
|
+
this.log.debug('No requests to analyze for L1 fees');
|
|
285
|
+
return undefined;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Extract blob config from requests (if any)
|
|
289
|
+
const blobConfigs = requestsToAnalyze.filter(request => request.blobConfig).map(request => request.blobConfig);
|
|
290
|
+
const blobConfig = blobConfigs[0];
|
|
291
|
+
|
|
292
|
+
// Get gas configs
|
|
293
|
+
const gasConfigs = requestsToAnalyze.filter(request => request.gasConfig).map(request => request.gasConfig);
|
|
294
|
+
const gasLimits = gasConfigs.map(g => g?.gasLimit).filter((g): g is bigint => g !== undefined);
|
|
295
|
+
const gasLimit = gasLimits.length > 0 ? gasLimits.reduce((sum, g) => sum + g, 0n) : 0n;
|
|
296
|
+
|
|
297
|
+
// Get the transaction requests
|
|
298
|
+
const l1Requests = requestsToAnalyze.map(r => r.request);
|
|
299
|
+
|
|
300
|
+
// Start the analysis
|
|
301
|
+
const analysisId = await this.l1FeeAnalyzer.startAnalysis(
|
|
302
|
+
l2SlotNumber,
|
|
303
|
+
gasLimit > 0n ? gasLimit : MAX_L1_TX_LIMIT,
|
|
304
|
+
l1Requests,
|
|
305
|
+
blobConfig,
|
|
306
|
+
onComplete,
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
this.log.info('Started L1 fee analysis', {
|
|
310
|
+
analysisId,
|
|
311
|
+
l2SlotNumber: l2SlotNumber.toString(),
|
|
312
|
+
requestCount: requestsToAnalyze.length,
|
|
313
|
+
hasBlobConfig: !!blobConfig,
|
|
314
|
+
gasLimit: gasLimit.toString(),
|
|
315
|
+
actions: requestsToAnalyze.map(r => r.action),
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// Return the analysis result (will be incomplete until block mines)
|
|
319
|
+
return this.l1FeeAnalyzer.getAnalysis(analysisId);
|
|
320
|
+
}
|
|
321
|
+
|
|
214
322
|
/**
|
|
215
323
|
* Sends all requests that are still valid.
|
|
216
324
|
* @returns one of:
|
|
@@ -218,10 +326,11 @@ export class SequencerPublisher {
|
|
|
218
326
|
* - a receipt and errorMsg if it failed on L1
|
|
219
327
|
* - undefined if no valid requests are found OR the tx failed to send.
|
|
220
328
|
*/
|
|
329
|
+
@trackSpan('SequencerPublisher.sendRequests')
|
|
221
330
|
public async sendRequests() {
|
|
222
331
|
const requestsToProcess = [...this.requests];
|
|
223
332
|
this.requests = [];
|
|
224
|
-
if (this.interrupted) {
|
|
333
|
+
if (this.interrupted || requestsToProcess.length === 0) {
|
|
225
334
|
return undefined;
|
|
226
335
|
}
|
|
227
336
|
const currentL2Slot = this.getCurrentL2Slot();
|
|
@@ -253,8 +362,8 @@ export class SequencerPublisher {
|
|
|
253
362
|
// @note - we can only have one blob config per bundle
|
|
254
363
|
// find requests with gas and blob configs
|
|
255
364
|
// See https://github.com/AztecProtocol/aztec-packages/issues/11513
|
|
256
|
-
const gasConfigs =
|
|
257
|
-
const blobConfigs =
|
|
365
|
+
const gasConfigs = validRequests.filter(request => request.gasConfig).map(request => request.gasConfig);
|
|
366
|
+
const blobConfigs = validRequests.filter(request => request.blobConfig).map(request => request.blobConfig);
|
|
258
367
|
|
|
259
368
|
if (blobConfigs.length > 1) {
|
|
260
369
|
throw new Error('Multiple blob configs found');
|
|
@@ -264,7 +373,16 @@ export class SequencerPublisher {
|
|
|
264
373
|
|
|
265
374
|
// Merge gasConfigs. Yields the sum of gasLimits, and the earliest txTimeoutAt, or undefined if no gasConfig sets them.
|
|
266
375
|
const gasLimits = gasConfigs.map(g => g?.gasLimit).filter((g): g is bigint => g !== undefined);
|
|
267
|
-
|
|
376
|
+
let gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
|
|
377
|
+
// Cap at L1 block gas limit so the node accepts the tx ("gas limit too high" otherwise).
|
|
378
|
+
const maxGas = MAX_L1_TX_LIMIT;
|
|
379
|
+
if (gasLimit !== undefined && gasLimit > maxGas) {
|
|
380
|
+
this.log.debug('Capping bundled tx gas limit to L1 max', {
|
|
381
|
+
requested: gasLimit,
|
|
382
|
+
capped: maxGas,
|
|
383
|
+
});
|
|
384
|
+
gasLimit = maxGas;
|
|
385
|
+
}
|
|
268
386
|
const txTimeoutAts = gasConfigs.map(g => g?.txTimeoutAt).filter((g): g is Date => g !== undefined);
|
|
269
387
|
const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map(g => g.getTime()))) : undefined; // earliest
|
|
270
388
|
const txConfig: RequestWithExpiry['gasConfig'] = { gasLimit, txTimeoutAt };
|
|
@@ -313,7 +431,16 @@ export class SequencerPublisher {
|
|
|
313
431
|
this.log.error(`Failed to publish bundled transactions (${actionsListStr})`, result);
|
|
314
432
|
return { failedActions: requests.map(r => r.action) };
|
|
315
433
|
} else {
|
|
316
|
-
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
434
|
+
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
435
|
+
result,
|
|
436
|
+
requests: requests.map(r => ({
|
|
437
|
+
...r,
|
|
438
|
+
// Avoid logging large blob data
|
|
439
|
+
blobConfig: r.blobConfig
|
|
440
|
+
? { ...r.blobConfig, blobs: r.blobConfig.blobs.map(b => ({ size: trimmedBytesLength(b) })) }
|
|
441
|
+
: undefined,
|
|
442
|
+
})),
|
|
443
|
+
});
|
|
317
444
|
const successfulActions: Action[] = [];
|
|
318
445
|
const failedActions: Action[] = [];
|
|
319
446
|
for (const request of requests) {
|
|
@@ -328,21 +455,25 @@ export class SequencerPublisher {
|
|
|
328
455
|
}
|
|
329
456
|
|
|
330
457
|
/**
|
|
331
|
-
* @notice Will call `
|
|
458
|
+
* @notice Will call `canProposeAt` to make sure that it is possible to propose
|
|
332
459
|
* @param tipArchive - The archive to check
|
|
333
460
|
* @returns The slot and block number if it is possible to propose, undefined otherwise
|
|
334
461
|
*/
|
|
335
|
-
public
|
|
462
|
+
public canProposeAt(
|
|
336
463
|
tipArchive: Fr,
|
|
337
464
|
msgSender: EthAddress,
|
|
338
|
-
opts: {
|
|
465
|
+
opts: { forcePendingCheckpointNumber?: CheckpointNumber; pipelined?: boolean } = {},
|
|
339
466
|
) {
|
|
340
467
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
341
468
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
342
469
|
|
|
470
|
+
const pipelined = opts.pipelined ?? false;
|
|
471
|
+
const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
|
|
472
|
+
const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
|
|
473
|
+
|
|
343
474
|
return this.rollupContract
|
|
344
|
-
.
|
|
345
|
-
forcePendingCheckpointNumber: opts.
|
|
475
|
+
.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
|
|
476
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
346
477
|
})
|
|
347
478
|
.catch(err => {
|
|
348
479
|
if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
|
|
@@ -361,7 +492,11 @@ export class SequencerPublisher {
|
|
|
361
492
|
* It will throw if the block header is invalid.
|
|
362
493
|
* @param header - The block header to validate
|
|
363
494
|
*/
|
|
364
|
-
|
|
495
|
+
@trackSpan('SequencerPublisher.validateBlockHeader')
|
|
496
|
+
public async validateBlockHeader(
|
|
497
|
+
header: CheckpointHeader,
|
|
498
|
+
opts?: { forcePendingCheckpointNumber: CheckpointNumber | undefined },
|
|
499
|
+
): Promise<void> {
|
|
365
500
|
const flags = { ignoreDA: true, ignoreSignatures: true };
|
|
366
501
|
|
|
367
502
|
const args = [
|
|
@@ -370,12 +505,14 @@ export class SequencerPublisher {
|
|
|
370
505
|
[], // no signers
|
|
371
506
|
Signature.empty().toViemSignature(),
|
|
372
507
|
`0x${'0'.repeat(64)}`, // 32 empty bytes
|
|
373
|
-
header.
|
|
508
|
+
header.blobsHash.toString(),
|
|
374
509
|
flags,
|
|
375
510
|
] as const;
|
|
376
511
|
|
|
377
|
-
const ts =
|
|
378
|
-
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
512
|
+
const ts = this.getSimulationTimestamp(header.slotNumber);
|
|
513
|
+
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
514
|
+
opts?.forcePendingCheckpointNumber,
|
|
515
|
+
);
|
|
379
516
|
let balance = 0n;
|
|
380
517
|
if (this.config.fishermanMode) {
|
|
381
518
|
// In fisherman mode, we can't know where the proposer is publishing from
|
|
@@ -395,84 +532,102 @@ export class SequencerPublisher {
|
|
|
395
532
|
data: encodeFunctionData({ abi: RollupAbi, functionName: 'validateHeaderWithAttestations', args }),
|
|
396
533
|
from: MULTI_CALL_3_ADDRESS,
|
|
397
534
|
},
|
|
398
|
-
{ time: ts
|
|
535
|
+
{ time: ts },
|
|
399
536
|
stateOverrides,
|
|
400
537
|
);
|
|
401
538
|
this.log.debug(`Simulated validateHeader`);
|
|
402
539
|
}
|
|
403
540
|
|
|
404
541
|
/**
|
|
405
|
-
* Simulate making a call to invalidate a
|
|
406
|
-
* @param
|
|
542
|
+
* Simulate making a call to invalidate a checkpoint with invalid attestations. Returns undefined if no need to invalidate.
|
|
543
|
+
* @param validationResult - The validation result indicating which checkpoint to invalidate (as returned by the archiver)
|
|
407
544
|
*/
|
|
408
|
-
public async
|
|
409
|
-
validationResult:
|
|
410
|
-
): Promise<
|
|
545
|
+
public async simulateInvalidateCheckpoint(
|
|
546
|
+
validationResult: ValidateCheckpointResult,
|
|
547
|
+
): Promise<InvalidateCheckpointRequest | undefined> {
|
|
411
548
|
if (validationResult.valid) {
|
|
412
549
|
return undefined;
|
|
413
550
|
}
|
|
414
551
|
|
|
415
|
-
const { reason,
|
|
416
|
-
const
|
|
417
|
-
const logData = { ...
|
|
552
|
+
const { reason, checkpoint } = validationResult;
|
|
553
|
+
const checkpointNumber = checkpoint.checkpointNumber;
|
|
554
|
+
const logData = { ...checkpoint, reason };
|
|
418
555
|
|
|
419
|
-
const
|
|
420
|
-
if (
|
|
556
|
+
const currentCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
557
|
+
if (currentCheckpointNumber < checkpointNumber) {
|
|
421
558
|
this.log.verbose(
|
|
422
|
-
`Skipping
|
|
423
|
-
{
|
|
559
|
+
`Skipping checkpoint ${checkpointNumber} invalidation since it has already been removed from the pending chain`,
|
|
560
|
+
{ currentCheckpointNumber, ...logData },
|
|
424
561
|
);
|
|
425
562
|
return undefined;
|
|
426
563
|
}
|
|
427
564
|
|
|
428
|
-
const request = this.
|
|
429
|
-
this.log.debug(`Simulating invalidate
|
|
565
|
+
const request = this.buildInvalidateCheckpointRequest(validationResult);
|
|
566
|
+
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
|
|
430
567
|
|
|
431
568
|
try {
|
|
432
|
-
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
433
|
-
|
|
569
|
+
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
570
|
+
request,
|
|
571
|
+
undefined,
|
|
572
|
+
undefined,
|
|
573
|
+
mergeAbis([request.abi ?? [], ErrorsAbi]),
|
|
574
|
+
);
|
|
575
|
+
this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} succeeded`, {
|
|
576
|
+
...logData,
|
|
577
|
+
request,
|
|
578
|
+
gasUsed,
|
|
579
|
+
});
|
|
434
580
|
|
|
435
|
-
return {
|
|
581
|
+
return {
|
|
582
|
+
request,
|
|
583
|
+
gasUsed,
|
|
584
|
+
checkpointNumber,
|
|
585
|
+
forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
|
|
586
|
+
reason,
|
|
587
|
+
};
|
|
436
588
|
} catch (err) {
|
|
437
589
|
const viemError = formatViemError(err);
|
|
438
590
|
|
|
439
|
-
// If the error is due to the
|
|
440
|
-
// we can safely ignore it and return undefined so we go ahead with
|
|
441
|
-
if (viemError.message?.includes('
|
|
591
|
+
// If the error is due to the checkpoint not being in the pending chain, and it was indeed removed by someone else,
|
|
592
|
+
// we can safely ignore it and return undefined so we go ahead with checkpoint building.
|
|
593
|
+
if (viemError.message?.includes('Rollup__CheckpointNotInPendingChain')) {
|
|
442
594
|
this.log.verbose(
|
|
443
|
-
`Simulation for invalidate
|
|
595
|
+
`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
|
|
444
596
|
{ ...logData, request, error: viemError.message },
|
|
445
597
|
);
|
|
446
|
-
const
|
|
447
|
-
if (
|
|
448
|
-
this.log.verbose(`
|
|
598
|
+
const latestPendingCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
599
|
+
if (latestPendingCheckpointNumber < checkpointNumber) {
|
|
600
|
+
this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, { ...logData });
|
|
449
601
|
return undefined;
|
|
450
602
|
} else {
|
|
451
603
|
this.log.error(
|
|
452
|
-
`Simulation for invalidate ${
|
|
604
|
+
`Simulation for invalidate checkpoint ${checkpointNumber} failed and it is still in pending chain`,
|
|
453
605
|
viemError,
|
|
454
606
|
logData,
|
|
455
607
|
);
|
|
456
|
-
throw new Error(
|
|
457
|
-
|
|
458
|
-
|
|
608
|
+
throw new Error(
|
|
609
|
+
`Failed to simulate invalidate checkpoint ${checkpointNumber} while it is still in pending chain`,
|
|
610
|
+
{
|
|
611
|
+
cause: viemError,
|
|
612
|
+
},
|
|
613
|
+
);
|
|
459
614
|
}
|
|
460
615
|
}
|
|
461
616
|
|
|
462
|
-
// Otherwise, throw. We cannot build the next
|
|
463
|
-
this.log.error(`Simulation for invalidate
|
|
464
|
-
throw new Error(`Failed to simulate invalidate
|
|
617
|
+
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
618
|
+
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
|
|
619
|
+
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
|
|
465
620
|
}
|
|
466
621
|
}
|
|
467
622
|
|
|
468
|
-
private
|
|
623
|
+
private buildInvalidateCheckpointRequest(validationResult: ValidateCheckpointResult) {
|
|
469
624
|
if (validationResult.valid) {
|
|
470
|
-
throw new Error('Cannot invalidate a valid
|
|
625
|
+
throw new Error('Cannot invalidate a valid checkpoint');
|
|
471
626
|
}
|
|
472
627
|
|
|
473
|
-
const {
|
|
474
|
-
const logData = { ...
|
|
475
|
-
this.log.debug(`
|
|
628
|
+
const { checkpoint, committee, reason } = validationResult;
|
|
629
|
+
const logData = { ...checkpoint, reason };
|
|
630
|
+
this.log.debug(`Building invalidate checkpoint ${checkpoint.checkpointNumber} request`, logData);
|
|
476
631
|
|
|
477
632
|
const attestationsAndSigners = new CommitteeAttestationsAndSigners(
|
|
478
633
|
validationResult.attestations,
|
|
@@ -480,14 +635,14 @@ export class SequencerPublisher {
|
|
|
480
635
|
|
|
481
636
|
if (reason === 'invalid-attestation') {
|
|
482
637
|
return this.rollupContract.buildInvalidateBadAttestationRequest(
|
|
483
|
-
|
|
638
|
+
checkpoint.checkpointNumber,
|
|
484
639
|
attestationsAndSigners,
|
|
485
640
|
committee,
|
|
486
641
|
validationResult.invalidIndex,
|
|
487
642
|
);
|
|
488
643
|
} else if (reason === 'insufficient-attestations') {
|
|
489
644
|
return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
|
|
490
|
-
|
|
645
|
+
checkpoint.checkpointNumber,
|
|
491
646
|
attestationsAndSigners,
|
|
492
647
|
committee,
|
|
493
648
|
);
|
|
@@ -497,47 +652,24 @@ export class SequencerPublisher {
|
|
|
497
652
|
}
|
|
498
653
|
}
|
|
499
654
|
|
|
500
|
-
/**
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
*
|
|
505
|
-
* @param block - The block to propose
|
|
506
|
-
* @param attestationData - The block's attestation data
|
|
507
|
-
*
|
|
508
|
-
*/
|
|
509
|
-
public async validateBlockForSubmission(
|
|
510
|
-
block: L2Block,
|
|
655
|
+
/** Simulates `propose` to make sure that the checkpoint is valid for submission */
|
|
656
|
+
@trackSpan('SequencerPublisher.validateCheckpointForSubmission')
|
|
657
|
+
public async validateCheckpointForSubmission(
|
|
658
|
+
checkpoint: Checkpoint,
|
|
511
659
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
512
660
|
attestationsAndSignersSignature: Signature,
|
|
513
|
-
options: {
|
|
514
|
-
): Promise<
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
// If we have no attestations, we still need to provide the empty attestations
|
|
518
|
-
// so that the committee is recalculated correctly
|
|
519
|
-
const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
520
|
-
if (ignoreSignatures) {
|
|
521
|
-
const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
|
|
522
|
-
if (!committee) {
|
|
523
|
-
this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
524
|
-
throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
525
|
-
}
|
|
526
|
-
attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
527
|
-
CommitteeAttestation.fromAddress(committeeMember),
|
|
528
|
-
);
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
const blobFields = block.getCheckpointBlobFields();
|
|
532
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
661
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
662
|
+
): Promise<void> {
|
|
663
|
+
const blobFields = checkpoint.toBlobFields();
|
|
664
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
533
665
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
534
666
|
|
|
535
667
|
const args = [
|
|
536
668
|
{
|
|
537
|
-
header:
|
|
538
|
-
archive: toHex(
|
|
669
|
+
header: checkpoint.header.toViem(),
|
|
670
|
+
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
539
671
|
oracleInput: {
|
|
540
|
-
feeAssetPriceModifier:
|
|
672
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
541
673
|
},
|
|
542
674
|
},
|
|
543
675
|
attestationsAndSigners.getPackedAttestations(),
|
|
@@ -546,13 +678,11 @@ export class SequencerPublisher {
|
|
|
546
678
|
blobInput,
|
|
547
679
|
] as const;
|
|
548
680
|
|
|
549
|
-
await this.simulateProposeTx(args,
|
|
550
|
-
return ts;
|
|
681
|
+
await this.simulateProposeTx(args, options);
|
|
551
682
|
}
|
|
552
683
|
|
|
553
684
|
private async enqueueCastSignalHelper(
|
|
554
685
|
slotNumber: SlotNumber,
|
|
555
|
-
timestamp: bigint,
|
|
556
686
|
signalType: GovernanceSignalAction,
|
|
557
687
|
payload: EthAddress,
|
|
558
688
|
base: IEmpireBase,
|
|
@@ -573,10 +703,45 @@ export class SequencerPublisher {
|
|
|
573
703
|
const round = await base.computeRound(slotNumber);
|
|
574
704
|
const roundInfo = await base.getRoundInfo(this.rollupContract.address, round);
|
|
575
705
|
|
|
706
|
+
if (roundInfo.quorumReached) {
|
|
707
|
+
return false;
|
|
708
|
+
}
|
|
709
|
+
|
|
576
710
|
if (roundInfo.lastSignalSlot >= slotNumber) {
|
|
577
711
|
return false;
|
|
578
712
|
}
|
|
579
713
|
|
|
714
|
+
if (await this.isPayloadEmpty(payload)) {
|
|
715
|
+
this.log.warn(`Skipping vote cast for payload with empty code`);
|
|
716
|
+
return false;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Check if payload was already submitted to governance
|
|
720
|
+
const cacheKey = payload.toString();
|
|
721
|
+
if (!this.payloadProposedCache.has(cacheKey)) {
|
|
722
|
+
try {
|
|
723
|
+
const l1StartBlock = await this.rollupContract.getL1StartBlock();
|
|
724
|
+
const proposed = await retry(
|
|
725
|
+
() => base.hasPayloadBeenProposed(payload.toString(), l1StartBlock),
|
|
726
|
+
'Check if payload was proposed',
|
|
727
|
+
makeBackoff([0, 1, 2]),
|
|
728
|
+
this.log,
|
|
729
|
+
true,
|
|
730
|
+
);
|
|
731
|
+
if (proposed) {
|
|
732
|
+
this.payloadProposedCache.add(cacheKey);
|
|
733
|
+
}
|
|
734
|
+
} catch (err) {
|
|
735
|
+
this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
|
|
736
|
+
return false;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (this.payloadProposedCache.has(cacheKey)) {
|
|
741
|
+
this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
|
|
742
|
+
return false;
|
|
743
|
+
}
|
|
744
|
+
|
|
580
745
|
const cachedLastVote = this.lastActions[signalType];
|
|
581
746
|
this.lastActions[signalType] = slotNumber;
|
|
582
747
|
const action = signalType;
|
|
@@ -595,11 +760,16 @@ export class SequencerPublisher {
|
|
|
595
760
|
lastValidL2Slot: slotNumber,
|
|
596
761
|
});
|
|
597
762
|
|
|
763
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
764
|
+
|
|
598
765
|
try {
|
|
599
|
-
await this.l1TxUtils.simulate(request, { time: timestamp }, [], ErrorsAbi);
|
|
766
|
+
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
600
767
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
601
768
|
} catch (err) {
|
|
602
|
-
|
|
769
|
+
const viemError = formatViemError(err);
|
|
770
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError, {
|
|
771
|
+
simulationTimestamp: timestamp,
|
|
772
|
+
});
|
|
603
773
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
604
774
|
}
|
|
605
775
|
|
|
@@ -619,14 +789,14 @@ export class SequencerPublisher {
|
|
|
619
789
|
const logData = { ...result, slotNumber, round, payload: payload.toString() };
|
|
620
790
|
if (!success) {
|
|
621
791
|
this.log.error(
|
|
622
|
-
`Signaling in
|
|
792
|
+
`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} failed`,
|
|
623
793
|
logData,
|
|
624
794
|
);
|
|
625
795
|
this.lastActions[signalType] = cachedLastVote;
|
|
626
796
|
return false;
|
|
627
797
|
} else {
|
|
628
798
|
this.log.info(
|
|
629
|
-
`Signaling in
|
|
799
|
+
`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} succeeded`,
|
|
630
800
|
logData,
|
|
631
801
|
);
|
|
632
802
|
return true;
|
|
@@ -636,22 +806,30 @@ export class SequencerPublisher {
|
|
|
636
806
|
return true;
|
|
637
807
|
}
|
|
638
808
|
|
|
809
|
+
private async isPayloadEmpty(payload: EthAddress): Promise<boolean> {
|
|
810
|
+
const key = payload.toString();
|
|
811
|
+
const cached = this.isPayloadEmptyCache.get(key);
|
|
812
|
+
if (cached) {
|
|
813
|
+
return cached;
|
|
814
|
+
}
|
|
815
|
+
const isEmpty = !(await this.l1TxUtils.getCode(payload));
|
|
816
|
+
this.isPayloadEmptyCache.set(key, isEmpty);
|
|
817
|
+
return isEmpty;
|
|
818
|
+
}
|
|
819
|
+
|
|
639
820
|
/**
|
|
640
821
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
641
822
|
* @param slotNumber - The slot number to cast a signal for.
|
|
642
|
-
* @param timestamp - The timestamp of the slot to cast a signal for.
|
|
643
823
|
* @returns True if the signal was successfully enqueued, false otherwise.
|
|
644
824
|
*/
|
|
645
825
|
public enqueueGovernanceCastSignal(
|
|
646
826
|
governancePayload: EthAddress,
|
|
647
827
|
slotNumber: SlotNumber,
|
|
648
|
-
timestamp: bigint,
|
|
649
828
|
signerAddress: EthAddress,
|
|
650
829
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
651
830
|
): Promise<boolean> {
|
|
652
831
|
return this.enqueueCastSignalHelper(
|
|
653
832
|
slotNumber,
|
|
654
|
-
timestamp,
|
|
655
833
|
'governance-signal',
|
|
656
834
|
governancePayload,
|
|
657
835
|
this.govProposerContract,
|
|
@@ -664,7 +842,6 @@ export class SequencerPublisher {
|
|
|
664
842
|
public async enqueueSlashingActions(
|
|
665
843
|
actions: ProposerSlashAction[],
|
|
666
844
|
slotNumber: SlotNumber,
|
|
667
|
-
timestamp: bigint,
|
|
668
845
|
signerAddress: EthAddress,
|
|
669
846
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
670
847
|
): Promise<boolean> {
|
|
@@ -685,7 +862,6 @@ export class SequencerPublisher {
|
|
|
685
862
|
});
|
|
686
863
|
await this.enqueueCastSignalHelper(
|
|
687
864
|
slotNumber,
|
|
688
|
-
timestamp,
|
|
689
865
|
'empire-slashing-signal',
|
|
690
866
|
action.payload,
|
|
691
867
|
this.slashingProposerContract,
|
|
@@ -704,7 +880,6 @@ export class SequencerPublisher {
|
|
|
704
880
|
(receipt: TransactionReceipt) =>
|
|
705
881
|
!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs),
|
|
706
882
|
slotNumber,
|
|
707
|
-
timestamp,
|
|
708
883
|
);
|
|
709
884
|
break;
|
|
710
885
|
}
|
|
@@ -722,7 +897,6 @@ export class SequencerPublisher {
|
|
|
722
897
|
request,
|
|
723
898
|
(receipt: TransactionReceipt) => !!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs),
|
|
724
899
|
slotNumber,
|
|
725
|
-
timestamp,
|
|
726
900
|
);
|
|
727
901
|
break;
|
|
728
902
|
}
|
|
@@ -746,7 +920,6 @@ export class SequencerPublisher {
|
|
|
746
920
|
request,
|
|
747
921
|
(receipt: TransactionReceipt) => !!tallySlashingProposer.tryExtractVoteCastEvent(receipt.logs),
|
|
748
922
|
slotNumber,
|
|
749
|
-
timestamp,
|
|
750
923
|
);
|
|
751
924
|
break;
|
|
752
925
|
}
|
|
@@ -768,7 +941,6 @@ export class SequencerPublisher {
|
|
|
768
941
|
request,
|
|
769
942
|
(receipt: TransactionReceipt) => !!tallySlashingProposer.tryExtractRoundExecutedEvent(receipt.logs),
|
|
770
943
|
slotNumber,
|
|
771
|
-
timestamp,
|
|
772
944
|
);
|
|
773
945
|
break;
|
|
774
946
|
}
|
|
@@ -783,56 +955,56 @@ export class SequencerPublisher {
|
|
|
783
955
|
return true;
|
|
784
956
|
}
|
|
785
957
|
|
|
786
|
-
/**
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
* @param block - L2 block to propose.
|
|
790
|
-
* @returns True if the tx has been enqueued, throws otherwise. See #9315
|
|
791
|
-
*/
|
|
792
|
-
public async enqueueProposeL2Block(
|
|
793
|
-
block: L2Block,
|
|
958
|
+
/** Simulates and enqueues a proposal for a checkpoint on L1 */
|
|
959
|
+
public async enqueueProposeCheckpoint(
|
|
960
|
+
checkpoint: Checkpoint,
|
|
794
961
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
795
962
|
attestationsAndSignersSignature: Signature,
|
|
796
|
-
opts: { txTimeoutAt?: Date;
|
|
797
|
-
): Promise<
|
|
798
|
-
const checkpointHeader =
|
|
963
|
+
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
964
|
+
): Promise<void> {
|
|
965
|
+
const checkpointHeader = checkpoint.header;
|
|
799
966
|
|
|
800
|
-
const blobFields =
|
|
801
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
967
|
+
const blobFields = checkpoint.toBlobFields();
|
|
968
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
802
969
|
|
|
803
|
-
const proposeTxArgs = {
|
|
970
|
+
const proposeTxArgs: L1ProcessArgs = {
|
|
804
971
|
header: checkpointHeader,
|
|
805
|
-
archive:
|
|
806
|
-
body: block.body.toBuffer(),
|
|
972
|
+
archive: checkpoint.archive.root.toBuffer(),
|
|
807
973
|
blobs,
|
|
808
974
|
attestationsAndSigners,
|
|
809
975
|
attestationsAndSignersSignature,
|
|
976
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
810
977
|
};
|
|
811
978
|
|
|
812
|
-
let ts: bigint;
|
|
813
|
-
|
|
814
979
|
try {
|
|
815
980
|
// @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
|
|
816
981
|
// This means that we can avoid the simulation issues in later checks.
|
|
817
982
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
818
983
|
// make time consistency checks break.
|
|
819
984
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
820
|
-
|
|
985
|
+
await this.validateCheckpointForSubmission(
|
|
986
|
+
checkpoint,
|
|
987
|
+
attestationsAndSigners,
|
|
988
|
+
attestationsAndSignersSignature,
|
|
989
|
+
opts,
|
|
990
|
+
);
|
|
821
991
|
} catch (err: any) {
|
|
822
|
-
this.log.error(`
|
|
823
|
-
...
|
|
824
|
-
slotNumber:
|
|
825
|
-
|
|
992
|
+
this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
993
|
+
...checkpoint.getStats(),
|
|
994
|
+
slotNumber: checkpoint.header.slotNumber,
|
|
995
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
826
996
|
});
|
|
827
997
|
throw err;
|
|
828
998
|
}
|
|
829
999
|
|
|
830
|
-
this.log.verbose(`Enqueuing
|
|
831
|
-
await this.addProposeTx(
|
|
832
|
-
return true;
|
|
1000
|
+
this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
|
|
1001
|
+
await this.addProposeTx(checkpoint, proposeTxArgs, opts);
|
|
833
1002
|
}
|
|
834
1003
|
|
|
835
|
-
public
|
|
1004
|
+
public enqueueInvalidateCheckpoint(
|
|
1005
|
+
request: InvalidateCheckpointRequest | undefined,
|
|
1006
|
+
opts: { txTimeoutAt?: Date } = {},
|
|
1007
|
+
) {
|
|
836
1008
|
if (!request) {
|
|
837
1009
|
return;
|
|
838
1010
|
}
|
|
@@ -840,9 +1012,9 @@ export class SequencerPublisher {
|
|
|
840
1012
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
841
1013
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(request.gasUsed) * 64) / 63)));
|
|
842
1014
|
|
|
843
|
-
const { gasUsed,
|
|
844
|
-
const logData = { gasUsed,
|
|
845
|
-
this.log.verbose(`Enqueuing invalidate
|
|
1015
|
+
const { gasUsed, checkpointNumber } = request;
|
|
1016
|
+
const logData = { gasUsed, checkpointNumber, gasLimit, opts };
|
|
1017
|
+
this.log.verbose(`Enqueuing invalidate checkpoint request`, logData);
|
|
846
1018
|
this.addRequest({
|
|
847
1019
|
action: `invalidate-by-${request.reason}`,
|
|
848
1020
|
request: request.request,
|
|
@@ -855,9 +1027,9 @@ export class SequencerPublisher {
|
|
|
855
1027
|
result.receipt.status === 'success' &&
|
|
856
1028
|
tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointInvalidated');
|
|
857
1029
|
if (!success) {
|
|
858
|
-
this.log.warn(`Invalidate
|
|
1030
|
+
this.log.warn(`Invalidate checkpoint ${request.checkpointNumber} failed`, { ...result, ...logData });
|
|
859
1031
|
} else {
|
|
860
|
-
this.log.info(`Invalidate
|
|
1032
|
+
this.log.info(`Invalidate checkpoint ${request.checkpointNumber} succeeded`, { ...result, ...logData });
|
|
861
1033
|
}
|
|
862
1034
|
return !!success;
|
|
863
1035
|
},
|
|
@@ -869,8 +1041,8 @@ export class SequencerPublisher {
|
|
|
869
1041
|
request: L1TxRequest,
|
|
870
1042
|
checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
|
|
871
1043
|
slotNumber: SlotNumber,
|
|
872
|
-
timestamp: bigint,
|
|
873
1044
|
) {
|
|
1045
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
874
1046
|
const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
|
|
875
1047
|
if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
|
|
876
1048
|
this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
|
|
@@ -883,12 +1055,15 @@ export class SequencerPublisher {
|
|
|
883
1055
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
884
1056
|
|
|
885
1057
|
let gasUsed: bigint;
|
|
1058
|
+
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
1059
|
+
|
|
886
1060
|
try {
|
|
887
|
-
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [],
|
|
1061
|
+
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi));
|
|
888
1062
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
889
1063
|
} catch (err) {
|
|
890
|
-
const viemError = formatViemError(err);
|
|
1064
|
+
const viemError = formatViemError(err, simulateAbi);
|
|
891
1065
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1066
|
+
|
|
892
1067
|
return false;
|
|
893
1068
|
}
|
|
894
1069
|
|
|
@@ -896,10 +1071,14 @@ export class SequencerPublisher {
|
|
|
896
1071
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(gasUsed) * 64) / 63)));
|
|
897
1072
|
logData.gasLimit = gasLimit;
|
|
898
1073
|
|
|
1074
|
+
// Store the ABI used for simulation on the request so Multicall3.forward can decode errors
|
|
1075
|
+
// when the tx is sent and a revert is diagnosed via simulation.
|
|
1076
|
+
const requestWithAbi = { ...request, abi: simulateAbi };
|
|
1077
|
+
|
|
899
1078
|
this.log.debug(`Enqueuing ${action}`, logData);
|
|
900
1079
|
this.addRequest({
|
|
901
1080
|
action,
|
|
902
|
-
request,
|
|
1081
|
+
request: requestWithAbi,
|
|
903
1082
|
gasConfig: { gasLimit },
|
|
904
1083
|
lastValidL2Slot: slotNumber,
|
|
905
1084
|
checkSuccess: (_req, result) => {
|
|
@@ -935,8 +1114,7 @@ export class SequencerPublisher {
|
|
|
935
1114
|
|
|
936
1115
|
private async prepareProposeTx(
|
|
937
1116
|
encodedData: L1ProcessArgs,
|
|
938
|
-
|
|
939
|
-
options: { forcePendingBlockNumber?: number },
|
|
1117
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
940
1118
|
) {
|
|
941
1119
|
const kzg = Blob.getViemKzgInstance();
|
|
942
1120
|
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
@@ -981,8 +1159,7 @@ export class SequencerPublisher {
|
|
|
981
1159
|
header: encodedData.header.toViem(),
|
|
982
1160
|
archive: toHex(encodedData.archive),
|
|
983
1161
|
oracleInput: {
|
|
984
|
-
|
|
985
|
-
feeAssetPriceModifier: 0n,
|
|
1162
|
+
feeAssetPriceModifier: encodedData.feeAssetPriceModifier,
|
|
986
1163
|
},
|
|
987
1164
|
},
|
|
988
1165
|
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
@@ -991,7 +1168,7 @@ export class SequencerPublisher {
|
|
|
991
1168
|
blobInput,
|
|
992
1169
|
] as const;
|
|
993
1170
|
|
|
994
|
-
const { rollupData, simulationResult } = await this.simulateProposeTx(args,
|
|
1171
|
+
const { rollupData, simulationResult } = await this.simulateProposeTx(args, options);
|
|
995
1172
|
|
|
996
1173
|
return { args, blobEvaluationGas, rollupData, simulationResult };
|
|
997
1174
|
}
|
|
@@ -999,7 +1176,6 @@ export class SequencerPublisher {
|
|
|
999
1176
|
/**
|
|
1000
1177
|
* Simulates the propose tx with eth_simulateV1
|
|
1001
1178
|
* @param args - The propose tx args
|
|
1002
|
-
* @param timestamp - The timestamp to simulate proposal at
|
|
1003
1179
|
* @returns The simulation result
|
|
1004
1180
|
*/
|
|
1005
1181
|
private async simulateProposeTx(
|
|
@@ -1008,7 +1184,7 @@ export class SequencerPublisher {
|
|
|
1008
1184
|
readonly header: ViemHeader;
|
|
1009
1185
|
readonly archive: `0x${string}`;
|
|
1010
1186
|
readonly oracleInput: {
|
|
1011
|
-
readonly feeAssetPriceModifier:
|
|
1187
|
+
readonly feeAssetPriceModifier: bigint;
|
|
1012
1188
|
};
|
|
1013
1189
|
},
|
|
1014
1190
|
ViemCommitteeAttestations,
|
|
@@ -1016,8 +1192,7 @@ export class SequencerPublisher {
|
|
|
1016
1192
|
ViemSignature,
|
|
1017
1193
|
`0x${string}`,
|
|
1018
1194
|
],
|
|
1019
|
-
|
|
1020
|
-
options: { forcePendingBlockNumber?: number },
|
|
1195
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1021
1196
|
) {
|
|
1022
1197
|
const rollupData = encodeFunctionData({
|
|
1023
1198
|
abi: RollupAbi,
|
|
@@ -1025,10 +1200,10 @@ export class SequencerPublisher {
|
|
|
1025
1200
|
args,
|
|
1026
1201
|
});
|
|
1027
1202
|
|
|
1028
|
-
// override the pending
|
|
1029
|
-
const
|
|
1030
|
-
options.
|
|
1031
|
-
? await this.rollupContract.makePendingCheckpointNumberOverride(options.
|
|
1203
|
+
// override the pending checkpoint number if requested
|
|
1204
|
+
const forcePendingCheckpointNumberStateDiff = (
|
|
1205
|
+
options.forcePendingCheckpointNumber !== undefined
|
|
1206
|
+
? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber)
|
|
1032
1207
|
: []
|
|
1033
1208
|
).flatMap(override => override.stateDiff ?? []);
|
|
1034
1209
|
|
|
@@ -1038,7 +1213,7 @@ export class SequencerPublisher {
|
|
|
1038
1213
|
// @note we override checkBlob to false since blobs are not part simulate()
|
|
1039
1214
|
stateDiff: [
|
|
1040
1215
|
{ slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
|
|
1041
|
-
...
|
|
1216
|
+
...forcePendingCheckpointNumberStateDiff,
|
|
1042
1217
|
],
|
|
1043
1218
|
},
|
|
1044
1219
|
];
|
|
@@ -1050,25 +1225,26 @@ export class SequencerPublisher {
|
|
|
1050
1225
|
});
|
|
1051
1226
|
}
|
|
1052
1227
|
|
|
1228
|
+
const simTs = this.getSimulationTimestamp(SlotNumber.fromBigInt(args[0].header.slotNumber));
|
|
1229
|
+
|
|
1053
1230
|
const simulationResult = await this.l1TxUtils
|
|
1054
1231
|
.simulate(
|
|
1055
1232
|
{
|
|
1056
1233
|
to: this.rollupContract.address,
|
|
1057
1234
|
data: rollupData,
|
|
1058
|
-
gas:
|
|
1235
|
+
gas: MAX_L1_TX_LIMIT,
|
|
1059
1236
|
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1060
1237
|
},
|
|
1061
1238
|
{
|
|
1062
|
-
|
|
1063
|
-
time: timestamp + 1n,
|
|
1239
|
+
time: simTs,
|
|
1064
1240
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1065
|
-
gasLimit:
|
|
1241
|
+
gasLimit: MAX_L1_TX_LIMIT * 2n,
|
|
1066
1242
|
},
|
|
1067
1243
|
stateOverrides,
|
|
1068
1244
|
RollupAbi,
|
|
1069
1245
|
{
|
|
1070
1246
|
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
1071
|
-
fallbackGasEstimate:
|
|
1247
|
+
fallbackGasEstimate: MAX_L1_TX_LIMIT,
|
|
1072
1248
|
},
|
|
1073
1249
|
)
|
|
1074
1250
|
.catch(err => {
|
|
@@ -1078,11 +1254,11 @@ export class SequencerPublisher {
|
|
|
1078
1254
|
this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
|
|
1079
1255
|
// Return a minimal simulation result with the fallback gas estimate
|
|
1080
1256
|
return {
|
|
1081
|
-
gasUsed:
|
|
1257
|
+
gasUsed: MAX_L1_TX_LIMIT,
|
|
1082
1258
|
logs: [],
|
|
1083
1259
|
};
|
|
1084
1260
|
}
|
|
1085
|
-
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1261
|
+
this.log.error(`Failed to simulate propose tx`, viemError, { simulationTimestamp: simTs });
|
|
1086
1262
|
throw err;
|
|
1087
1263
|
});
|
|
1088
1264
|
|
|
@@ -1090,18 +1266,14 @@ export class SequencerPublisher {
|
|
|
1090
1266
|
}
|
|
1091
1267
|
|
|
1092
1268
|
private async addProposeTx(
|
|
1093
|
-
|
|
1269
|
+
checkpoint: Checkpoint,
|
|
1094
1270
|
encodedData: L1ProcessArgs,
|
|
1095
|
-
opts: { txTimeoutAt?: Date;
|
|
1096
|
-
timestamp: bigint,
|
|
1271
|
+
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
1097
1272
|
): Promise<void> {
|
|
1273
|
+
const slot = checkpoint.header.slotNumber;
|
|
1098
1274
|
const timer = new Timer();
|
|
1099
1275
|
const kzg = Blob.getViemKzgInstance();
|
|
1100
|
-
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
1101
|
-
encodedData,
|
|
1102
|
-
timestamp,
|
|
1103
|
-
opts,
|
|
1104
|
-
);
|
|
1276
|
+
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, opts);
|
|
1105
1277
|
const startBlock = await this.l1TxUtils.getBlockNumber();
|
|
1106
1278
|
const gasLimit = this.l1TxUtils.bumpGasLimit(
|
|
1107
1279
|
BigInt(Math.ceil((Number(simulationResult.gasUsed) * 64) / 63)) +
|
|
@@ -1109,11 +1281,13 @@ export class SequencerPublisher {
|
|
|
1109
1281
|
SequencerPublisher.MULTICALL_OVERHEAD_GAS_GUESS, // We issue the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
1110
1282
|
);
|
|
1111
1283
|
|
|
1112
|
-
// Send the blobs to the blob
|
|
1113
|
-
// tx fails but it does get mined. We make sure that the blobs are sent to the blob
|
|
1114
|
-
void
|
|
1115
|
-
this.
|
|
1116
|
-
|
|
1284
|
+
// Send the blobs to the blob client preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
|
|
1285
|
+
// tx fails but it does get mined. We make sure that the blobs are sent to the blob client regardless of the tx outcome.
|
|
1286
|
+
void Promise.resolve().then(() =>
|
|
1287
|
+
this.blobClient.sendBlobsToFilestore(encodedData.blobs).catch(_err => {
|
|
1288
|
+
this.log.error('Failed to send blobs to blob client');
|
|
1289
|
+
}),
|
|
1290
|
+
);
|
|
1117
1291
|
|
|
1118
1292
|
return this.addRequest({
|
|
1119
1293
|
action: 'propose',
|
|
@@ -1121,7 +1295,7 @@ export class SequencerPublisher {
|
|
|
1121
1295
|
to: this.rollupContract.address,
|
|
1122
1296
|
data: rollupData,
|
|
1123
1297
|
},
|
|
1124
|
-
lastValidL2Slot:
|
|
1298
|
+
lastValidL2Slot: checkpoint.header.slotNumber,
|
|
1125
1299
|
gasConfig: { ...opts, gasLimit },
|
|
1126
1300
|
blobConfig: {
|
|
1127
1301
|
blobs: encodedData.blobs.map(b => b.data),
|
|
@@ -1136,11 +1310,12 @@ export class SequencerPublisher {
|
|
|
1136
1310
|
receipt &&
|
|
1137
1311
|
receipt.status === 'success' &&
|
|
1138
1312
|
tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointProposed');
|
|
1313
|
+
|
|
1139
1314
|
if (success) {
|
|
1140
1315
|
const endBlock = receipt.blockNumber;
|
|
1141
1316
|
const inclusionBlocks = Number(endBlock - startBlock);
|
|
1142
1317
|
const { calldataGas, calldataSize, sender } = stats!;
|
|
1143
|
-
const publishStats:
|
|
1318
|
+
const publishStats: L1PublishCheckpointStats = {
|
|
1144
1319
|
gasPrice: receipt.effectiveGasPrice,
|
|
1145
1320
|
gasUsed: receipt.gasUsed,
|
|
1146
1321
|
blobGasUsed: receipt.blobGasUsed ?? 0n,
|
|
@@ -1149,26 +1324,42 @@ export class SequencerPublisher {
|
|
|
1149
1324
|
calldataGas,
|
|
1150
1325
|
calldataSize,
|
|
1151
1326
|
sender,
|
|
1152
|
-
...
|
|
1327
|
+
...checkpoint.getStats(),
|
|
1153
1328
|
eventName: 'rollup-published-to-l1',
|
|
1154
1329
|
blobCount: encodedData.blobs.length,
|
|
1155
1330
|
inclusionBlocks,
|
|
1156
1331
|
};
|
|
1157
|
-
this.log.info(`Published
|
|
1332
|
+
this.log.info(`Published checkpoint ${checkpoint.number} at slot ${slot} to rollup contract`, {
|
|
1333
|
+
...stats,
|
|
1334
|
+
...checkpoint.getStats(),
|
|
1335
|
+
...pick(receipt, 'transactionHash', 'blockHash'),
|
|
1336
|
+
});
|
|
1158
1337
|
this.metrics.recordProcessBlockTx(timer.ms(), publishStats);
|
|
1159
1338
|
|
|
1160
1339
|
return true;
|
|
1161
1340
|
} else {
|
|
1162
1341
|
this.metrics.recordFailedTx('process');
|
|
1163
|
-
this.log.error(
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
});
|
|
1342
|
+
this.log.error(
|
|
1343
|
+
`Publishing checkpoint at slot ${slot} failed with ${errorMsg ?? 'no error message'}`,
|
|
1344
|
+
undefined,
|
|
1345
|
+
{ ...checkpoint.getStats(), ...receipt },
|
|
1346
|
+
);
|
|
1169
1347
|
return false;
|
|
1170
1348
|
}
|
|
1171
1349
|
},
|
|
1172
1350
|
});
|
|
1173
1351
|
}
|
|
1352
|
+
|
|
1353
|
+
/** Returns the timestamp of the last L1 slot within a given L2 slot. Used as the simulation timestamp
|
|
1354
|
+
* for eth_simulateV1 calls, since it's guaranteed to be greater than any L1 block produced during the slot. */
|
|
1355
|
+
private getSimulationTimestamp(slot: SlotNumber): bigint {
|
|
1356
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1357
|
+
return getLastL1SlotTimestampForL2Slot(slot, l1Constants);
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
/** Returns the timestamp of the next L1 slot boundary after now. */
|
|
1361
|
+
private getNextL1SlotTimestamp(): bigint {
|
|
1362
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1363
|
+
return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
|
|
1364
|
+
}
|
|
1174
1365
|
}
|