@aztec/sequencer-client 4.0.0-nightly.20250907 → 4.0.0-nightly.20260108
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 +10 -8
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +40 -28
- package/dest/config.d.ts +13 -5
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +82 -25
- package/dest/global_variable_builder/global_builder.d.ts +22 -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 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -1
- package/dest/publisher/config.d.ts +11 -8
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +21 -13
- package/dest/publisher/index.d.ts +2 -2
- package/dest/publisher/index.d.ts.map +1 -1
- package/dest/publisher/index.js +1 -1
- package/dest/publisher/sequencer-publisher-factory.d.ts +11 -5
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +9 -2
- 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 +1 -1
- package/dest/publisher/sequencer-publisher.d.ts +78 -70
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +687 -182
- package/dest/sequencer/block_builder.d.ts +6 -10
- package/dest/sequencer/block_builder.d.ts.map +1 -1
- package/dest/sequencer/block_builder.js +21 -10
- package/dest/sequencer/checkpoint_builder.d.ts +63 -0
- package/dest/sequencer/checkpoint_builder.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_builder.js +131 -0
- package/dest/sequencer/checkpoint_proposal_job.d.ts +76 -0
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_proposal_job.js +1070 -0
- package/dest/sequencer/checkpoint_voter.d.ts +34 -0
- package/dest/sequencer/checkpoint_voter.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_voter.js +85 -0
- package/dest/sequencer/config.d.ts +3 -2
- package/dest/sequencer/config.d.ts.map +1 -1
- package/dest/sequencer/errors.d.ts +11 -0
- package/dest/sequencer/errors.d.ts.map +1 -0
- package/dest/sequencer/errors.js +15 -0
- 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 +5 -1
- package/dest/sequencer/index.d.ts.map +1 -1
- package/dest/sequencer/index.js +4 -0
- package/dest/sequencer/metrics.d.ts +37 -20
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +211 -85
- package/dest/sequencer/sequencer.d.ts +110 -121
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +809 -524
- package/dest/sequencer/timetable.d.ts +57 -21
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +150 -68
- 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 +20 -28
- package/dest/sequencer/utils.d.ts.map +1 -1
- package/dest/sequencer/utils.js +12 -24
- package/dest/test/index.d.ts +4 -2
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts +83 -0
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -0
- package/dest/test/mock_checkpoint_builder.js +179 -0
- package/dest/test/utils.d.ts +49 -0
- package/dest/test/utils.d.ts.map +1 -0
- package/dest/test/utils.js +94 -0
- package/dest/tx_validator/nullifier_cache.d.ts +1 -1
- package/dest/tx_validator/nullifier_cache.d.ts.map +1 -1
- package/dest/tx_validator/tx_validator_factory.d.ts +4 -3
- package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
- package/dest/tx_validator/tx_validator_factory.js +12 -9
- package/package.json +32 -31
- package/src/client/sequencer-client.ts +34 -40
- package/src/config.ts +89 -29
- package/src/global_variable_builder/global_builder.ts +67 -59
- package/src/index.ts +2 -0
- package/src/publisher/config.ts +32 -19
- package/src/publisher/index.ts +1 -1
- package/src/publisher/sequencer-publisher-factory.ts +19 -6
- package/src/publisher/sequencer-publisher-metrics.ts +3 -3
- package/src/publisher/sequencer-publisher.ts +418 -242
- package/src/sequencer/README.md +531 -0
- package/src/sequencer/block_builder.ts +28 -30
- package/src/sequencer/checkpoint_builder.ts +217 -0
- package/src/sequencer/checkpoint_proposal_job.ts +722 -0
- package/src/sequencer/checkpoint_voter.ts +105 -0
- package/src/sequencer/config.ts +2 -1
- package/src/sequencer/errors.ts +21 -0
- package/src/sequencer/events.ts +27 -0
- package/src/sequencer/index.ts +4 -0
- package/src/sequencer/metrics.ts +269 -94
- package/src/sequencer/sequencer.ts +508 -675
- package/src/sequencer/timetable.ts +181 -91
- package/src/sequencer/types.ts +6 -0
- package/src/sequencer/utils.ts +24 -29
- package/src/test/index.ts +3 -1
- package/src/test/mock_checkpoint_builder.ts +247 -0
- package/src/test/utils.ts +137 -0
- package/src/tx_validator/tx_validator_factory.ts +13 -7
|
@@ -1,47 +1,48 @@
|
|
|
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
|
-
FormattedViemError,
|
|
8
|
-
type GasPrice,
|
|
9
7
|
type GovernanceProposerContract,
|
|
10
8
|
type IEmpireBase,
|
|
11
|
-
type L1BlobInputs,
|
|
12
|
-
type L1ContractsConfig,
|
|
13
|
-
type L1GasConfig,
|
|
14
|
-
type L1TxRequest,
|
|
15
9
|
MULTI_CALL_3_ADDRESS,
|
|
16
10
|
Multicall3,
|
|
17
11
|
RollupContract,
|
|
18
12
|
type TallySlashingProposerContract,
|
|
19
|
-
type TransactionStats,
|
|
20
13
|
type ViemCommitteeAttestations,
|
|
21
14
|
type ViemHeader,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
15
|
+
} from '@aztec/ethereum/contracts';
|
|
16
|
+
import { type L1FeeAnalysisResult, L1FeeAnalyzer } from '@aztec/ethereum/l1-fee-analysis';
|
|
17
|
+
import {
|
|
18
|
+
type L1BlobInputs,
|
|
19
|
+
type L1TxConfig,
|
|
20
|
+
type L1TxRequest,
|
|
21
|
+
type TransactionStats,
|
|
22
|
+
WEI_CONST,
|
|
23
|
+
} from '@aztec/ethereum/l1-tx-utils';
|
|
26
24
|
import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs';
|
|
25
|
+
import { FormattedViemError, formatViemError, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
27
26
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
28
27
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
28
|
+
import { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
29
|
+
import { pick } from '@aztec/foundation/collection';
|
|
30
|
+
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
29
31
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
30
|
-
import type
|
|
31
|
-
import { createLogger } from '@aztec/foundation/log';
|
|
32
|
+
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
33
|
+
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
32
34
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
33
35
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
34
36
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
35
37
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
36
|
-
import {
|
|
38
|
+
import { CommitteeAttestationsAndSigners, type ValidateBlockResult } from '@aztec/stdlib/block';
|
|
39
|
+
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
37
40
|
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
38
|
-
import {
|
|
39
|
-
import type {
|
|
40
|
-
import { type
|
|
41
|
-
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
41
|
+
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
42
|
+
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
43
|
+
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
42
44
|
|
|
43
|
-
import
|
|
44
|
-
import { type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
|
|
45
|
+
import { type StateOverride, type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
|
|
45
46
|
|
|
46
47
|
import type { PublisherConfig, TxSenderConfig } from './config.js';
|
|
47
48
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
@@ -49,24 +50,17 @@ import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
|
49
50
|
/** Arguments to the process method of the rollup contract */
|
|
50
51
|
type L1ProcessArgs = {
|
|
51
52
|
/** The L2 block header. */
|
|
52
|
-
header:
|
|
53
|
+
header: CheckpointHeader;
|
|
53
54
|
/** A root of the archive tree after the L2 block is applied. */
|
|
54
55
|
archive: Buffer;
|
|
55
|
-
/** State reference after the L2 block is applied. */
|
|
56
|
-
stateReference: StateReference;
|
|
57
56
|
/** L2 block blobs containing all tx effects. */
|
|
58
57
|
blobs: Blob[];
|
|
59
|
-
/** L2 block tx hashes */
|
|
60
|
-
txHashes: TxHash[];
|
|
61
58
|
/** Attestations */
|
|
62
|
-
|
|
59
|
+
attestationsAndSigners: CommitteeAttestationsAndSigners;
|
|
60
|
+
/** Attestations and signers signature */
|
|
61
|
+
attestationsAndSignersSignature: Signature;
|
|
63
62
|
};
|
|
64
63
|
|
|
65
|
-
export enum SignalType {
|
|
66
|
-
GOVERNANCE,
|
|
67
|
-
SLASHING,
|
|
68
|
-
}
|
|
69
|
-
|
|
70
64
|
export const Actions = [
|
|
71
65
|
'invalidate-by-invalid-attestation',
|
|
72
66
|
'invalidate-by-insufficient-attestations',
|
|
@@ -78,8 +72,11 @@ export const Actions = [
|
|
|
78
72
|
'vote-offenses',
|
|
79
73
|
'execute-slash',
|
|
80
74
|
] as const;
|
|
75
|
+
|
|
81
76
|
export type Action = (typeof Actions)[number];
|
|
82
77
|
|
|
78
|
+
type GovernanceSignalAction = Extract<Action, 'governance-signal' | 'empire-slashing-signal'>;
|
|
79
|
+
|
|
83
80
|
// Sorting for actions such that invalidations go before proposals, and proposals go before votes
|
|
84
81
|
export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
|
|
85
82
|
|
|
@@ -87,19 +84,19 @@ export type InvalidateBlockRequest = {
|
|
|
87
84
|
request: L1TxRequest;
|
|
88
85
|
reason: 'invalid-attestation' | 'insufficient-attestations';
|
|
89
86
|
gasUsed: bigint;
|
|
90
|
-
blockNumber:
|
|
91
|
-
forcePendingBlockNumber:
|
|
87
|
+
blockNumber: BlockNumber;
|
|
88
|
+
forcePendingBlockNumber: BlockNumber;
|
|
92
89
|
};
|
|
93
90
|
|
|
94
91
|
interface RequestWithExpiry {
|
|
95
92
|
action: Action;
|
|
96
93
|
request: L1TxRequest;
|
|
97
|
-
lastValidL2Slot:
|
|
98
|
-
gasConfig?: Pick<
|
|
94
|
+
lastValidL2Slot: SlotNumber;
|
|
95
|
+
gasConfig?: Pick<L1TxConfig, 'txTimeoutAt' | 'gasLimit'>;
|
|
99
96
|
blobConfig?: L1BlobInputs;
|
|
100
97
|
checkSuccess: (
|
|
101
98
|
request: L1TxRequest,
|
|
102
|
-
result?: { receipt: TransactionReceipt;
|
|
99
|
+
result?: { receipt: TransactionReceipt; stats?: TransactionStats; errorMsg?: string },
|
|
103
100
|
) => boolean;
|
|
104
101
|
}
|
|
105
102
|
|
|
@@ -111,15 +108,20 @@ export class SequencerPublisher {
|
|
|
111
108
|
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
112
109
|
protected slashingLog = createLogger('sequencer:publisher:slashing');
|
|
113
110
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
};
|
|
111
|
+
protected lastActions: Partial<Record<Action, SlotNumber>> = {};
|
|
112
|
+
|
|
113
|
+
private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
|
|
118
114
|
|
|
119
|
-
protected log
|
|
115
|
+
protected log: Logger;
|
|
120
116
|
protected ethereumSlotDuration: bigint;
|
|
121
117
|
|
|
122
|
-
private
|
|
118
|
+
private blobClient: BlobClientInterface;
|
|
119
|
+
|
|
120
|
+
/** Address to use for simulations in fisherman mode (actual proposer's address) */
|
|
121
|
+
private proposerAddressForSimulation?: EthAddress;
|
|
122
|
+
|
|
123
|
+
/** L1 fee analyzer for fisherman mode */
|
|
124
|
+
private l1FeeAnalyzer?: L1FeeAnalyzer;
|
|
123
125
|
// @note - with blobs, the below estimate seems too large.
|
|
124
126
|
// Total used for full block from int_l1_pub e2e test: 1m (of which 86k is 1x blob)
|
|
125
127
|
// Total used for emptier block from above test: 429k (of which 84k is 1x blob)
|
|
@@ -137,13 +139,15 @@ export class SequencerPublisher {
|
|
|
137
139
|
public slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
138
140
|
public slashFactoryContract: SlashFactoryContract;
|
|
139
141
|
|
|
142
|
+
public readonly tracer: Tracer;
|
|
143
|
+
|
|
140
144
|
protected requests: RequestWithExpiry[] = [];
|
|
141
145
|
|
|
142
146
|
constructor(
|
|
143
147
|
private config: TxSenderConfig & PublisherConfig & Pick<L1ContractsConfig, 'ethereumSlotDuration'>,
|
|
144
148
|
deps: {
|
|
145
149
|
telemetry?: TelemetryClient;
|
|
146
|
-
|
|
150
|
+
blobClient: BlobClientInterface;
|
|
147
151
|
l1TxUtils: L1TxUtilsWithBlobs;
|
|
148
152
|
rollupContract: RollupContract;
|
|
149
153
|
slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
|
|
@@ -152,16 +156,20 @@ export class SequencerPublisher {
|
|
|
152
156
|
epochCache: EpochCache;
|
|
153
157
|
dateProvider: DateProvider;
|
|
154
158
|
metrics: SequencerPublisherMetrics;
|
|
159
|
+
lastActions: Partial<Record<Action, SlotNumber>>;
|
|
160
|
+
log?: Logger;
|
|
155
161
|
},
|
|
156
162
|
) {
|
|
163
|
+
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
157
164
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
158
165
|
this.epochCache = deps.epochCache;
|
|
166
|
+
this.lastActions = deps.lastActions;
|
|
159
167
|
|
|
160
|
-
this.
|
|
161
|
-
deps.blobSinkClient ?? createBlobSinkClient(config, { logger: createLogger('sequencer:blob-sink:client') });
|
|
168
|
+
this.blobClient = deps.blobClient;
|
|
162
169
|
|
|
163
170
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
164
171
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
172
|
+
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
165
173
|
this.l1TxUtils = deps.l1TxUtils;
|
|
166
174
|
|
|
167
175
|
this.rollupContract = deps.rollupContract;
|
|
@@ -175,6 +183,15 @@ export class SequencerPublisher {
|
|
|
175
183
|
this.slashingProposerContract = newSlashingProposer;
|
|
176
184
|
});
|
|
177
185
|
this.slashFactoryContract = deps.slashFactoryContract;
|
|
186
|
+
|
|
187
|
+
// Initialize L1 fee analyzer for fisherman mode
|
|
188
|
+
if (config.fishermanMode) {
|
|
189
|
+
this.l1FeeAnalyzer = new L1FeeAnalyzer(
|
|
190
|
+
this.l1TxUtils.client,
|
|
191
|
+
deps.dateProvider,
|
|
192
|
+
createLogger('sequencer:publisher:fee-analyzer'),
|
|
193
|
+
);
|
|
194
|
+
}
|
|
178
195
|
}
|
|
179
196
|
|
|
180
197
|
public getRollupContract(): RollupContract {
|
|
@@ -185,14 +202,96 @@ export class SequencerPublisher {
|
|
|
185
202
|
return this.l1TxUtils.getSenderAddress();
|
|
186
203
|
}
|
|
187
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Gets the L1 fee analyzer instance (only available in fisherman mode)
|
|
207
|
+
*/
|
|
208
|
+
public getL1FeeAnalyzer(): L1FeeAnalyzer | undefined {
|
|
209
|
+
return this.l1FeeAnalyzer;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Sets the proposer address to use for simulations in fisherman mode.
|
|
214
|
+
* @param proposerAddress - The actual proposer's address to use for balance lookups in simulations
|
|
215
|
+
*/
|
|
216
|
+
public setProposerAddressForSimulation(proposerAddress: EthAddress | undefined) {
|
|
217
|
+
this.proposerAddressForSimulation = proposerAddress;
|
|
218
|
+
}
|
|
219
|
+
|
|
188
220
|
public addRequest(request: RequestWithExpiry) {
|
|
189
221
|
this.requests.push(request);
|
|
190
222
|
}
|
|
191
223
|
|
|
192
|
-
public getCurrentL2Slot():
|
|
224
|
+
public getCurrentL2Slot(): SlotNumber {
|
|
193
225
|
return this.epochCache.getEpochAndSlotNow().slot;
|
|
194
226
|
}
|
|
195
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Clears all pending requests without sending them.
|
|
230
|
+
*/
|
|
231
|
+
public clearPendingRequests(): void {
|
|
232
|
+
const count = this.requests.length;
|
|
233
|
+
this.requests = [];
|
|
234
|
+
if (count > 0) {
|
|
235
|
+
this.log.debug(`Cleared ${count} pending request(s)`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Analyzes L1 fees for the pending requests without sending them.
|
|
241
|
+
* This is used in fisherman mode to validate fee calculations.
|
|
242
|
+
* @param l2SlotNumber - The L2 slot number for this analysis
|
|
243
|
+
* @param onComplete - Optional callback to invoke when analysis completes (after block is mined)
|
|
244
|
+
* @returns The analysis result (incomplete until block mines), or undefined if no requests
|
|
245
|
+
*/
|
|
246
|
+
public async analyzeL1Fees(
|
|
247
|
+
l2SlotNumber: SlotNumber,
|
|
248
|
+
onComplete?: (analysis: L1FeeAnalysisResult) => void,
|
|
249
|
+
): Promise<L1FeeAnalysisResult | undefined> {
|
|
250
|
+
if (!this.l1FeeAnalyzer) {
|
|
251
|
+
this.log.warn('L1 fee analyzer not available (not in fisherman mode)');
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const requestsToAnalyze = [...this.requests];
|
|
256
|
+
if (requestsToAnalyze.length === 0) {
|
|
257
|
+
this.log.debug('No requests to analyze for L1 fees');
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Extract blob config from requests (if any)
|
|
262
|
+
const blobConfigs = requestsToAnalyze.filter(request => request.blobConfig).map(request => request.blobConfig);
|
|
263
|
+
const blobConfig = blobConfigs[0];
|
|
264
|
+
|
|
265
|
+
// Get gas configs
|
|
266
|
+
const gasConfigs = requestsToAnalyze.filter(request => request.gasConfig).map(request => request.gasConfig);
|
|
267
|
+
const gasLimits = gasConfigs.map(g => g?.gasLimit).filter((g): g is bigint => g !== undefined);
|
|
268
|
+
const gasLimit = gasLimits.length > 0 ? gasLimits.reduce((sum, g) => sum + g, 0n) : 0n;
|
|
269
|
+
|
|
270
|
+
// Get the transaction requests
|
|
271
|
+
const l1Requests = requestsToAnalyze.map(r => r.request);
|
|
272
|
+
|
|
273
|
+
// Start the analysis
|
|
274
|
+
const analysisId = await this.l1FeeAnalyzer.startAnalysis(
|
|
275
|
+
l2SlotNumber,
|
|
276
|
+
gasLimit > 0n ? gasLimit : SequencerPublisher.PROPOSE_GAS_GUESS,
|
|
277
|
+
l1Requests,
|
|
278
|
+
blobConfig,
|
|
279
|
+
onComplete,
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
this.log.info('Started L1 fee analysis', {
|
|
283
|
+
analysisId,
|
|
284
|
+
l2SlotNumber: l2SlotNumber.toString(),
|
|
285
|
+
requestCount: requestsToAnalyze.length,
|
|
286
|
+
hasBlobConfig: !!blobConfig,
|
|
287
|
+
gasLimit: gasLimit.toString(),
|
|
288
|
+
actions: requestsToAnalyze.map(r => r.action),
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Return the analysis result (will be incomplete until block mines)
|
|
292
|
+
return this.l1FeeAnalyzer.getAnalysis(analysisId);
|
|
293
|
+
}
|
|
294
|
+
|
|
196
295
|
/**
|
|
197
296
|
* Sends all requests that are still valid.
|
|
198
297
|
* @returns one of:
|
|
@@ -200,10 +299,11 @@ export class SequencerPublisher {
|
|
|
200
299
|
* - a receipt and errorMsg if it failed on L1
|
|
201
300
|
* - undefined if no valid requests are found OR the tx failed to send.
|
|
202
301
|
*/
|
|
302
|
+
@trackSpan('SequencerPublisher.sendRequests')
|
|
203
303
|
public async sendRequests() {
|
|
204
304
|
const requestsToProcess = [...this.requests];
|
|
205
305
|
this.requests = [];
|
|
206
|
-
if (this.interrupted) {
|
|
306
|
+
if (this.interrupted || requestsToProcess.length === 0) {
|
|
207
307
|
return undefined;
|
|
208
308
|
}
|
|
209
309
|
const currentL2Slot = this.getCurrentL2Slot();
|
|
@@ -249,18 +349,21 @@ export class SequencerPublisher {
|
|
|
249
349
|
const gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
|
|
250
350
|
const txTimeoutAts = gasConfigs.map(g => g?.txTimeoutAt).filter((g): g is Date => g !== undefined);
|
|
251
351
|
const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map(g => g.getTime()))) : undefined; // earliest
|
|
252
|
-
const
|
|
352
|
+
const txConfig: RequestWithExpiry['gasConfig'] = { gasLimit, txTimeoutAt };
|
|
253
353
|
|
|
254
354
|
// Sort the requests so that proposals always go first
|
|
255
355
|
// This ensures the committee gets precomputed correctly
|
|
256
356
|
validRequests.sort((a, b) => compareActions(a.action, b.action));
|
|
257
357
|
|
|
258
358
|
try {
|
|
259
|
-
this.log.debug('Forwarding transactions', {
|
|
359
|
+
this.log.debug('Forwarding transactions', {
|
|
360
|
+
validRequests: validRequests.map(request => request.action),
|
|
361
|
+
txConfig,
|
|
362
|
+
});
|
|
260
363
|
const result = await Multicall3.forward(
|
|
261
364
|
validRequests.map(request => request.request),
|
|
262
365
|
this.l1TxUtils,
|
|
263
|
-
|
|
366
|
+
txConfig,
|
|
264
367
|
blobConfig,
|
|
265
368
|
this.rollupContract.address,
|
|
266
369
|
this.log,
|
|
@@ -285,7 +388,7 @@ export class SequencerPublisher {
|
|
|
285
388
|
|
|
286
389
|
private callbackBundledTransactions(
|
|
287
390
|
requests: RequestWithExpiry[],
|
|
288
|
-
result?: { receipt: TransactionReceipt
|
|
391
|
+
result?: { receipt: TransactionReceipt } | FormattedViemError,
|
|
289
392
|
) {
|
|
290
393
|
const actionsListStr = requests.map(r => r.action).join(', ');
|
|
291
394
|
if (result instanceof FormattedViemError) {
|
|
@@ -314,13 +417,18 @@ export class SequencerPublisher {
|
|
|
314
417
|
public canProposeAtNextEthBlock(
|
|
315
418
|
tipArchive: Fr,
|
|
316
419
|
msgSender: EthAddress,
|
|
317
|
-
opts: { forcePendingBlockNumber?:
|
|
420
|
+
opts: { forcePendingBlockNumber?: BlockNumber } = {},
|
|
318
421
|
) {
|
|
319
422
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
320
423
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
321
424
|
|
|
322
425
|
return this.rollupContract
|
|
323
|
-
.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), this.ethereumSlotDuration,
|
|
426
|
+
.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
|
|
427
|
+
forcePendingCheckpointNumber:
|
|
428
|
+
opts.forcePendingBlockNumber !== undefined
|
|
429
|
+
? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber)
|
|
430
|
+
: undefined,
|
|
431
|
+
})
|
|
324
432
|
.catch(err => {
|
|
325
433
|
if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
|
|
326
434
|
this.log.warn(`Failed canProposeAtTime check with ${ignoredErrors.find(e => err.message.includes(e))}`, {
|
|
@@ -338,26 +446,44 @@ export class SequencerPublisher {
|
|
|
338
446
|
* It will throw if the block header is invalid.
|
|
339
447
|
* @param header - The block header to validate
|
|
340
448
|
*/
|
|
449
|
+
@trackSpan('SequencerPublisher.validateBlockHeader')
|
|
341
450
|
public async validateBlockHeader(
|
|
342
|
-
header:
|
|
343
|
-
opts?: { forcePendingBlockNumber:
|
|
344
|
-
) {
|
|
451
|
+
header: CheckpointHeader,
|
|
452
|
+
opts?: { forcePendingBlockNumber: BlockNumber | undefined },
|
|
453
|
+
): Promise<void> {
|
|
345
454
|
const flags = { ignoreDA: true, ignoreSignatures: true };
|
|
346
455
|
|
|
347
456
|
const args = [
|
|
348
457
|
header.toViem(),
|
|
349
|
-
|
|
458
|
+
CommitteeAttestationsAndSigners.empty().getPackedAttestations(),
|
|
350
459
|
[], // no signers
|
|
460
|
+
Signature.empty().toViemSignature(),
|
|
351
461
|
`0x${'0'.repeat(64)}`, // 32 empty bytes
|
|
352
462
|
header.contentCommitment.blobsHash.toString(),
|
|
353
463
|
flags,
|
|
354
464
|
] as const;
|
|
355
465
|
|
|
356
466
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
467
|
+
const optsForcePendingCheckpointNumber =
|
|
468
|
+
opts?.forcePendingBlockNumber !== undefined
|
|
469
|
+
? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber)
|
|
470
|
+
: undefined;
|
|
471
|
+
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
472
|
+
optsForcePendingCheckpointNumber,
|
|
473
|
+
);
|
|
474
|
+
let balance = 0n;
|
|
475
|
+
if (this.config.fishermanMode) {
|
|
476
|
+
// In fisherman mode, we can't know where the proposer is publishing from
|
|
477
|
+
// so we just add sufficient balance to the multicall3 address
|
|
478
|
+
balance = 10n * WEI_CONST * WEI_CONST; // 10 ETH
|
|
479
|
+
} else {
|
|
480
|
+
balance = await this.l1TxUtils.getSenderBalance();
|
|
481
|
+
}
|
|
482
|
+
stateOverrides.push({
|
|
483
|
+
address: MULTI_CALL_3_ADDRESS,
|
|
484
|
+
balance,
|
|
485
|
+
});
|
|
357
486
|
|
|
358
|
-
// use sender balance to simulate
|
|
359
|
-
const balance = await this.l1TxUtils.getSenderBalance();
|
|
360
|
-
this.log.debug(`Simulating validateHeader with balance: ${balance}`);
|
|
361
487
|
await this.l1TxUtils.simulate(
|
|
362
488
|
{
|
|
363
489
|
to: this.rollupContract.address,
|
|
@@ -365,10 +491,7 @@ export class SequencerPublisher {
|
|
|
365
491
|
from: MULTI_CALL_3_ADDRESS,
|
|
366
492
|
},
|
|
367
493
|
{ time: ts + 1n },
|
|
368
|
-
|
|
369
|
-
{ address: MULTI_CALL_3_ADDRESS, balance },
|
|
370
|
-
...(await this.rollupContract.makePendingBlockNumberOverride(opts?.forcePendingBlockNumber)),
|
|
371
|
-
],
|
|
494
|
+
stateOverrides,
|
|
372
495
|
);
|
|
373
496
|
this.log.debug(`Simulated validateHeader`);
|
|
374
497
|
}
|
|
@@ -385,11 +508,11 @@ export class SequencerPublisher {
|
|
|
385
508
|
}
|
|
386
509
|
|
|
387
510
|
const { reason, block } = validationResult;
|
|
388
|
-
const blockNumber = block.
|
|
389
|
-
const logData = { ...block
|
|
511
|
+
const blockNumber = block.blockNumber;
|
|
512
|
+
const logData = { ...block, reason };
|
|
390
513
|
|
|
391
|
-
const currentBlockNumber = await this.rollupContract.
|
|
392
|
-
if (currentBlockNumber < validationResult.block.
|
|
514
|
+
const currentBlockNumber = await this.rollupContract.getCheckpointNumber();
|
|
515
|
+
if (currentBlockNumber < validationResult.block.blockNumber) {
|
|
393
516
|
this.log.verbose(
|
|
394
517
|
`Skipping block ${blockNumber} invalidation since it has already been removed from the pending chain`,
|
|
395
518
|
{ currentBlockNumber, ...logData },
|
|
@@ -398,13 +521,13 @@ export class SequencerPublisher {
|
|
|
398
521
|
}
|
|
399
522
|
|
|
400
523
|
const request = this.buildInvalidateBlockRequest(validationResult);
|
|
401
|
-
this.log.debug(`Simulating invalidate block ${blockNumber}`, logData);
|
|
524
|
+
this.log.debug(`Simulating invalidate block ${blockNumber}`, { ...logData, request });
|
|
402
525
|
|
|
403
526
|
try {
|
|
404
527
|
const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, ErrorsAbi);
|
|
405
528
|
this.log.verbose(`Simulation for invalidate block ${blockNumber} succeeded`, { ...logData, request, gasUsed });
|
|
406
529
|
|
|
407
|
-
return { request, gasUsed, blockNumber, forcePendingBlockNumber: blockNumber - 1, reason };
|
|
530
|
+
return { request, gasUsed, blockNumber, forcePendingBlockNumber: BlockNumber(blockNumber - 1), reason };
|
|
408
531
|
} catch (err) {
|
|
409
532
|
const viemError = formatViemError(err);
|
|
410
533
|
|
|
@@ -415,7 +538,7 @@ export class SequencerPublisher {
|
|
|
415
538
|
`Simulation for invalidate block ${blockNumber} failed due to block not being in pending chain`,
|
|
416
539
|
{ ...logData, request, error: viemError.message },
|
|
417
540
|
);
|
|
418
|
-
const latestPendingBlockNumber = await this.rollupContract.
|
|
541
|
+
const latestPendingBlockNumber = await this.rollupContract.getCheckpointNumber();
|
|
419
542
|
if (latestPendingBlockNumber < blockNumber) {
|
|
420
543
|
this.log.verbose(`Block number ${blockNumber} has already been invalidated`, { ...logData });
|
|
421
544
|
return undefined;
|
|
@@ -443,20 +566,24 @@ export class SequencerPublisher {
|
|
|
443
566
|
}
|
|
444
567
|
|
|
445
568
|
const { block, committee, reason } = validationResult;
|
|
446
|
-
const logData = { ...block
|
|
447
|
-
this.log.debug(`Simulating invalidate block ${block.
|
|
569
|
+
const logData = { ...block, reason };
|
|
570
|
+
this.log.debug(`Simulating invalidate block ${block.blockNumber}`, logData);
|
|
571
|
+
|
|
572
|
+
const attestationsAndSigners = new CommitteeAttestationsAndSigners(
|
|
573
|
+
validationResult.attestations,
|
|
574
|
+
).getPackedAttestations();
|
|
448
575
|
|
|
449
576
|
if (reason === 'invalid-attestation') {
|
|
450
577
|
return this.rollupContract.buildInvalidateBadAttestationRequest(
|
|
451
|
-
|
|
452
|
-
|
|
578
|
+
CheckpointNumber.fromBlockNumber(block.blockNumber),
|
|
579
|
+
attestationsAndSigners,
|
|
453
580
|
committee,
|
|
454
581
|
validationResult.invalidIndex,
|
|
455
582
|
);
|
|
456
583
|
} else if (reason === 'insufficient-attestations') {
|
|
457
584
|
return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
|
|
458
|
-
|
|
459
|
-
|
|
585
|
+
CheckpointNumber.fromBlockNumber(block.blockNumber),
|
|
586
|
+
attestationsAndSigners,
|
|
460
587
|
committee,
|
|
461
588
|
);
|
|
462
589
|
} else {
|
|
@@ -465,59 +592,46 @@ export class SequencerPublisher {
|
|
|
465
592
|
}
|
|
466
593
|
}
|
|
467
594
|
|
|
468
|
-
/**
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
*
|
|
476
|
-
*/
|
|
477
|
-
public async validateBlockForSubmission(
|
|
478
|
-
block: L2Block,
|
|
479
|
-
attestationData: { digest: Buffer; attestations: CommitteeAttestation[] } = {
|
|
480
|
-
digest: Buffer.alloc(32),
|
|
481
|
-
attestations: [],
|
|
482
|
-
},
|
|
483
|
-
options: { forcePendingBlockNumber?: number },
|
|
595
|
+
/** Simulates `propose` to make sure that the checkpoint is valid for submission */
|
|
596
|
+
@trackSpan('SequencerPublisher.validateCheckpointForSubmission')
|
|
597
|
+
public async validateCheckpointForSubmission(
|
|
598
|
+
checkpoint: Checkpoint,
|
|
599
|
+
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
600
|
+
attestationsAndSignersSignature: Signature,
|
|
601
|
+
options: { forcePendingBlockNumber?: BlockNumber }, // TODO(palla/mbps): Should this be forcePendingCheckpointNumber?
|
|
484
602
|
): Promise<bigint> {
|
|
485
603
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
486
604
|
|
|
605
|
+
// TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
|
|
487
606
|
// If we have no attestations, we still need to provide the empty attestations
|
|
488
607
|
// so that the committee is recalculated correctly
|
|
489
|
-
const ignoreSignatures =
|
|
490
|
-
if (ignoreSignatures) {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
const
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
const formattedAttestations = attestationData.attestations.map(attest => attest.toViem());
|
|
505
|
-
const signers = attestationData.attestations
|
|
506
|
-
.filter(attest => !attest.signature.isEmpty())
|
|
507
|
-
.map(attest => attest.address.toString());
|
|
608
|
+
// const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
609
|
+
// if (ignoreSignatures) {
|
|
610
|
+
// const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
|
|
611
|
+
// if (!committee) {
|
|
612
|
+
// this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
613
|
+
// throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
614
|
+
// }
|
|
615
|
+
// attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
616
|
+
// CommitteeAttestation.fromAddress(committeeMember),
|
|
617
|
+
// );
|
|
618
|
+
// }
|
|
619
|
+
|
|
620
|
+
const blobFields = checkpoint.toBlobFields();
|
|
621
|
+
const blobs = getBlobsPerL1Block(blobFields);
|
|
622
|
+
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
508
623
|
|
|
509
624
|
const args = [
|
|
510
625
|
{
|
|
511
|
-
header:
|
|
512
|
-
archive: toHex(
|
|
513
|
-
stateReference: block.header.state.toViem(),
|
|
514
|
-
txHashes: block.body.txEffects.map(txEffect => txEffect.txHash.toString()),
|
|
626
|
+
header: checkpoint.header.toViem(),
|
|
627
|
+
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
515
628
|
oracleInput: {
|
|
516
629
|
feeAssetPriceModifier: 0n,
|
|
517
630
|
},
|
|
518
631
|
},
|
|
519
|
-
|
|
520
|
-
|
|
632
|
+
attestationsAndSigners.getPackedAttestations(),
|
|
633
|
+
attestationsAndSigners.getSigners().map(signer => signer.toString()),
|
|
634
|
+
attestationsAndSignersSignature.toViemSignature(),
|
|
521
635
|
blobInput,
|
|
522
636
|
] as const;
|
|
523
637
|
|
|
@@ -526,15 +640,16 @@ export class SequencerPublisher {
|
|
|
526
640
|
}
|
|
527
641
|
|
|
528
642
|
private async enqueueCastSignalHelper(
|
|
529
|
-
slotNumber:
|
|
643
|
+
slotNumber: SlotNumber,
|
|
530
644
|
timestamp: bigint,
|
|
531
|
-
signalType:
|
|
645
|
+
signalType: GovernanceSignalAction,
|
|
532
646
|
payload: EthAddress,
|
|
533
647
|
base: IEmpireBase,
|
|
534
648
|
signerAddress: EthAddress,
|
|
535
649
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
536
650
|
): Promise<boolean> {
|
|
537
|
-
if (this.
|
|
651
|
+
if (this.lastActions[signalType] && this.lastActions[signalType] === slotNumber) {
|
|
652
|
+
this.log.debug(`Skipping duplicate vote cast signal ${signalType} for slot ${slotNumber}`);
|
|
538
653
|
return false;
|
|
539
654
|
}
|
|
540
655
|
if (payload.equals(EthAddress.ZERO)) {
|
|
@@ -547,14 +662,22 @@ export class SequencerPublisher {
|
|
|
547
662
|
const round = await base.computeRound(slotNumber);
|
|
548
663
|
const roundInfo = await base.getRoundInfo(this.rollupContract.address, round);
|
|
549
664
|
|
|
665
|
+
if (roundInfo.quorumReached) {
|
|
666
|
+
return false;
|
|
667
|
+
}
|
|
668
|
+
|
|
550
669
|
if (roundInfo.lastSignalSlot >= slotNumber) {
|
|
551
670
|
return false;
|
|
552
671
|
}
|
|
553
672
|
|
|
554
|
-
|
|
555
|
-
|
|
673
|
+
if (await this.isPayloadEmpty(payload)) {
|
|
674
|
+
this.log.warn(`Skipping vote cast for payload with empty code`);
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
556
677
|
|
|
557
|
-
const
|
|
678
|
+
const cachedLastVote = this.lastActions[signalType];
|
|
679
|
+
this.lastActions[signalType] = slotNumber;
|
|
680
|
+
const action = signalType;
|
|
558
681
|
|
|
559
682
|
const request = await base.createSignalRequestWithSignature(
|
|
560
683
|
payload.toString(),
|
|
@@ -594,14 +717,14 @@ export class SequencerPublisher {
|
|
|
594
717
|
const logData = { ...result, slotNumber, round, payload: payload.toString() };
|
|
595
718
|
if (!success) {
|
|
596
719
|
this.log.error(
|
|
597
|
-
`Signaling in
|
|
720
|
+
`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} failed`,
|
|
598
721
|
logData,
|
|
599
722
|
);
|
|
600
|
-
this.
|
|
723
|
+
this.lastActions[signalType] = cachedLastVote;
|
|
601
724
|
return false;
|
|
602
725
|
} else {
|
|
603
726
|
this.log.info(
|
|
604
|
-
`Signaling in
|
|
727
|
+
`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} succeeded`,
|
|
605
728
|
logData,
|
|
606
729
|
);
|
|
607
730
|
return true;
|
|
@@ -611,6 +734,17 @@ export class SequencerPublisher {
|
|
|
611
734
|
return true;
|
|
612
735
|
}
|
|
613
736
|
|
|
737
|
+
private async isPayloadEmpty(payload: EthAddress): Promise<boolean> {
|
|
738
|
+
const key = payload.toString();
|
|
739
|
+
const cached = this.isPayloadEmptyCache.get(key);
|
|
740
|
+
if (cached) {
|
|
741
|
+
return cached;
|
|
742
|
+
}
|
|
743
|
+
const isEmpty = !(await this.l1TxUtils.getCode(payload));
|
|
744
|
+
this.isPayloadEmptyCache.set(key, isEmpty);
|
|
745
|
+
return isEmpty;
|
|
746
|
+
}
|
|
747
|
+
|
|
614
748
|
/**
|
|
615
749
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
616
750
|
* @param slotNumber - The slot number to cast a signal for.
|
|
@@ -619,7 +753,7 @@ export class SequencerPublisher {
|
|
|
619
753
|
*/
|
|
620
754
|
public enqueueGovernanceCastSignal(
|
|
621
755
|
governancePayload: EthAddress,
|
|
622
|
-
slotNumber:
|
|
756
|
+
slotNumber: SlotNumber,
|
|
623
757
|
timestamp: bigint,
|
|
624
758
|
signerAddress: EthAddress,
|
|
625
759
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
@@ -627,7 +761,7 @@ export class SequencerPublisher {
|
|
|
627
761
|
return this.enqueueCastSignalHelper(
|
|
628
762
|
slotNumber,
|
|
629
763
|
timestamp,
|
|
630
|
-
|
|
764
|
+
'governance-signal',
|
|
631
765
|
governancePayload,
|
|
632
766
|
this.govProposerContract,
|
|
633
767
|
signerAddress,
|
|
@@ -638,7 +772,7 @@ export class SequencerPublisher {
|
|
|
638
772
|
/** Enqueues all slashing actions as returned by the slasher client. */
|
|
639
773
|
public async enqueueSlashingActions(
|
|
640
774
|
actions: ProposerSlashAction[],
|
|
641
|
-
slotNumber:
|
|
775
|
+
slotNumber: SlotNumber,
|
|
642
776
|
timestamp: bigint,
|
|
643
777
|
signerAddress: EthAddress,
|
|
644
778
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
@@ -661,7 +795,7 @@ export class SequencerPublisher {
|
|
|
661
795
|
await this.enqueueCastSignalHelper(
|
|
662
796
|
slotNumber,
|
|
663
797
|
timestamp,
|
|
664
|
-
|
|
798
|
+
'empire-slashing-signal',
|
|
665
799
|
action.payload,
|
|
666
800
|
this.slashingProposerContract,
|
|
667
801
|
signerAddress,
|
|
@@ -758,32 +892,24 @@ export class SequencerPublisher {
|
|
|
758
892
|
return true;
|
|
759
893
|
}
|
|
760
894
|
|
|
761
|
-
/**
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
attestations?: CommitteeAttestation[],
|
|
770
|
-
txHashes?: TxHash[],
|
|
771
|
-
opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: number } = {},
|
|
772
|
-
): Promise<boolean> {
|
|
773
|
-
const proposedBlockHeader = block.header.toPropose();
|
|
895
|
+
/** Simulates and enqueues a proposal for a checkpoint on L1 */
|
|
896
|
+
public async enqueueProposeCheckpoint(
|
|
897
|
+
checkpoint: Checkpoint,
|
|
898
|
+
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
899
|
+
attestationsAndSignersSignature: Signature,
|
|
900
|
+
opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: BlockNumber } = {},
|
|
901
|
+
): Promise<void> {
|
|
902
|
+
const checkpointHeader = checkpoint.header;
|
|
774
903
|
|
|
775
|
-
const
|
|
776
|
-
const
|
|
904
|
+
const blobFields = checkpoint.toBlobFields();
|
|
905
|
+
const blobs = getBlobsPerL1Block(blobFields);
|
|
777
906
|
|
|
778
|
-
const blobs = await Blob.getBlobsPerBlock(block.body.toBlobFields());
|
|
779
907
|
const proposeTxArgs = {
|
|
780
|
-
header:
|
|
781
|
-
archive:
|
|
782
|
-
stateReference: block.header.state,
|
|
783
|
-
body: block.body.toBuffer(),
|
|
908
|
+
header: checkpointHeader,
|
|
909
|
+
archive: checkpoint.archive.root.toBuffer(),
|
|
784
910
|
blobs,
|
|
785
|
-
|
|
786
|
-
|
|
911
|
+
attestationsAndSigners,
|
|
912
|
+
attestationsAndSignersSignature,
|
|
787
913
|
};
|
|
788
914
|
|
|
789
915
|
let ts: bigint;
|
|
@@ -793,21 +919,24 @@ export class SequencerPublisher {
|
|
|
793
919
|
// This means that we can avoid the simulation issues in later checks.
|
|
794
920
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
795
921
|
// make time consistency checks break.
|
|
796
|
-
const attestationData = { digest: digest.toBuffer(), attestations: attestations ?? [] };
|
|
797
922
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
798
|
-
ts = await this.
|
|
923
|
+
ts = await this.validateCheckpointForSubmission(
|
|
924
|
+
checkpoint,
|
|
925
|
+
attestationsAndSigners,
|
|
926
|
+
attestationsAndSignersSignature,
|
|
927
|
+
opts,
|
|
928
|
+
);
|
|
799
929
|
} catch (err: any) {
|
|
800
|
-
this.log.error(`
|
|
801
|
-
...
|
|
802
|
-
slotNumber:
|
|
930
|
+
this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
931
|
+
...checkpoint.getStats(),
|
|
932
|
+
slotNumber: checkpoint.header.slotNumber,
|
|
803
933
|
forcePendingBlockNumber: opts.forcePendingBlockNumber,
|
|
804
934
|
});
|
|
805
935
|
throw err;
|
|
806
936
|
}
|
|
807
937
|
|
|
808
|
-
this.log.verbose(`Enqueuing
|
|
809
|
-
await this.addProposeTx(
|
|
810
|
-
return true;
|
|
938
|
+
this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
|
|
939
|
+
await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
|
|
811
940
|
}
|
|
812
941
|
|
|
813
942
|
public enqueueInvalidateBlock(request: InvalidateBlockRequest | undefined, opts: { txTimeoutAt?: Date } = {}) {
|
|
@@ -818,19 +947,20 @@ export class SequencerPublisher {
|
|
|
818
947
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
819
948
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(request.gasUsed) * 64) / 63)));
|
|
820
949
|
|
|
821
|
-
const
|
|
950
|
+
const { gasUsed, blockNumber } = request;
|
|
951
|
+
const logData = { gasUsed, blockNumber, gasLimit, opts };
|
|
822
952
|
this.log.verbose(`Enqueuing invalidate block request`, logData);
|
|
823
953
|
this.addRequest({
|
|
824
954
|
action: `invalidate-by-${request.reason}`,
|
|
825
955
|
request: request.request,
|
|
826
956
|
gasConfig: { gasLimit, txTimeoutAt: opts.txTimeoutAt },
|
|
827
|
-
lastValidL2Slot: this.getCurrentL2Slot() +
|
|
957
|
+
lastValidL2Slot: SlotNumber(this.getCurrentL2Slot() + 2),
|
|
828
958
|
checkSuccess: (_req, result) => {
|
|
829
959
|
const success =
|
|
830
960
|
result &&
|
|
831
961
|
result.receipt &&
|
|
832
962
|
result.receipt.status === 'success' &&
|
|
833
|
-
tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, '
|
|
963
|
+
tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointInvalidated');
|
|
834
964
|
if (!success) {
|
|
835
965
|
this.log.warn(`Invalidate block ${request.blockNumber} failed`, { ...result, ...logData });
|
|
836
966
|
} else {
|
|
@@ -842,16 +972,24 @@ export class SequencerPublisher {
|
|
|
842
972
|
}
|
|
843
973
|
|
|
844
974
|
private async simulateAndEnqueueRequest(
|
|
845
|
-
action:
|
|
975
|
+
action: Action,
|
|
846
976
|
request: L1TxRequest,
|
|
847
977
|
checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
|
|
848
|
-
slotNumber:
|
|
978
|
+
slotNumber: SlotNumber,
|
|
849
979
|
timestamp: bigint,
|
|
850
980
|
) {
|
|
851
981
|
const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
|
|
852
|
-
|
|
982
|
+
if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
|
|
983
|
+
this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
|
|
984
|
+
return false;
|
|
985
|
+
}
|
|
853
986
|
|
|
854
|
-
this.
|
|
987
|
+
const cachedLastActionSlot = this.lastActions[action];
|
|
988
|
+
this.lastActions[action] = slotNumber;
|
|
989
|
+
|
|
990
|
+
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
991
|
+
|
|
992
|
+
let gasUsed: bigint;
|
|
855
993
|
try {
|
|
856
994
|
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], ErrorsAbi)); // TODO(palla/slash): Check the timestamp logic
|
|
857
995
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
@@ -875,6 +1013,7 @@ export class SequencerPublisher {
|
|
|
875
1013
|
const success = result && result.receipt && result.receipt.status === 'success' && checkSuccess(result.receipt);
|
|
876
1014
|
if (!success) {
|
|
877
1015
|
this.log.warn(`Action ${action} at ${slotNumber} failed`, { ...result, ...logData });
|
|
1016
|
+
this.lastActions[action] = cachedLastActionSlot;
|
|
878
1017
|
} else {
|
|
879
1018
|
this.log.info(`Action ${action} at ${slotNumber} succeeded`, { ...result, ...logData });
|
|
880
1019
|
}
|
|
@@ -904,54 +1043,58 @@ export class SequencerPublisher {
|
|
|
904
1043
|
private async prepareProposeTx(
|
|
905
1044
|
encodedData: L1ProcessArgs,
|
|
906
1045
|
timestamp: bigint,
|
|
907
|
-
options: { forcePendingBlockNumber?:
|
|
1046
|
+
options: { forcePendingBlockNumber?: BlockNumber },
|
|
908
1047
|
) {
|
|
909
1048
|
const kzg = Blob.getViemKzgInstance();
|
|
910
|
-
const blobInput =
|
|
1049
|
+
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
911
1050
|
this.log.debug('Validating blob input', { blobInput });
|
|
912
|
-
const blobEvaluationGas = await this.l1TxUtils
|
|
913
|
-
.estimateGas(
|
|
914
|
-
this.getSenderAddress().toString(),
|
|
915
|
-
{
|
|
916
|
-
to: this.rollupContract.address,
|
|
917
|
-
data: encodeFunctionData({
|
|
918
|
-
abi: RollupAbi,
|
|
919
|
-
functionName: 'validateBlobs',
|
|
920
|
-
args: [blobInput],
|
|
921
|
-
}),
|
|
922
|
-
},
|
|
923
|
-
{},
|
|
924
|
-
{
|
|
925
|
-
blobs: encodedData.blobs.map(b => b.data),
|
|
926
|
-
kzg,
|
|
927
|
-
},
|
|
928
|
-
)
|
|
929
|
-
.catch(err => {
|
|
930
|
-
const { message, metaMessages } = formatViemError(err);
|
|
931
|
-
this.log.error(`Failed to validate blobs`, message, { metaMessages });
|
|
932
|
-
throw new Error('Failed to validate blobs');
|
|
933
|
-
});
|
|
934
1051
|
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1052
|
+
// Get blob evaluation gas
|
|
1053
|
+
let blobEvaluationGas: bigint;
|
|
1054
|
+
if (this.config.fishermanMode) {
|
|
1055
|
+
// In fisherman mode, we can't estimate blob gas because estimateGas doesn't support state overrides
|
|
1056
|
+
// Use a fixed estimate.
|
|
1057
|
+
blobEvaluationGas = BigInt(encodedData.blobs.length) * 21_000n;
|
|
1058
|
+
this.log.debug(`Using fixed blob evaluation gas estimate in fisherman mode: ${blobEvaluationGas}`);
|
|
1059
|
+
} else {
|
|
1060
|
+
// Normal mode - use estimateGas with blob inputs
|
|
1061
|
+
blobEvaluationGas = await this.l1TxUtils
|
|
1062
|
+
.estimateGas(
|
|
1063
|
+
this.getSenderAddress().toString(),
|
|
1064
|
+
{
|
|
1065
|
+
to: this.rollupContract.address,
|
|
1066
|
+
data: encodeFunctionData({
|
|
1067
|
+
abi: RollupAbi,
|
|
1068
|
+
functionName: 'validateBlobs',
|
|
1069
|
+
args: [blobInput],
|
|
1070
|
+
}),
|
|
1071
|
+
},
|
|
1072
|
+
{},
|
|
1073
|
+
{
|
|
1074
|
+
blobs: encodedData.blobs.map(b => b.data),
|
|
1075
|
+
kzg,
|
|
1076
|
+
},
|
|
1077
|
+
)
|
|
1078
|
+
.catch(err => {
|
|
1079
|
+
const { message, metaMessages } = formatViemError(err);
|
|
1080
|
+
this.log.error(`Failed to validate blobs`, message, { metaMessages });
|
|
1081
|
+
throw new Error('Failed to validate blobs');
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
const signers = encodedData.attestationsAndSigners.getSigners().map(signer => signer.toString());
|
|
941
1085
|
|
|
942
1086
|
const args = [
|
|
943
1087
|
{
|
|
944
1088
|
header: encodedData.header.toViem(),
|
|
945
1089
|
archive: toHex(encodedData.archive),
|
|
946
|
-
stateReference: encodedData.stateReference.toViem(),
|
|
947
1090
|
oracleInput: {
|
|
948
1091
|
// We are currently not modifying these. See #9963
|
|
949
1092
|
feeAssetPriceModifier: 0n,
|
|
950
1093
|
},
|
|
951
|
-
txHashes,
|
|
952
1094
|
},
|
|
953
|
-
|
|
954
|
-
signers
|
|
1095
|
+
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
1096
|
+
signers,
|
|
1097
|
+
encodedData.attestationsAndSignersSignature.toViemSignature(),
|
|
955
1098
|
blobInput,
|
|
956
1099
|
] as const;
|
|
957
1100
|
|
|
@@ -971,18 +1114,17 @@ export class SequencerPublisher {
|
|
|
971
1114
|
{
|
|
972
1115
|
readonly header: ViemHeader;
|
|
973
1116
|
readonly archive: `0x${string}`;
|
|
974
|
-
readonly stateReference: ViemStateReference;
|
|
975
|
-
readonly txHashes: `0x${string}`[];
|
|
976
1117
|
readonly oracleInput: {
|
|
977
1118
|
readonly feeAssetPriceModifier: 0n;
|
|
978
1119
|
};
|
|
979
1120
|
},
|
|
980
1121
|
ViemCommitteeAttestations,
|
|
981
|
-
`0x${string}`[],
|
|
1122
|
+
`0x${string}`[], // Signers
|
|
1123
|
+
ViemSignature,
|
|
982
1124
|
`0x${string}`,
|
|
983
1125
|
],
|
|
984
1126
|
timestamp: bigint,
|
|
985
|
-
options: { forcePendingBlockNumber?:
|
|
1127
|
+
options: { forcePendingBlockNumber?: BlockNumber },
|
|
986
1128
|
) {
|
|
987
1129
|
const rollupData = encodeFunctionData({
|
|
988
1130
|
abi: RollupAbi,
|
|
@@ -990,19 +1132,42 @@ export class SequencerPublisher {
|
|
|
990
1132
|
args,
|
|
991
1133
|
});
|
|
992
1134
|
|
|
993
|
-
// override the pending
|
|
994
|
-
const
|
|
1135
|
+
// override the pending checkpoint number if requested
|
|
1136
|
+
const optsForcePendingCheckpointNumber =
|
|
995
1137
|
options.forcePendingBlockNumber !== undefined
|
|
996
|
-
?
|
|
1138
|
+
? CheckpointNumber.fromBlockNumber(options.forcePendingBlockNumber)
|
|
1139
|
+
: undefined;
|
|
1140
|
+
const forcePendingCheckpointNumberStateDiff = (
|
|
1141
|
+
optsForcePendingCheckpointNumber !== undefined
|
|
1142
|
+
? await this.rollupContract.makePendingCheckpointNumberOverride(optsForcePendingCheckpointNumber)
|
|
997
1143
|
: []
|
|
998
1144
|
).flatMap(override => override.stateDiff ?? []);
|
|
999
1145
|
|
|
1146
|
+
const stateOverrides: StateOverride = [
|
|
1147
|
+
{
|
|
1148
|
+
address: this.rollupContract.address,
|
|
1149
|
+
// @note we override checkBlob to false since blobs are not part simulate()
|
|
1150
|
+
stateDiff: [
|
|
1151
|
+
{ slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
|
|
1152
|
+
...forcePendingCheckpointNumberStateDiff,
|
|
1153
|
+
],
|
|
1154
|
+
},
|
|
1155
|
+
];
|
|
1156
|
+
// In fisherman mode, simulate as the proposer but with sufficient balance
|
|
1157
|
+
if (this.proposerAddressForSimulation) {
|
|
1158
|
+
stateOverrides.push({
|
|
1159
|
+
address: this.proposerAddressForSimulation.toString(),
|
|
1160
|
+
balance: 10n * WEI_CONST * WEI_CONST, // 10 ETH
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1000
1164
|
const simulationResult = await this.l1TxUtils
|
|
1001
1165
|
.simulate(
|
|
1002
1166
|
{
|
|
1003
1167
|
to: this.rollupContract.address,
|
|
1004
1168
|
data: rollupData,
|
|
1005
1169
|
gas: SequencerPublisher.PROPOSE_GAS_GUESS,
|
|
1170
|
+
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1006
1171
|
},
|
|
1007
1172
|
{
|
|
1008
1173
|
// @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
|
|
@@ -1010,16 +1175,7 @@ export class SequencerPublisher {
|
|
|
1010
1175
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1011
1176
|
gasLimit: SequencerPublisher.PROPOSE_GAS_GUESS * 2n,
|
|
1012
1177
|
},
|
|
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
|
-
],
|
|
1178
|
+
stateOverrides,
|
|
1023
1179
|
RollupAbi,
|
|
1024
1180
|
{
|
|
1025
1181
|
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
@@ -1027,7 +1183,17 @@ export class SequencerPublisher {
|
|
|
1027
1183
|
},
|
|
1028
1184
|
)
|
|
1029
1185
|
.catch(err => {
|
|
1030
|
-
|
|
1186
|
+
// In fisherman mode, we expect ValidatorSelection__MissingProposerSignature since fisherman doesn't have proposer signature
|
|
1187
|
+
const viemError = formatViemError(err);
|
|
1188
|
+
if (this.config.fishermanMode && viemError.message?.includes('ValidatorSelection__MissingProposerSignature')) {
|
|
1189
|
+
this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
|
|
1190
|
+
// Return a minimal simulation result with the fallback gas estimate
|
|
1191
|
+
return {
|
|
1192
|
+
gasUsed: SequencerPublisher.PROPOSE_GAS_GUESS,
|
|
1193
|
+
logs: [],
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1031
1197
|
throw err;
|
|
1032
1198
|
});
|
|
1033
1199
|
|
|
@@ -1035,11 +1201,12 @@ export class SequencerPublisher {
|
|
|
1035
1201
|
}
|
|
1036
1202
|
|
|
1037
1203
|
private async addProposeTx(
|
|
1038
|
-
|
|
1204
|
+
checkpoint: Checkpoint,
|
|
1039
1205
|
encodedData: L1ProcessArgs,
|
|
1040
|
-
opts: { txTimeoutAt?: Date; forcePendingBlockNumber?:
|
|
1206
|
+
opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: BlockNumber } = {},
|
|
1041
1207
|
timestamp: bigint,
|
|
1042
1208
|
): Promise<void> {
|
|
1209
|
+
const slot = checkpoint.header.slotNumber;
|
|
1043
1210
|
const timer = new Timer();
|
|
1044
1211
|
const kzg = Blob.getViemKzgInstance();
|
|
1045
1212
|
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
@@ -1054,11 +1221,13 @@ export class SequencerPublisher {
|
|
|
1054
1221
|
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
1222
|
);
|
|
1056
1223
|
|
|
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
|
-
|
|
1224
|
+
// Send the blobs to the blob client preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
|
|
1225
|
+
// tx fails but it does get mined. We make sure that the blobs are sent to the blob client regardless of the tx outcome.
|
|
1226
|
+
void Promise.resolve().then(() =>
|
|
1227
|
+
this.blobClient.sendBlobsToFilestore(encodedData.blobs).catch(_err => {
|
|
1228
|
+
this.log.error('Failed to send blobs to blob client');
|
|
1229
|
+
}),
|
|
1230
|
+
);
|
|
1062
1231
|
|
|
1063
1232
|
return this.addRequest({
|
|
1064
1233
|
action: 'propose',
|
|
@@ -1066,7 +1235,7 @@ export class SequencerPublisher {
|
|
|
1066
1235
|
to: this.rollupContract.address,
|
|
1067
1236
|
data: rollupData,
|
|
1068
1237
|
},
|
|
1069
|
-
lastValidL2Slot:
|
|
1238
|
+
lastValidL2Slot: checkpoint.header.slotNumber,
|
|
1070
1239
|
gasConfig: { ...opts, gasLimit },
|
|
1071
1240
|
blobConfig: {
|
|
1072
1241
|
blobs: encodedData.blobs.map(b => b.data),
|
|
@@ -1080,34 +1249,41 @@ export class SequencerPublisher {
|
|
|
1080
1249
|
const success =
|
|
1081
1250
|
receipt &&
|
|
1082
1251
|
receipt.status === 'success' &&
|
|
1083
|
-
tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, '
|
|
1252
|
+
tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointProposed');
|
|
1253
|
+
|
|
1084
1254
|
if (success) {
|
|
1085
1255
|
const endBlock = receipt.blockNumber;
|
|
1086
1256
|
const inclusionBlocks = Number(endBlock - startBlock);
|
|
1087
|
-
const
|
|
1257
|
+
const { calldataGas, calldataSize, sender } = stats!;
|
|
1258
|
+
const publishStats: L1PublishCheckpointStats = {
|
|
1088
1259
|
gasPrice: receipt.effectiveGasPrice,
|
|
1089
1260
|
gasUsed: receipt.gasUsed,
|
|
1090
1261
|
blobGasUsed: receipt.blobGasUsed ?? 0n,
|
|
1091
1262
|
blobDataGas: receipt.blobGasPrice ?? 0n,
|
|
1092
1263
|
transactionHash: receipt.transactionHash,
|
|
1093
|
-
|
|
1094
|
-
|
|
1264
|
+
calldataGas,
|
|
1265
|
+
calldataSize,
|
|
1266
|
+
sender,
|
|
1267
|
+
...checkpoint.getStats(),
|
|
1095
1268
|
eventName: 'rollup-published-to-l1',
|
|
1096
1269
|
blobCount: encodedData.blobs.length,
|
|
1097
1270
|
inclusionBlocks,
|
|
1098
1271
|
};
|
|
1099
|
-
this.log.info(`Published
|
|
1272
|
+
this.log.info(`Published checkpoint ${checkpoint.number} at slot ${slot} to rollup contract`, {
|
|
1273
|
+
...stats,
|
|
1274
|
+
...checkpoint.getStats(),
|
|
1275
|
+
...pick(receipt, 'transactionHash', 'blockHash'),
|
|
1276
|
+
});
|
|
1100
1277
|
this.metrics.recordProcessBlockTx(timer.ms(), publishStats);
|
|
1101
1278
|
|
|
1102
1279
|
return true;
|
|
1103
1280
|
} else {
|
|
1104
1281
|
this.metrics.recordFailedTx('process');
|
|
1105
|
-
this.log.error(
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
});
|
|
1282
|
+
this.log.error(
|
|
1283
|
+
`Publishing checkpoint at slot ${slot} failed with ${errorMsg ?? 'no error message'}`,
|
|
1284
|
+
undefined,
|
|
1285
|
+
{ ...checkpoint.getStats(), ...receipt },
|
|
1286
|
+
);
|
|
1111
1287
|
return false;
|
|
1112
1288
|
}
|
|
1113
1289
|
},
|