@aztec/sequencer-client 0.0.1-fake-c83136db25 → 0.0.2-commit.217f559981
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/index.d.ts +1 -1
- package/dest/client/sequencer-client.d.ts +21 -16
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +45 -26
- package/dest/config.d.ts +14 -8
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +90 -33
- package/dest/global_variable_builder/global_builder.d.ts +20 -16
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +52 -39
- package/dest/global_variable_builder/index.d.ts +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 +39 -20
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +104 -34
- package/dest/publisher/index.d.ts +1 -1
- 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 +89 -67
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +722 -178
- 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 +1213 -0
- package/dest/sequencer/checkpoint_voter.d.ts +35 -0
- package/dest/sequencer/checkpoint_voter.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_voter.js +109 -0
- package/dest/sequencer/config.d.ts +3 -2
- package/dest/sequencer/config.d.ts.map +1 -1
- package/dest/sequencer/errors.d.ts +1 -1
- package/dest/sequencer/errors.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 +44 -3
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +232 -50
- package/dest/sequencer/sequencer.d.ts +122 -144
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +740 -525
- package/dest/sequencer/timetable.d.ts +51 -14
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +145 -59
- 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 +97 -0
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -0
- package/dest/test/mock_checkpoint_builder.js +222 -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 +33 -30
- package/src/client/sequencer-client.ts +54 -47
- package/src/config.ts +103 -42
- package/src/global_variable_builder/global_builder.ts +67 -59
- package/src/index.ts +1 -7
- package/src/publisher/config.ts +130 -50
- package/src/publisher/sequencer-publisher-factory.ts +30 -11
- package/src/publisher/sequencer-publisher-metrics.ts +19 -71
- package/src/publisher/sequencer-publisher.ts +461 -232
- package/src/sequencer/README.md +531 -0
- package/src/sequencer/checkpoint_proposal_job.ts +914 -0
- package/src/sequencer/checkpoint_voter.ts +130 -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 +296 -61
- package/src/sequencer/sequencer.ts +494 -717
- package/src/sequencer/timetable.ts +175 -80
- 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 +320 -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 -130
- 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 -218
- package/src/tx_validator/nullifier_cache.ts +0 -30
- package/src/tx_validator/tx_validator_factory.ts +0 -132
|
@@ -1,48 +1,53 @@
|
|
|
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,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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,
|
|
25
|
+
WEI_CONST,
|
|
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';
|
|
30
|
+
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
31
|
+
import { pick } from '@aztec/foundation/collection';
|
|
32
|
+
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
28
33
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
29
34
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
30
|
-
import type { Fr } from '@aztec/foundation/fields';
|
|
31
35
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
36
|
+
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
32
37
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
33
38
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
34
39
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
35
40
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
36
|
-
import {
|
|
41
|
+
import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
42
|
+
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
37
43
|
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
38
44
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
39
|
-
import type {
|
|
40
|
-
import {
|
|
41
|
-
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
45
|
+
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
46
|
+
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
42
47
|
|
|
43
|
-
import { type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
|
|
48
|
+
import { type StateOverride, type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
|
|
44
49
|
|
|
45
|
-
import type {
|
|
50
|
+
import type { SequencerPublisherConfig } from './config.js';
|
|
46
51
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
47
52
|
|
|
48
53
|
/** Arguments to the process method of the rollup contract */
|
|
@@ -51,14 +56,14 @@ type L1ProcessArgs = {
|
|
|
51
56
|
header: CheckpointHeader;
|
|
52
57
|
/** A root of the archive tree after the L2 block is applied. */
|
|
53
58
|
archive: Buffer;
|
|
54
|
-
/** State reference after the L2 block is applied. */
|
|
55
|
-
stateReference: StateReference;
|
|
56
59
|
/** L2 block blobs containing all tx effects. */
|
|
57
60
|
blobs: Blob[];
|
|
58
61
|
/** Attestations */
|
|
59
62
|
attestationsAndSigners: CommitteeAttestationsAndSigners;
|
|
60
63
|
/** Attestations and signers signature */
|
|
61
64
|
attestationsAndSignersSignature: Signature;
|
|
65
|
+
/** The fee asset price modifier in basis points (from oracle) */
|
|
66
|
+
feeAssetPriceModifier: bigint;
|
|
62
67
|
};
|
|
63
68
|
|
|
64
69
|
export const Actions = [
|
|
@@ -80,18 +85,18 @@ type GovernanceSignalAction = Extract<Action, 'governance-signal' | 'empire-slas
|
|
|
80
85
|
// Sorting for actions such that invalidations go before proposals, and proposals go before votes
|
|
81
86
|
export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
|
|
82
87
|
|
|
83
|
-
export type
|
|
88
|
+
export type InvalidateCheckpointRequest = {
|
|
84
89
|
request: L1TxRequest;
|
|
85
90
|
reason: 'invalid-attestation' | 'insufficient-attestations';
|
|
86
91
|
gasUsed: bigint;
|
|
87
|
-
|
|
88
|
-
|
|
92
|
+
checkpointNumber: CheckpointNumber;
|
|
93
|
+
forcePendingCheckpointNumber: CheckpointNumber;
|
|
89
94
|
};
|
|
90
95
|
|
|
91
96
|
interface RequestWithExpiry {
|
|
92
97
|
action: Action;
|
|
93
98
|
request: L1TxRequest;
|
|
94
|
-
lastValidL2Slot:
|
|
99
|
+
lastValidL2Slot: SlotNumber;
|
|
95
100
|
gasConfig?: Pick<L1TxConfig, 'txTimeoutAt' | 'gasLimit'>;
|
|
96
101
|
blobConfig?: L1BlobInputs;
|
|
97
102
|
checkSuccess: (
|
|
@@ -108,16 +113,24 @@ export class SequencerPublisher {
|
|
|
108
113
|
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
109
114
|
protected slashingLog = createLogger('sequencer:publisher:slashing');
|
|
110
115
|
|
|
111
|
-
protected lastActions: Partial<Record<Action,
|
|
116
|
+
protected lastActions: Partial<Record<Action, SlotNumber>> = {};
|
|
117
|
+
|
|
118
|
+
private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
|
|
119
|
+
private payloadProposedCache: Set<string> = new Set<string>();
|
|
112
120
|
|
|
113
121
|
protected log: Logger;
|
|
114
122
|
protected ethereumSlotDuration: bigint;
|
|
115
123
|
|
|
116
|
-
private
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
124
|
+
private blobClient: BlobClientInterface;
|
|
125
|
+
|
|
126
|
+
/** Address to use for simulations in fisherman mode (actual proposer's address) */
|
|
127
|
+
private proposerAddressForSimulation?: EthAddress;
|
|
128
|
+
|
|
129
|
+
/** L1 fee analyzer for fisherman mode */
|
|
130
|
+
private l1FeeAnalyzer?: L1FeeAnalyzer;
|
|
131
|
+
|
|
132
|
+
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */
|
|
133
|
+
private feeAssetPriceOracle: FeeAssetPriceOracle;
|
|
121
134
|
|
|
122
135
|
// A CALL to a cold address is 2700 gas
|
|
123
136
|
public static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
@@ -125,20 +138,23 @@ export class SequencerPublisher {
|
|
|
125
138
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
126
139
|
public static VOTE_GAS_GUESS: bigint = 800_000n;
|
|
127
140
|
|
|
128
|
-
public l1TxUtils:
|
|
141
|
+
public l1TxUtils: L1TxUtils;
|
|
129
142
|
public rollupContract: RollupContract;
|
|
130
143
|
public govProposerContract: GovernanceProposerContract;
|
|
131
144
|
public slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
132
145
|
public slashFactoryContract: SlashFactoryContract;
|
|
133
146
|
|
|
147
|
+
public readonly tracer: Tracer;
|
|
148
|
+
|
|
134
149
|
protected requests: RequestWithExpiry[] = [];
|
|
135
150
|
|
|
136
151
|
constructor(
|
|
137
|
-
private config:
|
|
152
|
+
private config: Pick<SequencerPublisherConfig, 'fishermanMode'> &
|
|
153
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration'> & { l1ChainId: number },
|
|
138
154
|
deps: {
|
|
139
155
|
telemetry?: TelemetryClient;
|
|
140
|
-
|
|
141
|
-
l1TxUtils:
|
|
156
|
+
blobClient: BlobClientInterface;
|
|
157
|
+
l1TxUtils: L1TxUtils;
|
|
142
158
|
rollupContract: RollupContract;
|
|
143
159
|
slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
144
160
|
governanceProposerContract: GovernanceProposerContract;
|
|
@@ -146,7 +162,7 @@ export class SequencerPublisher {
|
|
|
146
162
|
epochCache: EpochCache;
|
|
147
163
|
dateProvider: DateProvider;
|
|
148
164
|
metrics: SequencerPublisherMetrics;
|
|
149
|
-
lastActions: Partial<Record<Action,
|
|
165
|
+
lastActions: Partial<Record<Action, SlotNumber>>;
|
|
150
166
|
log?: Logger;
|
|
151
167
|
},
|
|
152
168
|
) {
|
|
@@ -155,11 +171,11 @@ export class SequencerPublisher {
|
|
|
155
171
|
this.epochCache = deps.epochCache;
|
|
156
172
|
this.lastActions = deps.lastActions;
|
|
157
173
|
|
|
158
|
-
this.
|
|
159
|
-
deps.blobSinkClient ?? createBlobSinkClient(config, { logger: createLogger('sequencer:blob-sink:client') });
|
|
174
|
+
this.blobClient = deps.blobClient;
|
|
160
175
|
|
|
161
176
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
162
177
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
178
|
+
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
163
179
|
this.l1TxUtils = deps.l1TxUtils;
|
|
164
180
|
|
|
165
181
|
this.rollupContract = deps.rollupContract;
|
|
@@ -173,24 +189,130 @@ export class SequencerPublisher {
|
|
|
173
189
|
this.slashingProposerContract = newSlashingProposer;
|
|
174
190
|
});
|
|
175
191
|
this.slashFactoryContract = deps.slashFactoryContract;
|
|
192
|
+
|
|
193
|
+
// Initialize L1 fee analyzer for fisherman mode
|
|
194
|
+
if (config.fishermanMode) {
|
|
195
|
+
this.l1FeeAnalyzer = new L1FeeAnalyzer(
|
|
196
|
+
this.l1TxUtils.client,
|
|
197
|
+
deps.dateProvider,
|
|
198
|
+
createLogger('sequencer:publisher:fee-analyzer'),
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Initialize fee asset price oracle
|
|
203
|
+
this.feeAssetPriceOracle = new FeeAssetPriceOracle(
|
|
204
|
+
this.l1TxUtils.client,
|
|
205
|
+
this.rollupContract,
|
|
206
|
+
createLogger('sequencer:publisher:price-oracle'),
|
|
207
|
+
);
|
|
176
208
|
}
|
|
177
209
|
|
|
178
210
|
public getRollupContract(): RollupContract {
|
|
179
211
|
return this.rollupContract;
|
|
180
212
|
}
|
|
181
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Gets the fee asset price modifier from the oracle.
|
|
216
|
+
* Returns 0n if the oracle query fails.
|
|
217
|
+
*/
|
|
218
|
+
public getFeeAssetPriceModifier(): Promise<bigint> {
|
|
219
|
+
return this.feeAssetPriceOracle.computePriceModifier();
|
|
220
|
+
}
|
|
221
|
+
|
|
182
222
|
public getSenderAddress() {
|
|
183
223
|
return this.l1TxUtils.getSenderAddress();
|
|
184
224
|
}
|
|
185
225
|
|
|
226
|
+
/**
|
|
227
|
+
* Gets the L1 fee analyzer instance (only available in fisherman mode)
|
|
228
|
+
*/
|
|
229
|
+
public getL1FeeAnalyzer(): L1FeeAnalyzer | undefined {
|
|
230
|
+
return this.l1FeeAnalyzer;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Sets the proposer address to use for simulations in fisherman mode.
|
|
235
|
+
* @param proposerAddress - The actual proposer's address to use for balance lookups in simulations
|
|
236
|
+
*/
|
|
237
|
+
public setProposerAddressForSimulation(proposerAddress: EthAddress | undefined) {
|
|
238
|
+
this.proposerAddressForSimulation = proposerAddress;
|
|
239
|
+
}
|
|
240
|
+
|
|
186
241
|
public addRequest(request: RequestWithExpiry) {
|
|
187
242
|
this.requests.push(request);
|
|
188
243
|
}
|
|
189
244
|
|
|
190
|
-
public getCurrentL2Slot():
|
|
245
|
+
public getCurrentL2Slot(): SlotNumber {
|
|
191
246
|
return this.epochCache.getEpochAndSlotNow().slot;
|
|
192
247
|
}
|
|
193
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Clears all pending requests without sending them.
|
|
251
|
+
*/
|
|
252
|
+
public clearPendingRequests(): void {
|
|
253
|
+
const count = this.requests.length;
|
|
254
|
+
this.requests = [];
|
|
255
|
+
if (count > 0) {
|
|
256
|
+
this.log.debug(`Cleared ${count} pending request(s)`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Analyzes L1 fees for the pending requests without sending them.
|
|
262
|
+
* This is used in fisherman mode to validate fee calculations.
|
|
263
|
+
* @param l2SlotNumber - The L2 slot number for this analysis
|
|
264
|
+
* @param onComplete - Optional callback to invoke when analysis completes (after block is mined)
|
|
265
|
+
* @returns The analysis result (incomplete until block mines), or undefined if no requests
|
|
266
|
+
*/
|
|
267
|
+
public async analyzeL1Fees(
|
|
268
|
+
l2SlotNumber: SlotNumber,
|
|
269
|
+
onComplete?: (analysis: L1FeeAnalysisResult) => void,
|
|
270
|
+
): Promise<L1FeeAnalysisResult | undefined> {
|
|
271
|
+
if (!this.l1FeeAnalyzer) {
|
|
272
|
+
this.log.warn('L1 fee analyzer not available (not in fisherman mode)');
|
|
273
|
+
return undefined;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const requestsToAnalyze = [...this.requests];
|
|
277
|
+
if (requestsToAnalyze.length === 0) {
|
|
278
|
+
this.log.debug('No requests to analyze for L1 fees');
|
|
279
|
+
return undefined;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Extract blob config from requests (if any)
|
|
283
|
+
const blobConfigs = requestsToAnalyze.filter(request => request.blobConfig).map(request => request.blobConfig);
|
|
284
|
+
const blobConfig = blobConfigs[0];
|
|
285
|
+
|
|
286
|
+
// Get gas configs
|
|
287
|
+
const gasConfigs = requestsToAnalyze.filter(request => request.gasConfig).map(request => request.gasConfig);
|
|
288
|
+
const gasLimits = gasConfigs.map(g => g?.gasLimit).filter((g): g is bigint => g !== undefined);
|
|
289
|
+
const gasLimit = gasLimits.length > 0 ? gasLimits.reduce((sum, g) => sum + g, 0n) : 0n;
|
|
290
|
+
|
|
291
|
+
// Get the transaction requests
|
|
292
|
+
const l1Requests = requestsToAnalyze.map(r => r.request);
|
|
293
|
+
|
|
294
|
+
// Start the analysis
|
|
295
|
+
const analysisId = await this.l1FeeAnalyzer.startAnalysis(
|
|
296
|
+
l2SlotNumber,
|
|
297
|
+
gasLimit > 0n ? gasLimit : MAX_L1_TX_LIMIT,
|
|
298
|
+
l1Requests,
|
|
299
|
+
blobConfig,
|
|
300
|
+
onComplete,
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
this.log.info('Started L1 fee analysis', {
|
|
304
|
+
analysisId,
|
|
305
|
+
l2SlotNumber: l2SlotNumber.toString(),
|
|
306
|
+
requestCount: requestsToAnalyze.length,
|
|
307
|
+
hasBlobConfig: !!blobConfig,
|
|
308
|
+
gasLimit: gasLimit.toString(),
|
|
309
|
+
actions: requestsToAnalyze.map(r => r.action),
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// Return the analysis result (will be incomplete until block mines)
|
|
313
|
+
return this.l1FeeAnalyzer.getAnalysis(analysisId);
|
|
314
|
+
}
|
|
315
|
+
|
|
194
316
|
/**
|
|
195
317
|
* Sends all requests that are still valid.
|
|
196
318
|
* @returns one of:
|
|
@@ -198,10 +320,11 @@ export class SequencerPublisher {
|
|
|
198
320
|
* - a receipt and errorMsg if it failed on L1
|
|
199
321
|
* - undefined if no valid requests are found OR the tx failed to send.
|
|
200
322
|
*/
|
|
323
|
+
@trackSpan('SequencerPublisher.sendRequests')
|
|
201
324
|
public async sendRequests() {
|
|
202
325
|
const requestsToProcess = [...this.requests];
|
|
203
326
|
this.requests = [];
|
|
204
|
-
if (this.interrupted) {
|
|
327
|
+
if (this.interrupted || requestsToProcess.length === 0) {
|
|
205
328
|
return undefined;
|
|
206
329
|
}
|
|
207
330
|
const currentL2Slot = this.getCurrentL2Slot();
|
|
@@ -244,7 +367,16 @@ export class SequencerPublisher {
|
|
|
244
367
|
|
|
245
368
|
// Merge gasConfigs. Yields the sum of gasLimits, and the earliest txTimeoutAt, or undefined if no gasConfig sets them.
|
|
246
369
|
const gasLimits = gasConfigs.map(g => g?.gasLimit).filter((g): g is bigint => g !== undefined);
|
|
247
|
-
|
|
370
|
+
let gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
|
|
371
|
+
// Cap at L1 block gas limit so the node accepts the tx ("gas limit too high" otherwise).
|
|
372
|
+
const maxGas = MAX_L1_TX_LIMIT;
|
|
373
|
+
if (gasLimit !== undefined && gasLimit > maxGas) {
|
|
374
|
+
this.log.debug('Capping bundled tx gas limit to L1 max', {
|
|
375
|
+
requested: gasLimit,
|
|
376
|
+
capped: maxGas,
|
|
377
|
+
});
|
|
378
|
+
gasLimit = maxGas;
|
|
379
|
+
}
|
|
248
380
|
const txTimeoutAts = gasConfigs.map(g => g?.txTimeoutAt).filter((g): g is Date => g !== undefined);
|
|
249
381
|
const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map(g => g.getTime()))) : undefined; // earliest
|
|
250
382
|
const txConfig: RequestWithExpiry['gasConfig'] = { gasLimit, txTimeoutAt };
|
|
@@ -315,13 +447,15 @@ export class SequencerPublisher {
|
|
|
315
447
|
public canProposeAtNextEthBlock(
|
|
316
448
|
tipArchive: Fr,
|
|
317
449
|
msgSender: EthAddress,
|
|
318
|
-
opts: {
|
|
450
|
+
opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
319
451
|
) {
|
|
320
452
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
321
453
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
322
454
|
|
|
323
455
|
return this.rollupContract
|
|
324
|
-
.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), this.ethereumSlotDuration,
|
|
456
|
+
.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
|
|
457
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
458
|
+
})
|
|
325
459
|
.catch(err => {
|
|
326
460
|
if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
|
|
327
461
|
this.log.warn(`Failed canProposeAtTime check with ${ignoredErrors.find(e => err.message.includes(e))}`, {
|
|
@@ -339,7 +473,11 @@ export class SequencerPublisher {
|
|
|
339
473
|
* It will throw if the block header is invalid.
|
|
340
474
|
* @param header - The block header to validate
|
|
341
475
|
*/
|
|
342
|
-
|
|
476
|
+
@trackSpan('SequencerPublisher.validateBlockHeader')
|
|
477
|
+
public async validateBlockHeader(
|
|
478
|
+
header: CheckpointHeader,
|
|
479
|
+
opts?: { forcePendingCheckpointNumber: CheckpointNumber | undefined },
|
|
480
|
+
): Promise<void> {
|
|
343
481
|
const flags = { ignoreDA: true, ignoreSignatures: true };
|
|
344
482
|
|
|
345
483
|
const args = [
|
|
@@ -348,15 +486,27 @@ export class SequencerPublisher {
|
|
|
348
486
|
[], // no signers
|
|
349
487
|
Signature.empty().toViemSignature(),
|
|
350
488
|
`0x${'0'.repeat(64)}`, // 32 empty bytes
|
|
351
|
-
header.
|
|
489
|
+
header.blobsHash.toString(),
|
|
352
490
|
flags,
|
|
353
491
|
] as const;
|
|
354
492
|
|
|
355
493
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
494
|
+
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
495
|
+
opts?.forcePendingCheckpointNumber,
|
|
496
|
+
);
|
|
497
|
+
let balance = 0n;
|
|
498
|
+
if (this.config.fishermanMode) {
|
|
499
|
+
// In fisherman mode, we can't know where the proposer is publishing from
|
|
500
|
+
// so we just add sufficient balance to the multicall3 address
|
|
501
|
+
balance = 10n * WEI_CONST * WEI_CONST; // 10 ETH
|
|
502
|
+
} else {
|
|
503
|
+
balance = await this.l1TxUtils.getSenderBalance();
|
|
504
|
+
}
|
|
505
|
+
stateOverrides.push({
|
|
506
|
+
address: MULTI_CALL_3_ADDRESS,
|
|
507
|
+
balance,
|
|
508
|
+
});
|
|
356
509
|
|
|
357
|
-
// use sender balance to simulate
|
|
358
|
-
const balance = await this.l1TxUtils.getSenderBalance();
|
|
359
|
-
this.log.debug(`Simulating validateHeader with balance: ${balance}`);
|
|
360
510
|
await this.l1TxUtils.simulate(
|
|
361
511
|
{
|
|
362
512
|
to: this.rollupContract.address,
|
|
@@ -364,86 +514,101 @@ export class SequencerPublisher {
|
|
|
364
514
|
from: MULTI_CALL_3_ADDRESS,
|
|
365
515
|
},
|
|
366
516
|
{ time: ts + 1n },
|
|
367
|
-
|
|
368
|
-
{ address: MULTI_CALL_3_ADDRESS, balance },
|
|
369
|
-
...(await this.rollupContract.makePendingBlockNumberOverride(opts?.forcePendingBlockNumber)),
|
|
370
|
-
],
|
|
517
|
+
stateOverrides,
|
|
371
518
|
);
|
|
372
519
|
this.log.debug(`Simulated validateHeader`);
|
|
373
520
|
}
|
|
374
521
|
|
|
375
522
|
/**
|
|
376
|
-
* Simulate making a call to invalidate a
|
|
377
|
-
* @param
|
|
523
|
+
* Simulate making a call to invalidate a checkpoint with invalid attestations. Returns undefined if no need to invalidate.
|
|
524
|
+
* @param validationResult - The validation result indicating which checkpoint to invalidate (as returned by the archiver)
|
|
378
525
|
*/
|
|
379
|
-
public async
|
|
380
|
-
validationResult:
|
|
381
|
-
): Promise<
|
|
526
|
+
public async simulateInvalidateCheckpoint(
|
|
527
|
+
validationResult: ValidateCheckpointResult,
|
|
528
|
+
): Promise<InvalidateCheckpointRequest | undefined> {
|
|
382
529
|
if (validationResult.valid) {
|
|
383
530
|
return undefined;
|
|
384
531
|
}
|
|
385
532
|
|
|
386
|
-
const { reason,
|
|
387
|
-
const
|
|
388
|
-
const logData = { ...
|
|
533
|
+
const { reason, checkpoint } = validationResult;
|
|
534
|
+
const checkpointNumber = checkpoint.checkpointNumber;
|
|
535
|
+
const logData = { ...checkpoint, reason };
|
|
389
536
|
|
|
390
|
-
const
|
|
391
|
-
if (
|
|
537
|
+
const currentCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
538
|
+
if (currentCheckpointNumber < checkpointNumber) {
|
|
392
539
|
this.log.verbose(
|
|
393
|
-
`Skipping
|
|
394
|
-
{
|
|
540
|
+
`Skipping checkpoint ${checkpointNumber} invalidation since it has already been removed from the pending chain`,
|
|
541
|
+
{ currentCheckpointNumber, ...logData },
|
|
395
542
|
);
|
|
396
543
|
return undefined;
|
|
397
544
|
}
|
|
398
545
|
|
|
399
|
-
const request = this.
|
|
400
|
-
this.log.debug(`Simulating invalidate
|
|
546
|
+
const request = this.buildInvalidateCheckpointRequest(validationResult);
|
|
547
|
+
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
|
|
401
548
|
|
|
402
549
|
try {
|
|
403
|
-
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
404
|
-
|
|
550
|
+
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
551
|
+
request,
|
|
552
|
+
undefined,
|
|
553
|
+
undefined,
|
|
554
|
+
mergeAbis([request.abi ?? [], ErrorsAbi]),
|
|
555
|
+
);
|
|
556
|
+
this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} succeeded`, {
|
|
557
|
+
...logData,
|
|
558
|
+
request,
|
|
559
|
+
gasUsed,
|
|
560
|
+
});
|
|
405
561
|
|
|
406
|
-
return {
|
|
562
|
+
return {
|
|
563
|
+
request,
|
|
564
|
+
gasUsed,
|
|
565
|
+
checkpointNumber,
|
|
566
|
+
forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
|
|
567
|
+
reason,
|
|
568
|
+
};
|
|
407
569
|
} catch (err) {
|
|
408
570
|
const viemError = formatViemError(err);
|
|
409
571
|
|
|
410
|
-
// If the error is due to the
|
|
411
|
-
// we can safely ignore it and return undefined so we go ahead with
|
|
412
|
-
if (viemError.message?.includes('
|
|
572
|
+
// If the error is due to the checkpoint not being in the pending chain, and it was indeed removed by someone else,
|
|
573
|
+
// we can safely ignore it and return undefined so we go ahead with checkpoint building.
|
|
574
|
+
if (viemError.message?.includes('Rollup__CheckpointNotInPendingChain')) {
|
|
413
575
|
this.log.verbose(
|
|
414
|
-
`Simulation for invalidate
|
|
576
|
+
`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
|
|
415
577
|
{ ...logData, request, error: viemError.message },
|
|
416
578
|
);
|
|
417
|
-
const
|
|
418
|
-
if (
|
|
419
|
-
this.log.verbose(`
|
|
579
|
+
const latestPendingCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
580
|
+
if (latestPendingCheckpointNumber < checkpointNumber) {
|
|
581
|
+
this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, { ...logData });
|
|
420
582
|
return undefined;
|
|
421
583
|
} else {
|
|
422
584
|
this.log.error(
|
|
423
|
-
`Simulation for invalidate ${
|
|
585
|
+
`Simulation for invalidate checkpoint ${checkpointNumber} failed and it is still in pending chain`,
|
|
424
586
|
viemError,
|
|
425
587
|
logData,
|
|
426
588
|
);
|
|
427
|
-
throw new Error(
|
|
428
|
-
|
|
429
|
-
|
|
589
|
+
throw new Error(
|
|
590
|
+
`Failed to simulate invalidate checkpoint ${checkpointNumber} while it is still in pending chain`,
|
|
591
|
+
{
|
|
592
|
+
cause: viemError,
|
|
593
|
+
},
|
|
594
|
+
);
|
|
430
595
|
}
|
|
431
596
|
}
|
|
432
597
|
|
|
433
|
-
// Otherwise, throw. We cannot build the next
|
|
434
|
-
this.log.error(`Simulation for invalidate
|
|
435
|
-
throw new Error(`Failed to simulate invalidate
|
|
598
|
+
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
599
|
+
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
|
|
600
|
+
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
|
|
436
601
|
}
|
|
437
602
|
}
|
|
438
603
|
|
|
439
|
-
private
|
|
604
|
+
private buildInvalidateCheckpointRequest(validationResult: ValidateCheckpointResult) {
|
|
440
605
|
if (validationResult.valid) {
|
|
441
|
-
throw new Error('Cannot invalidate a valid
|
|
606
|
+
throw new Error('Cannot invalidate a valid checkpoint');
|
|
442
607
|
}
|
|
443
608
|
|
|
444
|
-
const {
|
|
445
|
-
const logData = { ...
|
|
446
|
-
this.log.debug(`
|
|
609
|
+
const { checkpoint, committee, reason } = validationResult;
|
|
610
|
+
const logData = { ...checkpoint, reason };
|
|
611
|
+
this.log.debug(`Building invalidate checkpoint ${checkpoint.checkpointNumber} request`, logData);
|
|
447
612
|
|
|
448
613
|
const attestationsAndSigners = new CommitteeAttestationsAndSigners(
|
|
449
614
|
validationResult.attestations,
|
|
@@ -451,14 +616,14 @@ export class SequencerPublisher {
|
|
|
451
616
|
|
|
452
617
|
if (reason === 'invalid-attestation') {
|
|
453
618
|
return this.rollupContract.buildInvalidateBadAttestationRequest(
|
|
454
|
-
|
|
619
|
+
checkpoint.checkpointNumber,
|
|
455
620
|
attestationsAndSigners,
|
|
456
621
|
committee,
|
|
457
622
|
validationResult.invalidIndex,
|
|
458
623
|
);
|
|
459
624
|
} else if (reason === 'insufficient-attestations') {
|
|
460
625
|
return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
|
|
461
|
-
|
|
626
|
+
checkpoint.checkpointNumber,
|
|
462
627
|
attestationsAndSigners,
|
|
463
628
|
committee,
|
|
464
629
|
);
|
|
@@ -468,48 +633,25 @@ export class SequencerPublisher {
|
|
|
468
633
|
}
|
|
469
634
|
}
|
|
470
635
|
|
|
471
|
-
/**
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
*
|
|
476
|
-
* @param block - The block to propose
|
|
477
|
-
* @param attestationData - The block's attestation data
|
|
478
|
-
*
|
|
479
|
-
*/
|
|
480
|
-
public async validateBlockForSubmission(
|
|
481
|
-
block: L2Block,
|
|
636
|
+
/** Simulates `propose` to make sure that the checkpoint is valid for submission */
|
|
637
|
+
@trackSpan('SequencerPublisher.validateCheckpointForSubmission')
|
|
638
|
+
public async validateCheckpointForSubmission(
|
|
639
|
+
checkpoint: Checkpoint,
|
|
482
640
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
483
641
|
attestationsAndSignersSignature: Signature,
|
|
484
|
-
options: {
|
|
642
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
485
643
|
): Promise<bigint> {
|
|
486
644
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
// so that the committee is recalculated correctly
|
|
490
|
-
const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
491
|
-
if (ignoreSignatures) {
|
|
492
|
-
const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber.toBigInt());
|
|
493
|
-
if (!committee) {
|
|
494
|
-
this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber.toBigInt()}`);
|
|
495
|
-
throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber.toBigInt()}`);
|
|
496
|
-
}
|
|
497
|
-
attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
498
|
-
CommitteeAttestation.fromAddress(committeeMember),
|
|
499
|
-
);
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
const blobFields = block.getCheckpointBlobFields();
|
|
503
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
645
|
+
const blobFields = checkpoint.toBlobFields();
|
|
646
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
504
647
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
505
648
|
|
|
506
649
|
const args = [
|
|
507
650
|
{
|
|
508
|
-
header:
|
|
509
|
-
archive: toHex(
|
|
510
|
-
stateReference: block.header.state.toViem(),
|
|
651
|
+
header: checkpoint.header.toViem(),
|
|
652
|
+
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
511
653
|
oracleInput: {
|
|
512
|
-
feeAssetPriceModifier:
|
|
654
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
513
655
|
},
|
|
514
656
|
},
|
|
515
657
|
attestationsAndSigners.getPackedAttestations(),
|
|
@@ -523,7 +665,7 @@ export class SequencerPublisher {
|
|
|
523
665
|
}
|
|
524
666
|
|
|
525
667
|
private async enqueueCastSignalHelper(
|
|
526
|
-
slotNumber:
|
|
668
|
+
slotNumber: SlotNumber,
|
|
527
669
|
timestamp: bigint,
|
|
528
670
|
signalType: GovernanceSignalAction,
|
|
529
671
|
payload: EthAddress,
|
|
@@ -545,10 +687,45 @@ export class SequencerPublisher {
|
|
|
545
687
|
const round = await base.computeRound(slotNumber);
|
|
546
688
|
const roundInfo = await base.getRoundInfo(this.rollupContract.address, round);
|
|
547
689
|
|
|
690
|
+
if (roundInfo.quorumReached) {
|
|
691
|
+
return false;
|
|
692
|
+
}
|
|
693
|
+
|
|
548
694
|
if (roundInfo.lastSignalSlot >= slotNumber) {
|
|
549
695
|
return false;
|
|
550
696
|
}
|
|
551
697
|
|
|
698
|
+
if (await this.isPayloadEmpty(payload)) {
|
|
699
|
+
this.log.warn(`Skipping vote cast for payload with empty code`);
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Check if payload was already submitted to governance
|
|
704
|
+
const cacheKey = payload.toString();
|
|
705
|
+
if (!this.payloadProposedCache.has(cacheKey)) {
|
|
706
|
+
try {
|
|
707
|
+
const l1StartBlock = await this.rollupContract.getL1StartBlock();
|
|
708
|
+
const proposed = await retry(
|
|
709
|
+
() => base.hasPayloadBeenProposed(payload.toString(), l1StartBlock),
|
|
710
|
+
'Check if payload was proposed',
|
|
711
|
+
makeBackoff([0, 1, 2]),
|
|
712
|
+
this.log,
|
|
713
|
+
true,
|
|
714
|
+
);
|
|
715
|
+
if (proposed) {
|
|
716
|
+
this.payloadProposedCache.add(cacheKey);
|
|
717
|
+
}
|
|
718
|
+
} catch (err) {
|
|
719
|
+
this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
if (this.payloadProposedCache.has(cacheKey)) {
|
|
725
|
+
this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
|
|
726
|
+
return false;
|
|
727
|
+
}
|
|
728
|
+
|
|
552
729
|
const cachedLastVote = this.lastActions[signalType];
|
|
553
730
|
this.lastActions[signalType] = slotNumber;
|
|
554
731
|
const action = signalType;
|
|
@@ -568,7 +745,7 @@ export class SequencerPublisher {
|
|
|
568
745
|
});
|
|
569
746
|
|
|
570
747
|
try {
|
|
571
|
-
await this.l1TxUtils.simulate(request, { time: timestamp }, [], ErrorsAbi);
|
|
748
|
+
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
572
749
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
573
750
|
} catch (err) {
|
|
574
751
|
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, err);
|
|
@@ -591,14 +768,14 @@ export class SequencerPublisher {
|
|
|
591
768
|
const logData = { ...result, slotNumber, round, payload: payload.toString() };
|
|
592
769
|
if (!success) {
|
|
593
770
|
this.log.error(
|
|
594
|
-
`Signaling in
|
|
771
|
+
`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} failed`,
|
|
595
772
|
logData,
|
|
596
773
|
);
|
|
597
774
|
this.lastActions[signalType] = cachedLastVote;
|
|
598
775
|
return false;
|
|
599
776
|
} else {
|
|
600
777
|
this.log.info(
|
|
601
|
-
`Signaling in
|
|
778
|
+
`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} succeeded`,
|
|
602
779
|
logData,
|
|
603
780
|
);
|
|
604
781
|
return true;
|
|
@@ -608,6 +785,17 @@ export class SequencerPublisher {
|
|
|
608
785
|
return true;
|
|
609
786
|
}
|
|
610
787
|
|
|
788
|
+
private async isPayloadEmpty(payload: EthAddress): Promise<boolean> {
|
|
789
|
+
const key = payload.toString();
|
|
790
|
+
const cached = this.isPayloadEmptyCache.get(key);
|
|
791
|
+
if (cached) {
|
|
792
|
+
return cached;
|
|
793
|
+
}
|
|
794
|
+
const isEmpty = !(await this.l1TxUtils.getCode(payload));
|
|
795
|
+
this.isPayloadEmptyCache.set(key, isEmpty);
|
|
796
|
+
return isEmpty;
|
|
797
|
+
}
|
|
798
|
+
|
|
611
799
|
/**
|
|
612
800
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
613
801
|
* @param slotNumber - The slot number to cast a signal for.
|
|
@@ -616,7 +804,7 @@ export class SequencerPublisher {
|
|
|
616
804
|
*/
|
|
617
805
|
public enqueueGovernanceCastSignal(
|
|
618
806
|
governancePayload: EthAddress,
|
|
619
|
-
slotNumber:
|
|
807
|
+
slotNumber: SlotNumber,
|
|
620
808
|
timestamp: bigint,
|
|
621
809
|
signerAddress: EthAddress,
|
|
622
810
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
@@ -635,7 +823,7 @@ export class SequencerPublisher {
|
|
|
635
823
|
/** Enqueues all slashing actions as returned by the slasher client. */
|
|
636
824
|
public async enqueueSlashingActions(
|
|
637
825
|
actions: ProposerSlashAction[],
|
|
638
|
-
slotNumber:
|
|
826
|
+
slotNumber: SlotNumber,
|
|
639
827
|
timestamp: bigint,
|
|
640
828
|
signerAddress: EthAddress,
|
|
641
829
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
@@ -755,31 +943,25 @@ export class SequencerPublisher {
|
|
|
755
943
|
return true;
|
|
756
944
|
}
|
|
757
945
|
|
|
758
|
-
/**
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
* @param block - L2 block to propose.
|
|
762
|
-
* @returns True if the tx has been enqueued, throws otherwise. See #9315
|
|
763
|
-
*/
|
|
764
|
-
public async enqueueProposeL2Block(
|
|
765
|
-
block: L2Block,
|
|
946
|
+
/** Simulates and enqueues a proposal for a checkpoint on L1 */
|
|
947
|
+
public async enqueueProposeCheckpoint(
|
|
948
|
+
checkpoint: Checkpoint,
|
|
766
949
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
767
950
|
attestationsAndSignersSignature: Signature,
|
|
768
|
-
opts: { txTimeoutAt?: Date;
|
|
769
|
-
): Promise<
|
|
770
|
-
const checkpointHeader =
|
|
951
|
+
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
952
|
+
): Promise<void> {
|
|
953
|
+
const checkpointHeader = checkpoint.header;
|
|
771
954
|
|
|
772
|
-
const blobFields =
|
|
773
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
955
|
+
const blobFields = checkpoint.toBlobFields();
|
|
956
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
774
957
|
|
|
775
|
-
const proposeTxArgs = {
|
|
958
|
+
const proposeTxArgs: L1ProcessArgs = {
|
|
776
959
|
header: checkpointHeader,
|
|
777
|
-
archive:
|
|
778
|
-
stateReference: block.header.state,
|
|
779
|
-
body: block.body.toBuffer(),
|
|
960
|
+
archive: checkpoint.archive.root.toBuffer(),
|
|
780
961
|
blobs,
|
|
781
962
|
attestationsAndSigners,
|
|
782
963
|
attestationsAndSignersSignature,
|
|
964
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
783
965
|
};
|
|
784
966
|
|
|
785
967
|
let ts: bigint;
|
|
@@ -790,22 +972,29 @@ export class SequencerPublisher {
|
|
|
790
972
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
791
973
|
// make time consistency checks break.
|
|
792
974
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
793
|
-
ts = await this.
|
|
975
|
+
ts = await this.validateCheckpointForSubmission(
|
|
976
|
+
checkpoint,
|
|
977
|
+
attestationsAndSigners,
|
|
978
|
+
attestationsAndSignersSignature,
|
|
979
|
+
opts,
|
|
980
|
+
);
|
|
794
981
|
} catch (err: any) {
|
|
795
|
-
this.log.error(`
|
|
796
|
-
...
|
|
797
|
-
slotNumber:
|
|
798
|
-
|
|
982
|
+
this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
983
|
+
...checkpoint.getStats(),
|
|
984
|
+
slotNumber: checkpoint.header.slotNumber,
|
|
985
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
799
986
|
});
|
|
800
987
|
throw err;
|
|
801
988
|
}
|
|
802
989
|
|
|
803
|
-
this.log.verbose(`Enqueuing
|
|
804
|
-
await this.addProposeTx(
|
|
805
|
-
return true;
|
|
990
|
+
this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
|
|
991
|
+
await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
|
|
806
992
|
}
|
|
807
993
|
|
|
808
|
-
public
|
|
994
|
+
public enqueueInvalidateCheckpoint(
|
|
995
|
+
request: InvalidateCheckpointRequest | undefined,
|
|
996
|
+
opts: { txTimeoutAt?: Date } = {},
|
|
997
|
+
) {
|
|
809
998
|
if (!request) {
|
|
810
999
|
return;
|
|
811
1000
|
}
|
|
@@ -813,24 +1002,24 @@ export class SequencerPublisher {
|
|
|
813
1002
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
814
1003
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(request.gasUsed) * 64) / 63)));
|
|
815
1004
|
|
|
816
|
-
const { gasUsed,
|
|
817
|
-
const logData = { gasUsed,
|
|
818
|
-
this.log.verbose(`Enqueuing invalidate
|
|
1005
|
+
const { gasUsed, checkpointNumber } = request;
|
|
1006
|
+
const logData = { gasUsed, checkpointNumber, gasLimit, opts };
|
|
1007
|
+
this.log.verbose(`Enqueuing invalidate checkpoint request`, logData);
|
|
819
1008
|
this.addRequest({
|
|
820
1009
|
action: `invalidate-by-${request.reason}`,
|
|
821
1010
|
request: request.request,
|
|
822
1011
|
gasConfig: { gasLimit, txTimeoutAt: opts.txTimeoutAt },
|
|
823
|
-
lastValidL2Slot: this.getCurrentL2Slot() +
|
|
1012
|
+
lastValidL2Slot: SlotNumber(this.getCurrentL2Slot() + 2),
|
|
824
1013
|
checkSuccess: (_req, result) => {
|
|
825
1014
|
const success =
|
|
826
1015
|
result &&
|
|
827
1016
|
result.receipt &&
|
|
828
1017
|
result.receipt.status === 'success' &&
|
|
829
|
-
tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, '
|
|
1018
|
+
tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointInvalidated');
|
|
830
1019
|
if (!success) {
|
|
831
|
-
this.log.warn(`Invalidate
|
|
1020
|
+
this.log.warn(`Invalidate checkpoint ${request.checkpointNumber} failed`, { ...result, ...logData });
|
|
832
1021
|
} else {
|
|
833
|
-
this.log.info(`Invalidate
|
|
1022
|
+
this.log.info(`Invalidate checkpoint ${request.checkpointNumber} succeeded`, { ...result, ...logData });
|
|
834
1023
|
}
|
|
835
1024
|
return !!success;
|
|
836
1025
|
},
|
|
@@ -841,7 +1030,7 @@ export class SequencerPublisher {
|
|
|
841
1030
|
action: Action,
|
|
842
1031
|
request: L1TxRequest,
|
|
843
1032
|
checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
|
|
844
|
-
slotNumber:
|
|
1033
|
+
slotNumber: SlotNumber,
|
|
845
1034
|
timestamp: bigint,
|
|
846
1035
|
) {
|
|
847
1036
|
const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
|
|
@@ -856,12 +1045,14 @@ export class SequencerPublisher {
|
|
|
856
1045
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
857
1046
|
|
|
858
1047
|
let gasUsed: bigint;
|
|
1048
|
+
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
859
1049
|
try {
|
|
860
|
-
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [],
|
|
1050
|
+
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi)); // TODO(palla/slash): Check the timestamp logic
|
|
861
1051
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
862
1052
|
} catch (err) {
|
|
863
|
-
const viemError = formatViemError(err);
|
|
1053
|
+
const viemError = formatViemError(err, simulateAbi);
|
|
864
1054
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1055
|
+
|
|
865
1056
|
return false;
|
|
866
1057
|
}
|
|
867
1058
|
|
|
@@ -869,10 +1060,14 @@ export class SequencerPublisher {
|
|
|
869
1060
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(gasUsed) * 64) / 63)));
|
|
870
1061
|
logData.gasLimit = gasLimit;
|
|
871
1062
|
|
|
1063
|
+
// Store the ABI used for simulation on the request so Multicall3.forward can decode errors
|
|
1064
|
+
// when the tx is sent and a revert is diagnosed via simulation.
|
|
1065
|
+
const requestWithAbi = { ...request, abi: simulateAbi };
|
|
1066
|
+
|
|
872
1067
|
this.log.debug(`Enqueuing ${action}`, logData);
|
|
873
1068
|
this.addRequest({
|
|
874
1069
|
action,
|
|
875
|
-
request,
|
|
1070
|
+
request: requestWithAbi,
|
|
876
1071
|
gasConfig: { gasLimit },
|
|
877
1072
|
lastValidL2Slot: slotNumber,
|
|
878
1073
|
checkSuccess: (_req, result) => {
|
|
@@ -909,44 +1104,52 @@ export class SequencerPublisher {
|
|
|
909
1104
|
private async prepareProposeTx(
|
|
910
1105
|
encodedData: L1ProcessArgs,
|
|
911
1106
|
timestamp: bigint,
|
|
912
|
-
options: {
|
|
1107
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
913
1108
|
) {
|
|
914
1109
|
const kzg = Blob.getViemKzgInstance();
|
|
915
1110
|
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
916
1111
|
this.log.debug('Validating blob input', { blobInput });
|
|
917
|
-
const blobEvaluationGas = await this.l1TxUtils
|
|
918
|
-
.estimateGas(
|
|
919
|
-
this.getSenderAddress().toString(),
|
|
920
|
-
{
|
|
921
|
-
to: this.rollupContract.address,
|
|
922
|
-
data: encodeFunctionData({
|
|
923
|
-
abi: RollupAbi,
|
|
924
|
-
functionName: 'validateBlobs',
|
|
925
|
-
args: [blobInput],
|
|
926
|
-
}),
|
|
927
|
-
},
|
|
928
|
-
{},
|
|
929
|
-
{
|
|
930
|
-
blobs: encodedData.blobs.map(b => b.data),
|
|
931
|
-
kzg,
|
|
932
|
-
},
|
|
933
|
-
)
|
|
934
|
-
.catch(err => {
|
|
935
|
-
const { message, metaMessages } = formatViemError(err);
|
|
936
|
-
this.log.error(`Failed to validate blobs`, message, { metaMessages });
|
|
937
|
-
throw new Error('Failed to validate blobs');
|
|
938
|
-
});
|
|
939
1112
|
|
|
1113
|
+
// Get blob evaluation gas
|
|
1114
|
+
let blobEvaluationGas: bigint;
|
|
1115
|
+
if (this.config.fishermanMode) {
|
|
1116
|
+
// In fisherman mode, we can't estimate blob gas because estimateGas doesn't support state overrides
|
|
1117
|
+
// Use a fixed estimate.
|
|
1118
|
+
blobEvaluationGas = BigInt(encodedData.blobs.length) * 21_000n;
|
|
1119
|
+
this.log.debug(`Using fixed blob evaluation gas estimate in fisherman mode: ${blobEvaluationGas}`);
|
|
1120
|
+
} else {
|
|
1121
|
+
// Normal mode - use estimateGas with blob inputs
|
|
1122
|
+
blobEvaluationGas = await this.l1TxUtils
|
|
1123
|
+
.estimateGas(
|
|
1124
|
+
this.getSenderAddress().toString(),
|
|
1125
|
+
{
|
|
1126
|
+
to: this.rollupContract.address,
|
|
1127
|
+
data: encodeFunctionData({
|
|
1128
|
+
abi: RollupAbi,
|
|
1129
|
+
functionName: 'validateBlobs',
|
|
1130
|
+
args: [blobInput],
|
|
1131
|
+
}),
|
|
1132
|
+
},
|
|
1133
|
+
{},
|
|
1134
|
+
{
|
|
1135
|
+
blobs: encodedData.blobs.map(b => b.data),
|
|
1136
|
+
kzg,
|
|
1137
|
+
},
|
|
1138
|
+
)
|
|
1139
|
+
.catch(err => {
|
|
1140
|
+
const { message, metaMessages } = formatViemError(err);
|
|
1141
|
+
this.log.error(`Failed to validate blobs`, message, { metaMessages });
|
|
1142
|
+
throw new Error('Failed to validate blobs');
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
940
1145
|
const signers = encodedData.attestationsAndSigners.getSigners().map(signer => signer.toString());
|
|
941
1146
|
|
|
942
1147
|
const args = [
|
|
943
1148
|
{
|
|
944
1149
|
header: encodedData.header.toViem(),
|
|
945
1150
|
archive: toHex(encodedData.archive),
|
|
946
|
-
stateReference: encodedData.stateReference.toViem(),
|
|
947
1151
|
oracleInput: {
|
|
948
|
-
|
|
949
|
-
feeAssetPriceModifier: 0n,
|
|
1152
|
+
feeAssetPriceModifier: encodedData.feeAssetPriceModifier,
|
|
950
1153
|
},
|
|
951
1154
|
},
|
|
952
1155
|
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
@@ -971,9 +1174,8 @@ export class SequencerPublisher {
|
|
|
971
1174
|
{
|
|
972
1175
|
readonly header: ViemHeader;
|
|
973
1176
|
readonly archive: `0x${string}`;
|
|
974
|
-
readonly stateReference: ViemStateReference;
|
|
975
1177
|
readonly oracleInput: {
|
|
976
|
-
readonly feeAssetPriceModifier:
|
|
1178
|
+
readonly feeAssetPriceModifier: bigint;
|
|
977
1179
|
};
|
|
978
1180
|
},
|
|
979
1181
|
ViemCommitteeAttestations,
|
|
@@ -982,7 +1184,7 @@ export class SequencerPublisher {
|
|
|
982
1184
|
`0x${string}`,
|
|
983
1185
|
],
|
|
984
1186
|
timestamp: bigint,
|
|
985
|
-
options: {
|
|
1187
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
986
1188
|
) {
|
|
987
1189
|
const rollupData = encodeFunctionData({
|
|
988
1190
|
abi: RollupAbi,
|
|
@@ -990,44 +1192,64 @@ export class SequencerPublisher {
|
|
|
990
1192
|
args,
|
|
991
1193
|
});
|
|
992
1194
|
|
|
993
|
-
// override the pending
|
|
994
|
-
const
|
|
995
|
-
options.
|
|
996
|
-
? await this.rollupContract.
|
|
1195
|
+
// override the pending checkpoint number if requested
|
|
1196
|
+
const forcePendingCheckpointNumberStateDiff = (
|
|
1197
|
+
options.forcePendingCheckpointNumber !== undefined
|
|
1198
|
+
? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber)
|
|
997
1199
|
: []
|
|
998
1200
|
).flatMap(override => override.stateDiff ?? []);
|
|
999
1201
|
|
|
1202
|
+
const stateOverrides: StateOverride = [
|
|
1203
|
+
{
|
|
1204
|
+
address: this.rollupContract.address,
|
|
1205
|
+
// @note we override checkBlob to false since blobs are not part simulate()
|
|
1206
|
+
stateDiff: [
|
|
1207
|
+
{ slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
|
|
1208
|
+
...forcePendingCheckpointNumberStateDiff,
|
|
1209
|
+
],
|
|
1210
|
+
},
|
|
1211
|
+
];
|
|
1212
|
+
// In fisherman mode, simulate as the proposer but with sufficient balance
|
|
1213
|
+
if (this.proposerAddressForSimulation) {
|
|
1214
|
+
stateOverrides.push({
|
|
1215
|
+
address: this.proposerAddressForSimulation.toString(),
|
|
1216
|
+
balance: 10n * WEI_CONST * WEI_CONST, // 10 ETH
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1000
1220
|
const simulationResult = await this.l1TxUtils
|
|
1001
1221
|
.simulate(
|
|
1002
1222
|
{
|
|
1003
1223
|
to: this.rollupContract.address,
|
|
1004
1224
|
data: rollupData,
|
|
1005
|
-
gas:
|
|
1225
|
+
gas: MAX_L1_TX_LIMIT,
|
|
1226
|
+
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1006
1227
|
},
|
|
1007
1228
|
{
|
|
1008
1229
|
// @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
|
|
1009
1230
|
time: timestamp + 1n,
|
|
1010
1231
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1011
|
-
gasLimit:
|
|
1232
|
+
gasLimit: MAX_L1_TX_LIMIT * 2n,
|
|
1012
1233
|
},
|
|
1013
|
-
|
|
1014
|
-
{
|
|
1015
|
-
address: this.rollupContract.address,
|
|
1016
|
-
// @note we override checkBlob to false since blobs are not part simulate()
|
|
1017
|
-
stateDiff: [
|
|
1018
|
-
{ slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
|
|
1019
|
-
...forcePendingBlockNumberStateDiff,
|
|
1020
|
-
],
|
|
1021
|
-
},
|
|
1022
|
-
],
|
|
1234
|
+
stateOverrides,
|
|
1023
1235
|
RollupAbi,
|
|
1024
1236
|
{
|
|
1025
1237
|
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
1026
|
-
fallbackGasEstimate:
|
|
1238
|
+
fallbackGasEstimate: MAX_L1_TX_LIMIT,
|
|
1027
1239
|
},
|
|
1028
1240
|
)
|
|
1029
1241
|
.catch(err => {
|
|
1030
|
-
|
|
1242
|
+
// In fisherman mode, we expect ValidatorSelection__MissingProposerSignature since fisherman doesn't have proposer signature
|
|
1243
|
+
const viemError = formatViemError(err);
|
|
1244
|
+
if (this.config.fishermanMode && viemError.message?.includes('ValidatorSelection__MissingProposerSignature')) {
|
|
1245
|
+
this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
|
|
1246
|
+
// Return a minimal simulation result with the fallback gas estimate
|
|
1247
|
+
return {
|
|
1248
|
+
gasUsed: MAX_L1_TX_LIMIT,
|
|
1249
|
+
logs: [],
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1031
1253
|
throw err;
|
|
1032
1254
|
});
|
|
1033
1255
|
|
|
@@ -1035,11 +1257,12 @@ export class SequencerPublisher {
|
|
|
1035
1257
|
}
|
|
1036
1258
|
|
|
1037
1259
|
private async addProposeTx(
|
|
1038
|
-
|
|
1260
|
+
checkpoint: Checkpoint,
|
|
1039
1261
|
encodedData: L1ProcessArgs,
|
|
1040
|
-
opts: { txTimeoutAt?: Date;
|
|
1262
|
+
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
1041
1263
|
timestamp: bigint,
|
|
1042
1264
|
): Promise<void> {
|
|
1265
|
+
const slot = checkpoint.header.slotNumber;
|
|
1043
1266
|
const timer = new Timer();
|
|
1044
1267
|
const kzg = Blob.getViemKzgInstance();
|
|
1045
1268
|
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
@@ -1054,11 +1277,13 @@ export class SequencerPublisher {
|
|
|
1054
1277
|
SequencerPublisher.MULTICALL_OVERHEAD_GAS_GUESS, // We issue the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
1055
1278
|
);
|
|
1056
1279
|
|
|
1057
|
-
// Send the blobs to the blob
|
|
1058
|
-
// tx fails but it does get mined. We make sure that the blobs are sent to the blob
|
|
1059
|
-
void
|
|
1060
|
-
this.
|
|
1061
|
-
|
|
1280
|
+
// Send the blobs to the blob client preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
|
|
1281
|
+
// tx fails but it does get mined. We make sure that the blobs are sent to the blob client regardless of the tx outcome.
|
|
1282
|
+
void Promise.resolve().then(() =>
|
|
1283
|
+
this.blobClient.sendBlobsToFilestore(encodedData.blobs).catch(_err => {
|
|
1284
|
+
this.log.error('Failed to send blobs to blob client');
|
|
1285
|
+
}),
|
|
1286
|
+
);
|
|
1062
1287
|
|
|
1063
1288
|
return this.addRequest({
|
|
1064
1289
|
action: 'propose',
|
|
@@ -1066,7 +1291,7 @@ export class SequencerPublisher {
|
|
|
1066
1291
|
to: this.rollupContract.address,
|
|
1067
1292
|
data: rollupData,
|
|
1068
1293
|
},
|
|
1069
|
-
lastValidL2Slot:
|
|
1294
|
+
lastValidL2Slot: checkpoint.header.slotNumber,
|
|
1070
1295
|
gasConfig: { ...opts, gasLimit },
|
|
1071
1296
|
blobConfig: {
|
|
1072
1297
|
blobs: encodedData.blobs.map(b => b.data),
|
|
@@ -1080,12 +1305,13 @@ export class SequencerPublisher {
|
|
|
1080
1305
|
const success =
|
|
1081
1306
|
receipt &&
|
|
1082
1307
|
receipt.status === 'success' &&
|
|
1083
|
-
tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, '
|
|
1308
|
+
tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointProposed');
|
|
1309
|
+
|
|
1084
1310
|
if (success) {
|
|
1085
1311
|
const endBlock = receipt.blockNumber;
|
|
1086
1312
|
const inclusionBlocks = Number(endBlock - startBlock);
|
|
1087
1313
|
const { calldataGas, calldataSize, sender } = stats!;
|
|
1088
|
-
const publishStats:
|
|
1314
|
+
const publishStats: L1PublishCheckpointStats = {
|
|
1089
1315
|
gasPrice: receipt.effectiveGasPrice,
|
|
1090
1316
|
gasUsed: receipt.gasUsed,
|
|
1091
1317
|
blobGasUsed: receipt.blobGasUsed ?? 0n,
|
|
@@ -1094,23 +1320,26 @@ export class SequencerPublisher {
|
|
|
1094
1320
|
calldataGas,
|
|
1095
1321
|
calldataSize,
|
|
1096
1322
|
sender,
|
|
1097
|
-
...
|
|
1323
|
+
...checkpoint.getStats(),
|
|
1098
1324
|
eventName: 'rollup-published-to-l1',
|
|
1099
1325
|
blobCount: encodedData.blobs.length,
|
|
1100
1326
|
inclusionBlocks,
|
|
1101
1327
|
};
|
|
1102
|
-
this.log.info(`Published
|
|
1328
|
+
this.log.info(`Published checkpoint ${checkpoint.number} at slot ${slot} to rollup contract`, {
|
|
1329
|
+
...stats,
|
|
1330
|
+
...checkpoint.getStats(),
|
|
1331
|
+
...pick(receipt, 'transactionHash', 'blockHash'),
|
|
1332
|
+
});
|
|
1103
1333
|
this.metrics.recordProcessBlockTx(timer.ms(), publishStats);
|
|
1104
1334
|
|
|
1105
1335
|
return true;
|
|
1106
1336
|
} else {
|
|
1107
1337
|
this.metrics.recordFailedTx('process');
|
|
1108
|
-
this.log.error(
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
});
|
|
1338
|
+
this.log.error(
|
|
1339
|
+
`Publishing checkpoint at slot ${slot} failed with ${errorMsg ?? 'no error message'}`,
|
|
1340
|
+
undefined,
|
|
1341
|
+
{ ...checkpoint.getStats(), ...receipt },
|
|
1342
|
+
);
|
|
1114
1343
|
return false;
|
|
1115
1344
|
}
|
|
1116
1345
|
},
|