@aztec/sequencer-client 0.0.1-fake-ceab37513c → 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 +93 -32
- 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 +17 -7
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +15 -4
- package/dest/publisher/sequencer-publisher-metrics.d.ts +4 -4
- package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-metrics.js +24 -87
- package/dest/publisher/sequencer-publisher.d.ts +91 -69
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +727 -181
- 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 +45 -4
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +232 -50
- package/dest/sequencer/sequencer.d.ts +123 -145
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +736 -520
- package/dest/sequencer/timetable.d.ts +54 -17
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +146 -60
- 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 +106 -41
- 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 +32 -12
- package/src/publisher/sequencer-publisher-metrics.ts +20 -72
- package/src/publisher/sequencer-publisher.ts +471 -239
- 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 +297 -62
- package/src/sequencer/sequencer.ts +488 -704
- package/src/sequencer/timetable.ts +178 -89
- 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 -126
- 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 -50
- package/src/sequencer/block_builder.ts +0 -216
- package/src/tx_validator/nullifier_cache.ts +0 -30
- package/src/tx_validator/tx_validator_factory.ts +0 -127
|
@@ -1,63 +1,69 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import { Blob } from '@aztec/blob-lib';
|
|
3
|
-
import { type BlobSinkClientInterface, createBlobSinkClient } from '@aztec/blob-sink/client';
|
|
1
|
+
import type { BlobClientInterface } from '@aztec/blob-client/client';
|
|
2
|
+
import { Blob, getBlobsPerL1Block, getPrefixedEthBlobCommitments } from '@aztec/blob-lib';
|
|
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
|
-
import type {
|
|
39
|
-
import
|
|
40
|
-
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
44
|
+
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
45
|
+
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
46
|
+
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
41
47
|
|
|
42
|
-
import { type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
|
|
48
|
+
import { type StateOverride, type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
|
|
43
49
|
|
|
44
|
-
import type {
|
|
50
|
+
import type { SequencerPublisherConfig } from './config.js';
|
|
45
51
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
46
52
|
|
|
47
53
|
/** Arguments to the process method of the rollup contract */
|
|
48
54
|
type L1ProcessArgs = {
|
|
49
55
|
/** The L2 block header. */
|
|
50
|
-
header:
|
|
56
|
+
header: CheckpointHeader;
|
|
51
57
|
/** A root of the archive tree after the L2 block is applied. */
|
|
52
58
|
archive: Buffer;
|
|
53
|
-
/** State reference after the L2 block is applied. */
|
|
54
|
-
stateReference: StateReference;
|
|
55
59
|
/** L2 block blobs containing all tx effects. */
|
|
56
60
|
blobs: Blob[];
|
|
57
61
|
/** Attestations */
|
|
58
62
|
attestationsAndSigners: CommitteeAttestationsAndSigners;
|
|
59
63
|
/** Attestations and signers signature */
|
|
60
64
|
attestationsAndSignersSignature: Signature;
|
|
65
|
+
/** The fee asset price modifier in basis points (from oracle) */
|
|
66
|
+
feeAssetPriceModifier: bigint;
|
|
61
67
|
};
|
|
62
68
|
|
|
63
69
|
export const Actions = [
|
|
@@ -79,18 +85,18 @@ type GovernanceSignalAction = Extract<Action, 'governance-signal' | 'empire-slas
|
|
|
79
85
|
// Sorting for actions such that invalidations go before proposals, and proposals go before votes
|
|
80
86
|
export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
|
|
81
87
|
|
|
82
|
-
export type
|
|
88
|
+
export type InvalidateCheckpointRequest = {
|
|
83
89
|
request: L1TxRequest;
|
|
84
90
|
reason: 'invalid-attestation' | 'insufficient-attestations';
|
|
85
91
|
gasUsed: bigint;
|
|
86
|
-
|
|
87
|
-
|
|
92
|
+
checkpointNumber: CheckpointNumber;
|
|
93
|
+
forcePendingCheckpointNumber: CheckpointNumber;
|
|
88
94
|
};
|
|
89
95
|
|
|
90
96
|
interface RequestWithExpiry {
|
|
91
97
|
action: Action;
|
|
92
98
|
request: L1TxRequest;
|
|
93
|
-
lastValidL2Slot:
|
|
99
|
+
lastValidL2Slot: SlotNumber;
|
|
94
100
|
gasConfig?: Pick<L1TxConfig, 'txTimeoutAt' | 'gasLimit'>;
|
|
95
101
|
blobConfig?: L1BlobInputs;
|
|
96
102
|
checkSuccess: (
|
|
@@ -107,16 +113,24 @@ export class SequencerPublisher {
|
|
|
107
113
|
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
108
114
|
protected slashingLog = createLogger('sequencer:publisher:slashing');
|
|
109
115
|
|
|
110
|
-
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>();
|
|
111
120
|
|
|
112
121
|
protected log: Logger;
|
|
113
122
|
protected ethereumSlotDuration: bigint;
|
|
114
123
|
|
|
115
|
-
private
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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;
|
|
120
134
|
|
|
121
135
|
// A CALL to a cold address is 2700 gas
|
|
122
136
|
public static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
@@ -124,20 +138,23 @@ export class SequencerPublisher {
|
|
|
124
138
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
125
139
|
public static VOTE_GAS_GUESS: bigint = 800_000n;
|
|
126
140
|
|
|
127
|
-
public l1TxUtils:
|
|
141
|
+
public l1TxUtils: L1TxUtils;
|
|
128
142
|
public rollupContract: RollupContract;
|
|
129
143
|
public govProposerContract: GovernanceProposerContract;
|
|
130
144
|
public slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
131
145
|
public slashFactoryContract: SlashFactoryContract;
|
|
132
146
|
|
|
147
|
+
public readonly tracer: Tracer;
|
|
148
|
+
|
|
133
149
|
protected requests: RequestWithExpiry[] = [];
|
|
134
150
|
|
|
135
151
|
constructor(
|
|
136
|
-
private config:
|
|
152
|
+
private config: Pick<SequencerPublisherConfig, 'fishermanMode'> &
|
|
153
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration'> & { l1ChainId: number },
|
|
137
154
|
deps: {
|
|
138
155
|
telemetry?: TelemetryClient;
|
|
139
|
-
|
|
140
|
-
l1TxUtils:
|
|
156
|
+
blobClient: BlobClientInterface;
|
|
157
|
+
l1TxUtils: L1TxUtils;
|
|
141
158
|
rollupContract: RollupContract;
|
|
142
159
|
slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
143
160
|
governanceProposerContract: GovernanceProposerContract;
|
|
@@ -145,7 +162,7 @@ export class SequencerPublisher {
|
|
|
145
162
|
epochCache: EpochCache;
|
|
146
163
|
dateProvider: DateProvider;
|
|
147
164
|
metrics: SequencerPublisherMetrics;
|
|
148
|
-
lastActions: Partial<Record<Action,
|
|
165
|
+
lastActions: Partial<Record<Action, SlotNumber>>;
|
|
149
166
|
log?: Logger;
|
|
150
167
|
},
|
|
151
168
|
) {
|
|
@@ -154,11 +171,11 @@ export class SequencerPublisher {
|
|
|
154
171
|
this.epochCache = deps.epochCache;
|
|
155
172
|
this.lastActions = deps.lastActions;
|
|
156
173
|
|
|
157
|
-
this.
|
|
158
|
-
deps.blobSinkClient ?? createBlobSinkClient(config, { logger: createLogger('sequencer:blob-sink:client') });
|
|
174
|
+
this.blobClient = deps.blobClient;
|
|
159
175
|
|
|
160
176
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
161
177
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
178
|
+
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
162
179
|
this.l1TxUtils = deps.l1TxUtils;
|
|
163
180
|
|
|
164
181
|
this.rollupContract = deps.rollupContract;
|
|
@@ -172,24 +189,130 @@ export class SequencerPublisher {
|
|
|
172
189
|
this.slashingProposerContract = newSlashingProposer;
|
|
173
190
|
});
|
|
174
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
|
+
);
|
|
175
208
|
}
|
|
176
209
|
|
|
177
210
|
public getRollupContract(): RollupContract {
|
|
178
211
|
return this.rollupContract;
|
|
179
212
|
}
|
|
180
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
|
+
|
|
181
222
|
public getSenderAddress() {
|
|
182
223
|
return this.l1TxUtils.getSenderAddress();
|
|
183
224
|
}
|
|
184
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
|
+
|
|
185
241
|
public addRequest(request: RequestWithExpiry) {
|
|
186
242
|
this.requests.push(request);
|
|
187
243
|
}
|
|
188
244
|
|
|
189
|
-
public getCurrentL2Slot():
|
|
245
|
+
public getCurrentL2Slot(): SlotNumber {
|
|
190
246
|
return this.epochCache.getEpochAndSlotNow().slot;
|
|
191
247
|
}
|
|
192
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
|
+
|
|
193
316
|
/**
|
|
194
317
|
* Sends all requests that are still valid.
|
|
195
318
|
* @returns one of:
|
|
@@ -197,10 +320,11 @@ export class SequencerPublisher {
|
|
|
197
320
|
* - a receipt and errorMsg if it failed on L1
|
|
198
321
|
* - undefined if no valid requests are found OR the tx failed to send.
|
|
199
322
|
*/
|
|
323
|
+
@trackSpan('SequencerPublisher.sendRequests')
|
|
200
324
|
public async sendRequests() {
|
|
201
325
|
const requestsToProcess = [...this.requests];
|
|
202
326
|
this.requests = [];
|
|
203
|
-
if (this.interrupted) {
|
|
327
|
+
if (this.interrupted || requestsToProcess.length === 0) {
|
|
204
328
|
return undefined;
|
|
205
329
|
}
|
|
206
330
|
const currentL2Slot = this.getCurrentL2Slot();
|
|
@@ -243,7 +367,16 @@ export class SequencerPublisher {
|
|
|
243
367
|
|
|
244
368
|
// Merge gasConfigs. Yields the sum of gasLimits, and the earliest txTimeoutAt, or undefined if no gasConfig sets them.
|
|
245
369
|
const gasLimits = gasConfigs.map(g => g?.gasLimit).filter((g): g is bigint => g !== undefined);
|
|
246
|
-
|
|
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
|
+
}
|
|
247
380
|
const txTimeoutAts = gasConfigs.map(g => g?.txTimeoutAt).filter((g): g is Date => g !== undefined);
|
|
248
381
|
const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map(g => g.getTime()))) : undefined; // earliest
|
|
249
382
|
const txConfig: RequestWithExpiry['gasConfig'] = { gasLimit, txTimeoutAt };
|
|
@@ -314,13 +447,15 @@ export class SequencerPublisher {
|
|
|
314
447
|
public canProposeAtNextEthBlock(
|
|
315
448
|
tipArchive: Fr,
|
|
316
449
|
msgSender: EthAddress,
|
|
317
|
-
opts: {
|
|
450
|
+
opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
318
451
|
) {
|
|
319
452
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
320
453
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
321
454
|
|
|
322
455
|
return this.rollupContract
|
|
323
|
-
.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), this.ethereumSlotDuration,
|
|
456
|
+
.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
|
|
457
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
458
|
+
})
|
|
324
459
|
.catch(err => {
|
|
325
460
|
if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
|
|
326
461
|
this.log.warn(`Failed canProposeAtTime check with ${ignoredErrors.find(e => err.message.includes(e))}`, {
|
|
@@ -338,10 +473,11 @@ export class SequencerPublisher {
|
|
|
338
473
|
* It will throw if the block header is invalid.
|
|
339
474
|
* @param header - The block header to validate
|
|
340
475
|
*/
|
|
476
|
+
@trackSpan('SequencerPublisher.validateBlockHeader')
|
|
341
477
|
public async validateBlockHeader(
|
|
342
|
-
header:
|
|
343
|
-
opts?: {
|
|
344
|
-
) {
|
|
478
|
+
header: CheckpointHeader,
|
|
479
|
+
opts?: { forcePendingCheckpointNumber: CheckpointNumber | undefined },
|
|
480
|
+
): Promise<void> {
|
|
345
481
|
const flags = { ignoreDA: true, ignoreSignatures: true };
|
|
346
482
|
|
|
347
483
|
const args = [
|
|
@@ -350,15 +486,27 @@ export class SequencerPublisher {
|
|
|
350
486
|
[], // no signers
|
|
351
487
|
Signature.empty().toViemSignature(),
|
|
352
488
|
`0x${'0'.repeat(64)}`, // 32 empty bytes
|
|
353
|
-
header.
|
|
489
|
+
header.blobsHash.toString(),
|
|
354
490
|
flags,
|
|
355
491
|
] as const;
|
|
356
492
|
|
|
357
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
|
+
});
|
|
358
509
|
|
|
359
|
-
// use sender balance to simulate
|
|
360
|
-
const balance = await this.l1TxUtils.getSenderBalance();
|
|
361
|
-
this.log.debug(`Simulating validateHeader with balance: ${balance}`);
|
|
362
510
|
await this.l1TxUtils.simulate(
|
|
363
511
|
{
|
|
364
512
|
to: this.rollupContract.address,
|
|
@@ -366,99 +514,116 @@ export class SequencerPublisher {
|
|
|
366
514
|
from: MULTI_CALL_3_ADDRESS,
|
|
367
515
|
},
|
|
368
516
|
{ time: ts + 1n },
|
|
369
|
-
|
|
370
|
-
{ address: MULTI_CALL_3_ADDRESS, balance },
|
|
371
|
-
...(await this.rollupContract.makePendingBlockNumberOverride(opts?.forcePendingBlockNumber)),
|
|
372
|
-
],
|
|
517
|
+
stateOverrides,
|
|
373
518
|
);
|
|
374
519
|
this.log.debug(`Simulated validateHeader`);
|
|
375
520
|
}
|
|
376
521
|
|
|
377
522
|
/**
|
|
378
|
-
* Simulate making a call to invalidate a
|
|
379
|
-
* @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)
|
|
380
525
|
*/
|
|
381
|
-
public async
|
|
382
|
-
validationResult:
|
|
383
|
-
): Promise<
|
|
526
|
+
public async simulateInvalidateCheckpoint(
|
|
527
|
+
validationResult: ValidateCheckpointResult,
|
|
528
|
+
): Promise<InvalidateCheckpointRequest | undefined> {
|
|
384
529
|
if (validationResult.valid) {
|
|
385
530
|
return undefined;
|
|
386
531
|
}
|
|
387
532
|
|
|
388
|
-
const { reason,
|
|
389
|
-
const
|
|
390
|
-
const logData = { ...
|
|
533
|
+
const { reason, checkpoint } = validationResult;
|
|
534
|
+
const checkpointNumber = checkpoint.checkpointNumber;
|
|
535
|
+
const logData = { ...checkpoint, reason };
|
|
391
536
|
|
|
392
|
-
const
|
|
393
|
-
if (
|
|
537
|
+
const currentCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
538
|
+
if (currentCheckpointNumber < checkpointNumber) {
|
|
394
539
|
this.log.verbose(
|
|
395
|
-
`Skipping
|
|
396
|
-
{
|
|
540
|
+
`Skipping checkpoint ${checkpointNumber} invalidation since it has already been removed from the pending chain`,
|
|
541
|
+
{ currentCheckpointNumber, ...logData },
|
|
397
542
|
);
|
|
398
543
|
return undefined;
|
|
399
544
|
}
|
|
400
545
|
|
|
401
|
-
const request = this.
|
|
402
|
-
this.log.debug(`Simulating invalidate
|
|
546
|
+
const request = this.buildInvalidateCheckpointRequest(validationResult);
|
|
547
|
+
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
|
|
403
548
|
|
|
404
549
|
try {
|
|
405
|
-
const { gasUsed } = await this.l1TxUtils.simulate(
|
|
406
|
-
|
|
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
|
+
});
|
|
407
561
|
|
|
408
|
-
return {
|
|
562
|
+
return {
|
|
563
|
+
request,
|
|
564
|
+
gasUsed,
|
|
565
|
+
checkpointNumber,
|
|
566
|
+
forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
|
|
567
|
+
reason,
|
|
568
|
+
};
|
|
409
569
|
} catch (err) {
|
|
410
570
|
const viemError = formatViemError(err);
|
|
411
571
|
|
|
412
|
-
// If the error is due to the
|
|
413
|
-
// we can safely ignore it and return undefined so we go ahead with
|
|
414
|
-
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')) {
|
|
415
575
|
this.log.verbose(
|
|
416
|
-
`Simulation for invalidate
|
|
576
|
+
`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
|
|
417
577
|
{ ...logData, request, error: viemError.message },
|
|
418
578
|
);
|
|
419
|
-
const
|
|
420
|
-
if (
|
|
421
|
-
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 });
|
|
422
582
|
return undefined;
|
|
423
583
|
} else {
|
|
424
584
|
this.log.error(
|
|
425
|
-
`Simulation for invalidate ${
|
|
585
|
+
`Simulation for invalidate checkpoint ${checkpointNumber} failed and it is still in pending chain`,
|
|
426
586
|
viemError,
|
|
427
587
|
logData,
|
|
428
588
|
);
|
|
429
|
-
throw new Error(
|
|
430
|
-
|
|
431
|
-
|
|
589
|
+
throw new Error(
|
|
590
|
+
`Failed to simulate invalidate checkpoint ${checkpointNumber} while it is still in pending chain`,
|
|
591
|
+
{
|
|
592
|
+
cause: viemError,
|
|
593
|
+
},
|
|
594
|
+
);
|
|
432
595
|
}
|
|
433
596
|
}
|
|
434
597
|
|
|
435
|
-
// Otherwise, throw. We cannot build the next
|
|
436
|
-
this.log.error(`Simulation for invalidate
|
|
437
|
-
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 });
|
|
438
601
|
}
|
|
439
602
|
}
|
|
440
603
|
|
|
441
|
-
private
|
|
604
|
+
private buildInvalidateCheckpointRequest(validationResult: ValidateCheckpointResult) {
|
|
442
605
|
if (validationResult.valid) {
|
|
443
|
-
throw new Error('Cannot invalidate a valid
|
|
606
|
+
throw new Error('Cannot invalidate a valid checkpoint');
|
|
444
607
|
}
|
|
445
608
|
|
|
446
|
-
const {
|
|
447
|
-
const logData = { ...
|
|
448
|
-
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);
|
|
449
612
|
|
|
450
|
-
const attestationsAndSigners = new CommitteeAttestationsAndSigners(
|
|
613
|
+
const attestationsAndSigners = new CommitteeAttestationsAndSigners(
|
|
614
|
+
validationResult.attestations,
|
|
615
|
+
).getPackedAttestations();
|
|
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,47 +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
|
-
|
|
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 blobs = await Blob.getBlobsPerBlock(block.body.toBlobFields());
|
|
503
|
-
const blobInput = Blob.getPrefixedEthBlobCommitments(blobs);
|
|
645
|
+
const blobFields = checkpoint.toBlobFields();
|
|
646
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
647
|
+
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
504
648
|
|
|
505
649
|
const args = [
|
|
506
650
|
{
|
|
507
|
-
header:
|
|
508
|
-
archive: toHex(
|
|
509
|
-
stateReference: block.header.state.toViem(),
|
|
651
|
+
header: checkpoint.header.toViem(),
|
|
652
|
+
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
510
653
|
oracleInput: {
|
|
511
|
-
feeAssetPriceModifier:
|
|
654
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
512
655
|
},
|
|
513
656
|
},
|
|
514
657
|
attestationsAndSigners.getPackedAttestations(),
|
|
@@ -522,7 +665,7 @@ export class SequencerPublisher {
|
|
|
522
665
|
}
|
|
523
666
|
|
|
524
667
|
private async enqueueCastSignalHelper(
|
|
525
|
-
slotNumber:
|
|
668
|
+
slotNumber: SlotNumber,
|
|
526
669
|
timestamp: bigint,
|
|
527
670
|
signalType: GovernanceSignalAction,
|
|
528
671
|
payload: EthAddress,
|
|
@@ -544,10 +687,45 @@ export class SequencerPublisher {
|
|
|
544
687
|
const round = await base.computeRound(slotNumber);
|
|
545
688
|
const roundInfo = await base.getRoundInfo(this.rollupContract.address, round);
|
|
546
689
|
|
|
690
|
+
if (roundInfo.quorumReached) {
|
|
691
|
+
return false;
|
|
692
|
+
}
|
|
693
|
+
|
|
547
694
|
if (roundInfo.lastSignalSlot >= slotNumber) {
|
|
548
695
|
return false;
|
|
549
696
|
}
|
|
550
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
|
+
|
|
551
729
|
const cachedLastVote = this.lastActions[signalType];
|
|
552
730
|
this.lastActions[signalType] = slotNumber;
|
|
553
731
|
const action = signalType;
|
|
@@ -567,7 +745,7 @@ export class SequencerPublisher {
|
|
|
567
745
|
});
|
|
568
746
|
|
|
569
747
|
try {
|
|
570
|
-
await this.l1TxUtils.simulate(request, { time: timestamp }, [], ErrorsAbi);
|
|
748
|
+
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
571
749
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
572
750
|
} catch (err) {
|
|
573
751
|
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, err);
|
|
@@ -590,14 +768,14 @@ export class SequencerPublisher {
|
|
|
590
768
|
const logData = { ...result, slotNumber, round, payload: payload.toString() };
|
|
591
769
|
if (!success) {
|
|
592
770
|
this.log.error(
|
|
593
|
-
`Signaling in
|
|
771
|
+
`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} failed`,
|
|
594
772
|
logData,
|
|
595
773
|
);
|
|
596
774
|
this.lastActions[signalType] = cachedLastVote;
|
|
597
775
|
return false;
|
|
598
776
|
} else {
|
|
599
777
|
this.log.info(
|
|
600
|
-
`Signaling in
|
|
778
|
+
`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} succeeded`,
|
|
601
779
|
logData,
|
|
602
780
|
);
|
|
603
781
|
return true;
|
|
@@ -607,6 +785,17 @@ export class SequencerPublisher {
|
|
|
607
785
|
return true;
|
|
608
786
|
}
|
|
609
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
|
+
|
|
610
799
|
/**
|
|
611
800
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
612
801
|
* @param slotNumber - The slot number to cast a signal for.
|
|
@@ -615,7 +804,7 @@ export class SequencerPublisher {
|
|
|
615
804
|
*/
|
|
616
805
|
public enqueueGovernanceCastSignal(
|
|
617
806
|
governancePayload: EthAddress,
|
|
618
|
-
slotNumber:
|
|
807
|
+
slotNumber: SlotNumber,
|
|
619
808
|
timestamp: bigint,
|
|
620
809
|
signerAddress: EthAddress,
|
|
621
810
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
@@ -634,7 +823,7 @@ export class SequencerPublisher {
|
|
|
634
823
|
/** Enqueues all slashing actions as returned by the slasher client. */
|
|
635
824
|
public async enqueueSlashingActions(
|
|
636
825
|
actions: ProposerSlashAction[],
|
|
637
|
-
slotNumber:
|
|
826
|
+
slotNumber: SlotNumber,
|
|
638
827
|
timestamp: bigint,
|
|
639
828
|
signerAddress: EthAddress,
|
|
640
829
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
@@ -754,29 +943,25 @@ export class SequencerPublisher {
|
|
|
754
943
|
return true;
|
|
755
944
|
}
|
|
756
945
|
|
|
757
|
-
/**
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
* @param block - L2 block to propose.
|
|
761
|
-
* @returns True if the tx has been enqueued, throws otherwise. See #9315
|
|
762
|
-
*/
|
|
763
|
-
public async enqueueProposeL2Block(
|
|
764
|
-
block: L2Block,
|
|
946
|
+
/** Simulates and enqueues a proposal for a checkpoint on L1 */
|
|
947
|
+
public async enqueueProposeCheckpoint(
|
|
948
|
+
checkpoint: Checkpoint,
|
|
765
949
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
766
950
|
attestationsAndSignersSignature: Signature,
|
|
767
|
-
opts: { txTimeoutAt?: Date;
|
|
768
|
-
): Promise<
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
const
|
|
772
|
-
const
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
951
|
+
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
952
|
+
): Promise<void> {
|
|
953
|
+
const checkpointHeader = checkpoint.header;
|
|
954
|
+
|
|
955
|
+
const blobFields = checkpoint.toBlobFields();
|
|
956
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
957
|
+
|
|
958
|
+
const proposeTxArgs: L1ProcessArgs = {
|
|
959
|
+
header: checkpointHeader,
|
|
960
|
+
archive: checkpoint.archive.root.toBuffer(),
|
|
777
961
|
blobs,
|
|
778
962
|
attestationsAndSigners,
|
|
779
963
|
attestationsAndSignersSignature,
|
|
964
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
780
965
|
};
|
|
781
966
|
|
|
782
967
|
let ts: bigint;
|
|
@@ -787,22 +972,29 @@ export class SequencerPublisher {
|
|
|
787
972
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
788
973
|
// make time consistency checks break.
|
|
789
974
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
790
|
-
ts = await this.
|
|
975
|
+
ts = await this.validateCheckpointForSubmission(
|
|
976
|
+
checkpoint,
|
|
977
|
+
attestationsAndSigners,
|
|
978
|
+
attestationsAndSignersSignature,
|
|
979
|
+
opts,
|
|
980
|
+
);
|
|
791
981
|
} catch (err: any) {
|
|
792
|
-
this.log.error(`
|
|
793
|
-
...
|
|
794
|
-
slotNumber:
|
|
795
|
-
|
|
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,
|
|
796
986
|
});
|
|
797
987
|
throw err;
|
|
798
988
|
}
|
|
799
989
|
|
|
800
|
-
this.log.verbose(`Enqueuing
|
|
801
|
-
await this.addProposeTx(
|
|
802
|
-
return true;
|
|
990
|
+
this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
|
|
991
|
+
await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
|
|
803
992
|
}
|
|
804
993
|
|
|
805
|
-
public
|
|
994
|
+
public enqueueInvalidateCheckpoint(
|
|
995
|
+
request: InvalidateCheckpointRequest | undefined,
|
|
996
|
+
opts: { txTimeoutAt?: Date } = {},
|
|
997
|
+
) {
|
|
806
998
|
if (!request) {
|
|
807
999
|
return;
|
|
808
1000
|
}
|
|
@@ -810,24 +1002,24 @@ export class SequencerPublisher {
|
|
|
810
1002
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
811
1003
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(request.gasUsed) * 64) / 63)));
|
|
812
1004
|
|
|
813
|
-
const { gasUsed,
|
|
814
|
-
const logData = { gasUsed,
|
|
815
|
-
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);
|
|
816
1008
|
this.addRequest({
|
|
817
1009
|
action: `invalidate-by-${request.reason}`,
|
|
818
1010
|
request: request.request,
|
|
819
1011
|
gasConfig: { gasLimit, txTimeoutAt: opts.txTimeoutAt },
|
|
820
|
-
lastValidL2Slot: this.getCurrentL2Slot() +
|
|
1012
|
+
lastValidL2Slot: SlotNumber(this.getCurrentL2Slot() + 2),
|
|
821
1013
|
checkSuccess: (_req, result) => {
|
|
822
1014
|
const success =
|
|
823
1015
|
result &&
|
|
824
1016
|
result.receipt &&
|
|
825
1017
|
result.receipt.status === 'success' &&
|
|
826
|
-
tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, '
|
|
1018
|
+
tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointInvalidated');
|
|
827
1019
|
if (!success) {
|
|
828
|
-
this.log.warn(`Invalidate
|
|
1020
|
+
this.log.warn(`Invalidate checkpoint ${request.checkpointNumber} failed`, { ...result, ...logData });
|
|
829
1021
|
} else {
|
|
830
|
-
this.log.info(`Invalidate
|
|
1022
|
+
this.log.info(`Invalidate checkpoint ${request.checkpointNumber} succeeded`, { ...result, ...logData });
|
|
831
1023
|
}
|
|
832
1024
|
return !!success;
|
|
833
1025
|
},
|
|
@@ -838,7 +1030,7 @@ export class SequencerPublisher {
|
|
|
838
1030
|
action: Action,
|
|
839
1031
|
request: L1TxRequest,
|
|
840
1032
|
checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
|
|
841
|
-
slotNumber:
|
|
1033
|
+
slotNumber: SlotNumber,
|
|
842
1034
|
timestamp: bigint,
|
|
843
1035
|
) {
|
|
844
1036
|
const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
|
|
@@ -853,12 +1045,14 @@ export class SequencerPublisher {
|
|
|
853
1045
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
854
1046
|
|
|
855
1047
|
let gasUsed: bigint;
|
|
1048
|
+
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
856
1049
|
try {
|
|
857
|
-
({ 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
|
|
858
1051
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
859
1052
|
} catch (err) {
|
|
860
|
-
const viemError = formatViemError(err);
|
|
1053
|
+
const viemError = formatViemError(err, simulateAbi);
|
|
861
1054
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1055
|
+
|
|
862
1056
|
return false;
|
|
863
1057
|
}
|
|
864
1058
|
|
|
@@ -866,10 +1060,14 @@ export class SequencerPublisher {
|
|
|
866
1060
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(gasUsed) * 64) / 63)));
|
|
867
1061
|
logData.gasLimit = gasLimit;
|
|
868
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
|
+
|
|
869
1067
|
this.log.debug(`Enqueuing ${action}`, logData);
|
|
870
1068
|
this.addRequest({
|
|
871
1069
|
action,
|
|
872
|
-
request,
|
|
1070
|
+
request: requestWithAbi,
|
|
873
1071
|
gasConfig: { gasLimit },
|
|
874
1072
|
lastValidL2Slot: slotNumber,
|
|
875
1073
|
checkSuccess: (_req, result) => {
|
|
@@ -906,44 +1104,52 @@ export class SequencerPublisher {
|
|
|
906
1104
|
private async prepareProposeTx(
|
|
907
1105
|
encodedData: L1ProcessArgs,
|
|
908
1106
|
timestamp: bigint,
|
|
909
|
-
options: {
|
|
1107
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
910
1108
|
) {
|
|
911
1109
|
const kzg = Blob.getViemKzgInstance();
|
|
912
|
-
const blobInput =
|
|
1110
|
+
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
913
1111
|
this.log.debug('Validating blob input', { blobInput });
|
|
914
|
-
const blobEvaluationGas = await this.l1TxUtils
|
|
915
|
-
.estimateGas(
|
|
916
|
-
this.getSenderAddress().toString(),
|
|
917
|
-
{
|
|
918
|
-
to: this.rollupContract.address,
|
|
919
|
-
data: encodeFunctionData({
|
|
920
|
-
abi: RollupAbi,
|
|
921
|
-
functionName: 'validateBlobs',
|
|
922
|
-
args: [blobInput],
|
|
923
|
-
}),
|
|
924
|
-
},
|
|
925
|
-
{},
|
|
926
|
-
{
|
|
927
|
-
blobs: encodedData.blobs.map(b => b.data),
|
|
928
|
-
kzg,
|
|
929
|
-
},
|
|
930
|
-
)
|
|
931
|
-
.catch(err => {
|
|
932
|
-
const { message, metaMessages } = formatViemError(err);
|
|
933
|
-
this.log.error(`Failed to validate blobs`, message, { metaMessages });
|
|
934
|
-
throw new Error('Failed to validate blobs');
|
|
935
|
-
});
|
|
936
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
|
+
}
|
|
937
1145
|
const signers = encodedData.attestationsAndSigners.getSigners().map(signer => signer.toString());
|
|
938
1146
|
|
|
939
1147
|
const args = [
|
|
940
1148
|
{
|
|
941
1149
|
header: encodedData.header.toViem(),
|
|
942
1150
|
archive: toHex(encodedData.archive),
|
|
943
|
-
stateReference: encodedData.stateReference.toViem(),
|
|
944
1151
|
oracleInput: {
|
|
945
|
-
|
|
946
|
-
feeAssetPriceModifier: 0n,
|
|
1152
|
+
feeAssetPriceModifier: encodedData.feeAssetPriceModifier,
|
|
947
1153
|
},
|
|
948
1154
|
},
|
|
949
1155
|
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
@@ -968,9 +1174,8 @@ export class SequencerPublisher {
|
|
|
968
1174
|
{
|
|
969
1175
|
readonly header: ViemHeader;
|
|
970
1176
|
readonly archive: `0x${string}`;
|
|
971
|
-
readonly stateReference: ViemStateReference;
|
|
972
1177
|
readonly oracleInput: {
|
|
973
|
-
readonly feeAssetPriceModifier:
|
|
1178
|
+
readonly feeAssetPriceModifier: bigint;
|
|
974
1179
|
};
|
|
975
1180
|
},
|
|
976
1181
|
ViemCommitteeAttestations,
|
|
@@ -979,7 +1184,7 @@ export class SequencerPublisher {
|
|
|
979
1184
|
`0x${string}`,
|
|
980
1185
|
],
|
|
981
1186
|
timestamp: bigint,
|
|
982
|
-
options: {
|
|
1187
|
+
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
983
1188
|
) {
|
|
984
1189
|
const rollupData = encodeFunctionData({
|
|
985
1190
|
abi: RollupAbi,
|
|
@@ -987,44 +1192,64 @@ export class SequencerPublisher {
|
|
|
987
1192
|
args,
|
|
988
1193
|
});
|
|
989
1194
|
|
|
990
|
-
// override the pending
|
|
991
|
-
const
|
|
992
|
-
options.
|
|
993
|
-
? 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)
|
|
994
1199
|
: []
|
|
995
1200
|
).flatMap(override => override.stateDiff ?? []);
|
|
996
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
|
+
|
|
997
1220
|
const simulationResult = await this.l1TxUtils
|
|
998
1221
|
.simulate(
|
|
999
1222
|
{
|
|
1000
1223
|
to: this.rollupContract.address,
|
|
1001
1224
|
data: rollupData,
|
|
1002
|
-
gas:
|
|
1225
|
+
gas: MAX_L1_TX_LIMIT,
|
|
1226
|
+
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1003
1227
|
},
|
|
1004
1228
|
{
|
|
1005
1229
|
// @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
|
|
1006
1230
|
time: timestamp + 1n,
|
|
1007
1231
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1008
|
-
gasLimit:
|
|
1232
|
+
gasLimit: MAX_L1_TX_LIMIT * 2n,
|
|
1009
1233
|
},
|
|
1010
|
-
|
|
1011
|
-
{
|
|
1012
|
-
address: this.rollupContract.address,
|
|
1013
|
-
// @note we override checkBlob to false since blobs are not part simulate()
|
|
1014
|
-
stateDiff: [
|
|
1015
|
-
{ slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
|
|
1016
|
-
...forcePendingBlockNumberStateDiff,
|
|
1017
|
-
],
|
|
1018
|
-
},
|
|
1019
|
-
],
|
|
1234
|
+
stateOverrides,
|
|
1020
1235
|
RollupAbi,
|
|
1021
1236
|
{
|
|
1022
1237
|
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
1023
|
-
fallbackGasEstimate:
|
|
1238
|
+
fallbackGasEstimate: MAX_L1_TX_LIMIT,
|
|
1024
1239
|
},
|
|
1025
1240
|
)
|
|
1026
1241
|
.catch(err => {
|
|
1027
|
-
|
|
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);
|
|
1028
1253
|
throw err;
|
|
1029
1254
|
});
|
|
1030
1255
|
|
|
@@ -1032,11 +1257,12 @@ export class SequencerPublisher {
|
|
|
1032
1257
|
}
|
|
1033
1258
|
|
|
1034
1259
|
private async addProposeTx(
|
|
1035
|
-
|
|
1260
|
+
checkpoint: Checkpoint,
|
|
1036
1261
|
encodedData: L1ProcessArgs,
|
|
1037
|
-
opts: { txTimeoutAt?: Date;
|
|
1262
|
+
opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
1038
1263
|
timestamp: bigint,
|
|
1039
1264
|
): Promise<void> {
|
|
1265
|
+
const slot = checkpoint.header.slotNumber;
|
|
1040
1266
|
const timer = new Timer();
|
|
1041
1267
|
const kzg = Blob.getViemKzgInstance();
|
|
1042
1268
|
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
@@ -1051,11 +1277,13 @@ export class SequencerPublisher {
|
|
|
1051
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
|
|
1052
1278
|
);
|
|
1053
1279
|
|
|
1054
|
-
// Send the blobs to the blob
|
|
1055
|
-
// tx fails but it does get mined. We make sure that the blobs are sent to the blob
|
|
1056
|
-
void
|
|
1057
|
-
this.
|
|
1058
|
-
|
|
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
|
+
);
|
|
1059
1287
|
|
|
1060
1288
|
return this.addRequest({
|
|
1061
1289
|
action: 'propose',
|
|
@@ -1063,7 +1291,7 @@ export class SequencerPublisher {
|
|
|
1063
1291
|
to: this.rollupContract.address,
|
|
1064
1292
|
data: rollupData,
|
|
1065
1293
|
},
|
|
1066
|
-
lastValidL2Slot:
|
|
1294
|
+
lastValidL2Slot: checkpoint.header.slotNumber,
|
|
1067
1295
|
gasConfig: { ...opts, gasLimit },
|
|
1068
1296
|
blobConfig: {
|
|
1069
1297
|
blobs: encodedData.blobs.map(b => b.data),
|
|
@@ -1077,12 +1305,13 @@ export class SequencerPublisher {
|
|
|
1077
1305
|
const success =
|
|
1078
1306
|
receipt &&
|
|
1079
1307
|
receipt.status === 'success' &&
|
|
1080
|
-
tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, '
|
|
1308
|
+
tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointProposed');
|
|
1309
|
+
|
|
1081
1310
|
if (success) {
|
|
1082
1311
|
const endBlock = receipt.blockNumber;
|
|
1083
1312
|
const inclusionBlocks = Number(endBlock - startBlock);
|
|
1084
1313
|
const { calldataGas, calldataSize, sender } = stats!;
|
|
1085
|
-
const publishStats:
|
|
1314
|
+
const publishStats: L1PublishCheckpointStats = {
|
|
1086
1315
|
gasPrice: receipt.effectiveGasPrice,
|
|
1087
1316
|
gasUsed: receipt.gasUsed,
|
|
1088
1317
|
blobGasUsed: receipt.blobGasUsed ?? 0n,
|
|
@@ -1091,23 +1320,26 @@ export class SequencerPublisher {
|
|
|
1091
1320
|
calldataGas,
|
|
1092
1321
|
calldataSize,
|
|
1093
1322
|
sender,
|
|
1094
|
-
...
|
|
1323
|
+
...checkpoint.getStats(),
|
|
1095
1324
|
eventName: 'rollup-published-to-l1',
|
|
1096
1325
|
blobCount: encodedData.blobs.length,
|
|
1097
1326
|
inclusionBlocks,
|
|
1098
1327
|
};
|
|
1099
|
-
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
|
+
});
|
|
1100
1333
|
this.metrics.recordProcessBlockTx(timer.ms(), publishStats);
|
|
1101
1334
|
|
|
1102
1335
|
return true;
|
|
1103
1336
|
} else {
|
|
1104
1337
|
this.metrics.recordFailedTx('process');
|
|
1105
|
-
this.log.error(
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
});
|
|
1338
|
+
this.log.error(
|
|
1339
|
+
`Publishing checkpoint at slot ${slot} failed with ${errorMsg ?? 'no error message'}`,
|
|
1340
|
+
undefined,
|
|
1341
|
+
{ ...checkpoint.getStats(), ...receipt },
|
|
1342
|
+
);
|
|
1111
1343
|
return false;
|
|
1112
1344
|
}
|
|
1113
1345
|
},
|