@aztec/sequencer-client 3.0.0-canary.a9708bd → 3.0.0-devnet.2-patch.1
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 +7 -6
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +23 -14
- package/dest/config.d.ts +5 -3
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +21 -1
- package/dest/global_variable_builder/global_builder.d.ts +5 -7
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +12 -8
- package/dest/global_variable_builder/index.d.ts +1 -1
- package/dest/index.d.ts +1 -1
- package/dest/publisher/config.d.ts +9 -10
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +19 -17
- 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 +9 -3
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +8 -1
- package/dest/publisher/sequencer-publisher-metrics.d.ts +2 -2
- 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 +53 -50
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +187 -132
- package/dest/sequencer/block_builder.d.ts +6 -8
- package/dest/sequencer/block_builder.d.ts.map +1 -1
- package/dest/sequencer/block_builder.js +18 -9
- package/dest/sequencer/config.d.ts +2 -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/index.d.ts +1 -1
- package/dest/sequencer/metrics.d.ts +17 -20
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +59 -87
- package/dest/sequencer/sequencer.d.ts +54 -32
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +458 -184
- package/dest/sequencer/timetable.d.ts +4 -8
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +3 -10
- package/dest/sequencer/utils.d.ts +11 -25
- package/dest/sequencer/utils.d.ts.map +1 -1
- package/dest/sequencer/utils.js +9 -24
- package/dest/test/index.d.ts +2 -2
- package/dest/test/index.d.ts.map +1 -1
- 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 +24 -18
- package/src/config.ts +23 -6
- package/src/global_variable_builder/global_builder.ts +19 -17
- package/src/publisher/config.ts +29 -27
- package/src/publisher/index.ts +1 -1
- package/src/publisher/sequencer-publisher-factory.ts +16 -3
- package/src/publisher/sequencer-publisher-metrics.ts +1 -1
- package/src/publisher/sequencer-publisher.ts +257 -183
- package/src/sequencer/block_builder.ts +23 -28
- package/src/sequencer/config.ts +1 -1
- package/src/sequencer/errors.ts +21 -0
- package/src/sequencer/metrics.ts +71 -98
- package/src/sequencer/sequencer.ts +546 -237
- package/src/sequencer/timetable.ts +10 -14
- package/src/sequencer/utils.ts +10 -24
- package/src/test/index.ts +1 -1
- package/src/tx_validator/tx_validator_factory.ts +13 -7
|
@@ -1,47 +1,46 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Blob } from '@aztec/blob-lib';
|
|
1
|
+
import { L2Block } from '@aztec/aztec.js/block';
|
|
2
|
+
import { Blob, getBlobsPerL1Block, getPrefixedEthBlobCommitments } from '@aztec/blob-lib';
|
|
3
3
|
import { type BlobSinkClientInterface, createBlobSinkClient } from '@aztec/blob-sink/client';
|
|
4
4
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
5
|
+
import type { L1ContractsConfig } from '@aztec/ethereum/config';
|
|
5
6
|
import {
|
|
6
7
|
type EmpireSlashingProposerContract,
|
|
7
|
-
FormattedViemError,
|
|
8
|
-
type GasPrice,
|
|
9
8
|
type GovernanceProposerContract,
|
|
10
9
|
type IEmpireBase,
|
|
11
|
-
type L1BlobInputs,
|
|
12
|
-
type L1ContractsConfig,
|
|
13
|
-
type L1GasConfig,
|
|
14
|
-
type L1TxRequest,
|
|
15
10
|
MULTI_CALL_3_ADDRESS,
|
|
16
11
|
Multicall3,
|
|
17
12
|
RollupContract,
|
|
18
13
|
type TallySlashingProposerContract,
|
|
19
|
-
type TransactionStats,
|
|
20
14
|
type ViemCommitteeAttestations,
|
|
21
15
|
type ViemHeader,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
} from '@aztec/ethereum/contracts';
|
|
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 type { Fr } from '@aztec/foundation/curves/bn254';
|
|
29
30
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
30
|
-
import type
|
|
31
|
-
import { createLogger } from '@aztec/foundation/log';
|
|
31
|
+
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
32
|
+
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
32
33
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
33
34
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
34
35
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
35
36
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
36
|
-
import { CommitteeAttestation, type ValidateBlockResult } from '@aztec/stdlib/block';
|
|
37
|
+
import { CommitteeAttestation, CommitteeAttestationsAndSigners, type ValidateBlockResult } from '@aztec/stdlib/block';
|
|
37
38
|
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
38
|
-
import {
|
|
39
|
+
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
39
40
|
import type { L1PublishBlockStats } from '@aztec/stdlib/stats';
|
|
40
|
-
import { type ProposedBlockHeader, StateReference, TxHash } from '@aztec/stdlib/tx';
|
|
41
41
|
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
42
42
|
|
|
43
|
-
import
|
|
44
|
-
import { type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
|
|
43
|
+
import { type StateOverride, type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
|
|
45
44
|
|
|
46
45
|
import type { PublisherConfig, TxSenderConfig } from './config.js';
|
|
47
46
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
@@ -49,24 +48,17 @@ import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
|
49
48
|
/** Arguments to the process method of the rollup contract */
|
|
50
49
|
type L1ProcessArgs = {
|
|
51
50
|
/** The L2 block header. */
|
|
52
|
-
header:
|
|
51
|
+
header: CheckpointHeader;
|
|
53
52
|
/** A root of the archive tree after the L2 block is applied. */
|
|
54
53
|
archive: Buffer;
|
|
55
|
-
/** State reference after the L2 block is applied. */
|
|
56
|
-
stateReference: StateReference;
|
|
57
54
|
/** L2 block blobs containing all tx effects. */
|
|
58
55
|
blobs: Blob[];
|
|
59
|
-
/** L2 block tx hashes */
|
|
60
|
-
txHashes: TxHash[];
|
|
61
56
|
/** Attestations */
|
|
62
|
-
|
|
57
|
+
attestationsAndSigners: CommitteeAttestationsAndSigners;
|
|
58
|
+
/** Attestations and signers signature */
|
|
59
|
+
attestationsAndSignersSignature: Signature;
|
|
63
60
|
};
|
|
64
61
|
|
|
65
|
-
export enum SignalType {
|
|
66
|
-
GOVERNANCE,
|
|
67
|
-
SLASHING,
|
|
68
|
-
}
|
|
69
|
-
|
|
70
62
|
export const Actions = [
|
|
71
63
|
'invalidate-by-invalid-attestation',
|
|
72
64
|
'invalidate-by-insufficient-attestations',
|
|
@@ -78,8 +70,11 @@ export const Actions = [
|
|
|
78
70
|
'vote-offenses',
|
|
79
71
|
'execute-slash',
|
|
80
72
|
] as const;
|
|
73
|
+
|
|
81
74
|
export type Action = (typeof Actions)[number];
|
|
82
75
|
|
|
76
|
+
type GovernanceSignalAction = Extract<Action, 'governance-signal' | 'empire-slashing-signal'>;
|
|
77
|
+
|
|
83
78
|
// Sorting for actions such that invalidations go before proposals, and proposals go before votes
|
|
84
79
|
export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
|
|
85
80
|
|
|
@@ -87,19 +82,19 @@ export type InvalidateBlockRequest = {
|
|
|
87
82
|
request: L1TxRequest;
|
|
88
83
|
reason: 'invalid-attestation' | 'insufficient-attestations';
|
|
89
84
|
gasUsed: bigint;
|
|
90
|
-
blockNumber:
|
|
91
|
-
forcePendingBlockNumber:
|
|
85
|
+
blockNumber: BlockNumber;
|
|
86
|
+
forcePendingBlockNumber: BlockNumber;
|
|
92
87
|
};
|
|
93
88
|
|
|
94
89
|
interface RequestWithExpiry {
|
|
95
90
|
action: Action;
|
|
96
91
|
request: L1TxRequest;
|
|
97
|
-
lastValidL2Slot:
|
|
98
|
-
gasConfig?: Pick<
|
|
92
|
+
lastValidL2Slot: SlotNumber;
|
|
93
|
+
gasConfig?: Pick<L1TxConfig, 'txTimeoutAt' | 'gasLimit'>;
|
|
99
94
|
blobConfig?: L1BlobInputs;
|
|
100
95
|
checkSuccess: (
|
|
101
96
|
request: L1TxRequest,
|
|
102
|
-
result?: { receipt: TransactionReceipt;
|
|
97
|
+
result?: { receipt: TransactionReceipt; stats?: TransactionStats; errorMsg?: string },
|
|
103
98
|
) => boolean;
|
|
104
99
|
}
|
|
105
100
|
|
|
@@ -111,15 +106,15 @@ export class SequencerPublisher {
|
|
|
111
106
|
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
112
107
|
protected slashingLog = createLogger('sequencer:publisher:slashing');
|
|
113
108
|
|
|
114
|
-
|
|
115
|
-
[SignalType.GOVERNANCE]: 0n,
|
|
116
|
-
[SignalType.SLASHING]: 0n,
|
|
117
|
-
};
|
|
109
|
+
protected lastActions: Partial<Record<Action, SlotNumber>> = {};
|
|
118
110
|
|
|
119
|
-
protected log
|
|
111
|
+
protected log: Logger;
|
|
120
112
|
protected ethereumSlotDuration: bigint;
|
|
121
113
|
|
|
122
114
|
private blobSinkClient: BlobSinkClientInterface;
|
|
115
|
+
|
|
116
|
+
/** Address to use for simulations in fisherman mode (actual proposer's address) */
|
|
117
|
+
private proposerAddressForSimulation?: EthAddress;
|
|
123
118
|
// @note - with blobs, the below estimate seems too large.
|
|
124
119
|
// Total used for full block from int_l1_pub e2e test: 1m (of which 86k is 1x blob)
|
|
125
120
|
// Total used for emptier block from above test: 429k (of which 84k is 1x blob)
|
|
@@ -152,10 +147,14 @@ export class SequencerPublisher {
|
|
|
152
147
|
epochCache: EpochCache;
|
|
153
148
|
dateProvider: DateProvider;
|
|
154
149
|
metrics: SequencerPublisherMetrics;
|
|
150
|
+
lastActions: Partial<Record<Action, SlotNumber>>;
|
|
151
|
+
log?: Logger;
|
|
155
152
|
},
|
|
156
153
|
) {
|
|
154
|
+
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
157
155
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
158
156
|
this.epochCache = deps.epochCache;
|
|
157
|
+
this.lastActions = deps.lastActions;
|
|
159
158
|
|
|
160
159
|
this.blobSinkClient =
|
|
161
160
|
deps.blobSinkClient ?? createBlobSinkClient(config, { logger: createLogger('sequencer:blob-sink:client') });
|
|
@@ -185,14 +184,33 @@ export class SequencerPublisher {
|
|
|
185
184
|
return this.l1TxUtils.getSenderAddress();
|
|
186
185
|
}
|
|
187
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Sets the proposer address to use for simulations in fisherman mode.
|
|
189
|
+
* @param proposerAddress - The actual proposer's address to use for balance lookups in simulations
|
|
190
|
+
*/
|
|
191
|
+
public setProposerAddressForSimulation(proposerAddress: EthAddress | undefined) {
|
|
192
|
+
this.proposerAddressForSimulation = proposerAddress;
|
|
193
|
+
}
|
|
194
|
+
|
|
188
195
|
public addRequest(request: RequestWithExpiry) {
|
|
189
196
|
this.requests.push(request);
|
|
190
197
|
}
|
|
191
198
|
|
|
192
|
-
public getCurrentL2Slot():
|
|
199
|
+
public getCurrentL2Slot(): SlotNumber {
|
|
193
200
|
return this.epochCache.getEpochAndSlotNow().slot;
|
|
194
201
|
}
|
|
195
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Clears all pending requests without sending them.
|
|
205
|
+
*/
|
|
206
|
+
public clearPendingRequests(): void {
|
|
207
|
+
const count = this.requests.length;
|
|
208
|
+
this.requests = [];
|
|
209
|
+
if (count > 0) {
|
|
210
|
+
this.log.debug(`Cleared ${count} pending request(s)`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
196
214
|
/**
|
|
197
215
|
* Sends all requests that are still valid.
|
|
198
216
|
* @returns one of:
|
|
@@ -249,18 +267,21 @@ export class SequencerPublisher {
|
|
|
249
267
|
const gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
|
|
250
268
|
const txTimeoutAts = gasConfigs.map(g => g?.txTimeoutAt).filter((g): g is Date => g !== undefined);
|
|
251
269
|
const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map(g => g.getTime()))) : undefined; // earliest
|
|
252
|
-
const
|
|
270
|
+
const txConfig: RequestWithExpiry['gasConfig'] = { gasLimit, txTimeoutAt };
|
|
253
271
|
|
|
254
272
|
// Sort the requests so that proposals always go first
|
|
255
273
|
// This ensures the committee gets precomputed correctly
|
|
256
274
|
validRequests.sort((a, b) => compareActions(a.action, b.action));
|
|
257
275
|
|
|
258
276
|
try {
|
|
259
|
-
this.log.debug('Forwarding transactions', {
|
|
277
|
+
this.log.debug('Forwarding transactions', {
|
|
278
|
+
validRequests: validRequests.map(request => request.action),
|
|
279
|
+
txConfig,
|
|
280
|
+
});
|
|
260
281
|
const result = await Multicall3.forward(
|
|
261
282
|
validRequests.map(request => request.request),
|
|
262
283
|
this.l1TxUtils,
|
|
263
|
-
|
|
284
|
+
txConfig,
|
|
264
285
|
blobConfig,
|
|
265
286
|
this.rollupContract.address,
|
|
266
287
|
this.log,
|
|
@@ -285,7 +306,7 @@ export class SequencerPublisher {
|
|
|
285
306
|
|
|
286
307
|
private callbackBundledTransactions(
|
|
287
308
|
requests: RequestWithExpiry[],
|
|
288
|
-
result?: { receipt: TransactionReceipt
|
|
309
|
+
result?: { receipt: TransactionReceipt } | FormattedViemError,
|
|
289
310
|
) {
|
|
290
311
|
const actionsListStr = requests.map(r => r.action).join(', ');
|
|
291
312
|
if (result instanceof FormattedViemError) {
|
|
@@ -314,13 +335,18 @@ export class SequencerPublisher {
|
|
|
314
335
|
public canProposeAtNextEthBlock(
|
|
315
336
|
tipArchive: Fr,
|
|
316
337
|
msgSender: EthAddress,
|
|
317
|
-
opts: { forcePendingBlockNumber?:
|
|
338
|
+
opts: { forcePendingBlockNumber?: BlockNumber } = {},
|
|
318
339
|
) {
|
|
319
340
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
320
341
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
321
342
|
|
|
322
343
|
return this.rollupContract
|
|
323
|
-
.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), this.ethereumSlotDuration,
|
|
344
|
+
.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
|
|
345
|
+
forcePendingCheckpointNumber:
|
|
346
|
+
opts.forcePendingBlockNumber !== undefined
|
|
347
|
+
? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber)
|
|
348
|
+
: undefined,
|
|
349
|
+
})
|
|
324
350
|
.catch(err => {
|
|
325
351
|
if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
|
|
326
352
|
this.log.warn(`Failed canProposeAtTime check with ${ignoredErrors.find(e => err.message.includes(e))}`, {
|
|
@@ -339,25 +365,42 @@ export class SequencerPublisher {
|
|
|
339
365
|
* @param header - The block header to validate
|
|
340
366
|
*/
|
|
341
367
|
public async validateBlockHeader(
|
|
342
|
-
header:
|
|
343
|
-
opts?: { forcePendingBlockNumber:
|
|
368
|
+
header: CheckpointHeader,
|
|
369
|
+
opts?: { forcePendingBlockNumber: BlockNumber | undefined },
|
|
344
370
|
) {
|
|
345
371
|
const flags = { ignoreDA: true, ignoreSignatures: true };
|
|
346
372
|
|
|
347
373
|
const args = [
|
|
348
374
|
header.toViem(),
|
|
349
|
-
|
|
375
|
+
CommitteeAttestationsAndSigners.empty().getPackedAttestations(),
|
|
350
376
|
[], // no signers
|
|
377
|
+
Signature.empty().toViemSignature(),
|
|
351
378
|
`0x${'0'.repeat(64)}`, // 32 empty bytes
|
|
352
379
|
header.contentCommitment.blobsHash.toString(),
|
|
353
380
|
flags,
|
|
354
381
|
] as const;
|
|
355
382
|
|
|
356
383
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
384
|
+
const optsForcePendingCheckpointNumber =
|
|
385
|
+
opts?.forcePendingBlockNumber !== undefined
|
|
386
|
+
? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber)
|
|
387
|
+
: undefined;
|
|
388
|
+
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
389
|
+
optsForcePendingCheckpointNumber,
|
|
390
|
+
);
|
|
391
|
+
let balance = 0n;
|
|
392
|
+
if (this.config.fishermanMode) {
|
|
393
|
+
// In fisherman mode, we can't know where the proposer is publishing from
|
|
394
|
+
// so we just add sufficient balance to the multicall3 address
|
|
395
|
+
balance = 10n * WEI_CONST * WEI_CONST; // 10 ETH
|
|
396
|
+
} else {
|
|
397
|
+
balance = await this.l1TxUtils.getSenderBalance();
|
|
398
|
+
}
|
|
399
|
+
stateOverrides.push({
|
|
400
|
+
address: MULTI_CALL_3_ADDRESS,
|
|
401
|
+
balance,
|
|
402
|
+
});
|
|
357
403
|
|
|
358
|
-
// use sender balance to simulate
|
|
359
|
-
const balance = await this.l1TxUtils.getSenderBalance();
|
|
360
|
-
this.log.debug(`Simulating validateHeader with balance: ${balance}`);
|
|
361
404
|
await this.l1TxUtils.simulate(
|
|
362
405
|
{
|
|
363
406
|
to: this.rollupContract.address,
|
|
@@ -365,10 +408,7 @@ export class SequencerPublisher {
|
|
|
365
408
|
from: MULTI_CALL_3_ADDRESS,
|
|
366
409
|
},
|
|
367
410
|
{ time: ts + 1n },
|
|
368
|
-
|
|
369
|
-
{ address: MULTI_CALL_3_ADDRESS, balance },
|
|
370
|
-
...(await this.rollupContract.makePendingBlockNumberOverride(opts?.forcePendingBlockNumber)),
|
|
371
|
-
],
|
|
411
|
+
stateOverrides,
|
|
372
412
|
);
|
|
373
413
|
this.log.debug(`Simulated validateHeader`);
|
|
374
414
|
}
|
|
@@ -385,11 +425,11 @@ export class SequencerPublisher {
|
|
|
385
425
|
}
|
|
386
426
|
|
|
387
427
|
const { reason, block } = validationResult;
|
|
388
|
-
const blockNumber = block.
|
|
389
|
-
const logData = { ...block
|
|
428
|
+
const blockNumber = block.blockNumber;
|
|
429
|
+
const logData = { ...block, reason };
|
|
390
430
|
|
|
391
|
-
const currentBlockNumber = await this.rollupContract.
|
|
392
|
-
if (currentBlockNumber < validationResult.block.
|
|
431
|
+
const currentBlockNumber = await this.rollupContract.getCheckpointNumber();
|
|
432
|
+
if (currentBlockNumber < validationResult.block.blockNumber) {
|
|
393
433
|
this.log.verbose(
|
|
394
434
|
`Skipping block ${blockNumber} invalidation since it has already been removed from the pending chain`,
|
|
395
435
|
{ currentBlockNumber, ...logData },
|
|
@@ -398,13 +438,13 @@ export class SequencerPublisher {
|
|
|
398
438
|
}
|
|
399
439
|
|
|
400
440
|
const request = this.buildInvalidateBlockRequest(validationResult);
|
|
401
|
-
this.log.debug(`Simulating invalidate block ${blockNumber}`, logData);
|
|
441
|
+
this.log.debug(`Simulating invalidate block ${blockNumber}`, { ...logData, request });
|
|
402
442
|
|
|
403
443
|
try {
|
|
404
444
|
const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, ErrorsAbi);
|
|
405
445
|
this.log.verbose(`Simulation for invalidate block ${blockNumber} succeeded`, { ...logData, request, gasUsed });
|
|
406
446
|
|
|
407
|
-
return { request, gasUsed, blockNumber, forcePendingBlockNumber: blockNumber - 1, reason };
|
|
447
|
+
return { request, gasUsed, blockNumber, forcePendingBlockNumber: BlockNumber(blockNumber - 1), reason };
|
|
408
448
|
} catch (err) {
|
|
409
449
|
const viemError = formatViemError(err);
|
|
410
450
|
|
|
@@ -415,7 +455,7 @@ export class SequencerPublisher {
|
|
|
415
455
|
`Simulation for invalidate block ${blockNumber} failed due to block not being in pending chain`,
|
|
416
456
|
{ ...logData, request, error: viemError.message },
|
|
417
457
|
);
|
|
418
|
-
const latestPendingBlockNumber = await this.rollupContract.
|
|
458
|
+
const latestPendingBlockNumber = await this.rollupContract.getCheckpointNumber();
|
|
419
459
|
if (latestPendingBlockNumber < blockNumber) {
|
|
420
460
|
this.log.verbose(`Block number ${blockNumber} has already been invalidated`, { ...logData });
|
|
421
461
|
return undefined;
|
|
@@ -443,20 +483,24 @@ export class SequencerPublisher {
|
|
|
443
483
|
}
|
|
444
484
|
|
|
445
485
|
const { block, committee, reason } = validationResult;
|
|
446
|
-
const logData = { ...block
|
|
447
|
-
this.log.debug(`Simulating invalidate block ${block.
|
|
486
|
+
const logData = { ...block, reason };
|
|
487
|
+
this.log.debug(`Simulating invalidate block ${block.blockNumber}`, logData);
|
|
488
|
+
|
|
489
|
+
const attestationsAndSigners = new CommitteeAttestationsAndSigners(
|
|
490
|
+
validationResult.attestations,
|
|
491
|
+
).getPackedAttestations();
|
|
448
492
|
|
|
449
493
|
if (reason === 'invalid-attestation') {
|
|
450
494
|
return this.rollupContract.buildInvalidateBadAttestationRequest(
|
|
451
|
-
|
|
452
|
-
|
|
495
|
+
CheckpointNumber.fromBlockNumber(block.blockNumber),
|
|
496
|
+
attestationsAndSigners,
|
|
453
497
|
committee,
|
|
454
498
|
validationResult.invalidIndex,
|
|
455
499
|
);
|
|
456
500
|
} else if (reason === 'insufficient-attestations') {
|
|
457
501
|
return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
|
|
458
|
-
|
|
459
|
-
|
|
502
|
+
CheckpointNumber.fromBlockNumber(block.blockNumber),
|
|
503
|
+
attestationsAndSigners,
|
|
460
504
|
committee,
|
|
461
505
|
);
|
|
462
506
|
} else {
|
|
@@ -476,48 +520,41 @@ export class SequencerPublisher {
|
|
|
476
520
|
*/
|
|
477
521
|
public async validateBlockForSubmission(
|
|
478
522
|
block: L2Block,
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
},
|
|
483
|
-
options: { forcePendingBlockNumber?: number },
|
|
523
|
+
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
524
|
+
attestationsAndSignersSignature: Signature,
|
|
525
|
+
options: { forcePendingBlockNumber?: BlockNumber },
|
|
484
526
|
): Promise<bigint> {
|
|
485
527
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
486
528
|
|
|
487
529
|
// If we have no attestations, we still need to provide the empty attestations
|
|
488
530
|
// so that the committee is recalculated correctly
|
|
489
|
-
const ignoreSignatures =
|
|
531
|
+
const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
490
532
|
if (ignoreSignatures) {
|
|
491
|
-
const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber
|
|
533
|
+
const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
|
|
492
534
|
if (!committee) {
|
|
493
|
-
this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber
|
|
494
|
-
throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber
|
|
535
|
+
this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
536
|
+
throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
495
537
|
}
|
|
496
|
-
|
|
538
|
+
attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
497
539
|
CommitteeAttestation.fromAddress(committeeMember),
|
|
498
540
|
);
|
|
499
541
|
}
|
|
500
542
|
|
|
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());
|
|
543
|
+
const blobFields = block.getCheckpointBlobFields();
|
|
544
|
+
const blobs = getBlobsPerL1Block(blobFields);
|
|
545
|
+
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
508
546
|
|
|
509
547
|
const args = [
|
|
510
548
|
{
|
|
511
|
-
header: block.
|
|
549
|
+
header: block.getCheckpointHeader().toViem(),
|
|
512
550
|
archive: toHex(block.archive.root.toBuffer()),
|
|
513
|
-
stateReference: block.header.state.toViem(),
|
|
514
|
-
txHashes: block.body.txEffects.map(txEffect => txEffect.txHash.toString()),
|
|
515
551
|
oracleInput: {
|
|
516
552
|
feeAssetPriceModifier: 0n,
|
|
517
553
|
},
|
|
518
554
|
},
|
|
519
|
-
|
|
520
|
-
|
|
555
|
+
attestationsAndSigners.getPackedAttestations(),
|
|
556
|
+
attestationsAndSigners.getSigners().map(signer => signer.toString()),
|
|
557
|
+
attestationsAndSignersSignature.toViemSignature(),
|
|
521
558
|
blobInput,
|
|
522
559
|
] as const;
|
|
523
560
|
|
|
@@ -526,15 +563,16 @@ export class SequencerPublisher {
|
|
|
526
563
|
}
|
|
527
564
|
|
|
528
565
|
private async enqueueCastSignalHelper(
|
|
529
|
-
slotNumber:
|
|
566
|
+
slotNumber: SlotNumber,
|
|
530
567
|
timestamp: bigint,
|
|
531
|
-
signalType:
|
|
568
|
+
signalType: GovernanceSignalAction,
|
|
532
569
|
payload: EthAddress,
|
|
533
570
|
base: IEmpireBase,
|
|
534
571
|
signerAddress: EthAddress,
|
|
535
572
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
536
573
|
): Promise<boolean> {
|
|
537
|
-
if (this.
|
|
574
|
+
if (this.lastActions[signalType] && this.lastActions[signalType] === slotNumber) {
|
|
575
|
+
this.log.debug(`Skipping duplicate vote cast signal ${signalType} for slot ${slotNumber}`);
|
|
538
576
|
return false;
|
|
539
577
|
}
|
|
540
578
|
if (payload.equals(EthAddress.ZERO)) {
|
|
@@ -551,10 +589,9 @@ export class SequencerPublisher {
|
|
|
551
589
|
return false;
|
|
552
590
|
}
|
|
553
591
|
|
|
554
|
-
const cachedLastVote = this.
|
|
555
|
-
this.
|
|
556
|
-
|
|
557
|
-
const action = signalType === SignalType.GOVERNANCE ? 'governance-signal' : 'empire-slashing-signal';
|
|
592
|
+
const cachedLastVote = this.lastActions[signalType];
|
|
593
|
+
this.lastActions[signalType] = slotNumber;
|
|
594
|
+
const action = signalType;
|
|
558
595
|
|
|
559
596
|
const request = await base.createSignalRequestWithSignature(
|
|
560
597
|
payload.toString(),
|
|
@@ -597,7 +634,7 @@ export class SequencerPublisher {
|
|
|
597
634
|
`Signaling in [${action}] for ${payload} at slot ${slotNumber} in round ${round} failed`,
|
|
598
635
|
logData,
|
|
599
636
|
);
|
|
600
|
-
this.
|
|
637
|
+
this.lastActions[signalType] = cachedLastVote;
|
|
601
638
|
return false;
|
|
602
639
|
} else {
|
|
603
640
|
this.log.info(
|
|
@@ -619,7 +656,7 @@ export class SequencerPublisher {
|
|
|
619
656
|
*/
|
|
620
657
|
public enqueueGovernanceCastSignal(
|
|
621
658
|
governancePayload: EthAddress,
|
|
622
|
-
slotNumber:
|
|
659
|
+
slotNumber: SlotNumber,
|
|
623
660
|
timestamp: bigint,
|
|
624
661
|
signerAddress: EthAddress,
|
|
625
662
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
@@ -627,7 +664,7 @@ export class SequencerPublisher {
|
|
|
627
664
|
return this.enqueueCastSignalHelper(
|
|
628
665
|
slotNumber,
|
|
629
666
|
timestamp,
|
|
630
|
-
|
|
667
|
+
'governance-signal',
|
|
631
668
|
governancePayload,
|
|
632
669
|
this.govProposerContract,
|
|
633
670
|
signerAddress,
|
|
@@ -638,7 +675,7 @@ export class SequencerPublisher {
|
|
|
638
675
|
/** Enqueues all slashing actions as returned by the slasher client. */
|
|
639
676
|
public async enqueueSlashingActions(
|
|
640
677
|
actions: ProposerSlashAction[],
|
|
641
|
-
slotNumber:
|
|
678
|
+
slotNumber: SlotNumber,
|
|
642
679
|
timestamp: bigint,
|
|
643
680
|
signerAddress: EthAddress,
|
|
644
681
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
@@ -661,7 +698,7 @@ export class SequencerPublisher {
|
|
|
661
698
|
await this.enqueueCastSignalHelper(
|
|
662
699
|
slotNumber,
|
|
663
700
|
timestamp,
|
|
664
|
-
|
|
701
|
+
'empire-slashing-signal',
|
|
665
702
|
action.payload,
|
|
666
703
|
this.slashingProposerContract,
|
|
667
704
|
signerAddress,
|
|
@@ -766,24 +803,22 @@ export class SequencerPublisher {
|
|
|
766
803
|
*/
|
|
767
804
|
public async enqueueProposeL2Block(
|
|
768
805
|
block: L2Block,
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
opts: { txTimeoutAt?: Date; forcePendingBlockNumber?:
|
|
806
|
+
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
807
|
+
attestationsAndSignersSignature: Signature,
|
|
808
|
+
opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: BlockNumber } = {},
|
|
772
809
|
): Promise<boolean> {
|
|
773
|
-
const
|
|
810
|
+
const checkpointHeader = block.getCheckpointHeader();
|
|
774
811
|
|
|
775
|
-
const
|
|
776
|
-
const
|
|
812
|
+
const blobFields = block.getCheckpointBlobFields();
|
|
813
|
+
const blobs = getBlobsPerL1Block(blobFields);
|
|
777
814
|
|
|
778
|
-
const blobs = await Blob.getBlobsPerBlock(block.body.toBlobFields());
|
|
779
815
|
const proposeTxArgs = {
|
|
780
|
-
header:
|
|
816
|
+
header: checkpointHeader,
|
|
781
817
|
archive: block.archive.root.toBuffer(),
|
|
782
|
-
stateReference: block.header.state,
|
|
783
818
|
body: block.body.toBuffer(),
|
|
784
819
|
blobs,
|
|
785
|
-
|
|
786
|
-
|
|
820
|
+
attestationsAndSigners,
|
|
821
|
+
attestationsAndSignersSignature,
|
|
787
822
|
};
|
|
788
823
|
|
|
789
824
|
let ts: bigint;
|
|
@@ -793,13 +828,12 @@ export class SequencerPublisher {
|
|
|
793
828
|
// This means that we can avoid the simulation issues in later checks.
|
|
794
829
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
795
830
|
// make time consistency checks break.
|
|
796
|
-
const attestationData = { digest: digest.toBuffer(), attestations: attestations ?? [] };
|
|
797
831
|
// 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.validateBlockForSubmission(block,
|
|
832
|
+
ts = await this.validateBlockForSubmission(block, attestationsAndSigners, attestationsAndSignersSignature, opts);
|
|
799
833
|
} catch (err: any) {
|
|
800
834
|
this.log.error(`Block validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
801
835
|
...block.getStats(),
|
|
802
|
-
slotNumber: block.header.globalVariables.slotNumber
|
|
836
|
+
slotNumber: block.header.globalVariables.slotNumber,
|
|
803
837
|
forcePendingBlockNumber: opts.forcePendingBlockNumber,
|
|
804
838
|
});
|
|
805
839
|
throw err;
|
|
@@ -818,19 +852,20 @@ export class SequencerPublisher {
|
|
|
818
852
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
819
853
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(request.gasUsed) * 64) / 63)));
|
|
820
854
|
|
|
821
|
-
const
|
|
855
|
+
const { gasUsed, blockNumber } = request;
|
|
856
|
+
const logData = { gasUsed, blockNumber, gasLimit, opts };
|
|
822
857
|
this.log.verbose(`Enqueuing invalidate block request`, logData);
|
|
823
858
|
this.addRequest({
|
|
824
859
|
action: `invalidate-by-${request.reason}`,
|
|
825
860
|
request: request.request,
|
|
826
861
|
gasConfig: { gasLimit, txTimeoutAt: opts.txTimeoutAt },
|
|
827
|
-
lastValidL2Slot: this.getCurrentL2Slot() +
|
|
862
|
+
lastValidL2Slot: SlotNumber(this.getCurrentL2Slot() + 2),
|
|
828
863
|
checkSuccess: (_req, result) => {
|
|
829
864
|
const success =
|
|
830
865
|
result &&
|
|
831
866
|
result.receipt &&
|
|
832
867
|
result.receipt.status === 'success' &&
|
|
833
|
-
tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, '
|
|
868
|
+
tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointInvalidated');
|
|
834
869
|
if (!success) {
|
|
835
870
|
this.log.warn(`Invalidate block ${request.blockNumber} failed`, { ...result, ...logData });
|
|
836
871
|
} else {
|
|
@@ -842,16 +877,24 @@ export class SequencerPublisher {
|
|
|
842
877
|
}
|
|
843
878
|
|
|
844
879
|
private async simulateAndEnqueueRequest(
|
|
845
|
-
action:
|
|
880
|
+
action: Action,
|
|
846
881
|
request: L1TxRequest,
|
|
847
882
|
checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
|
|
848
|
-
slotNumber:
|
|
883
|
+
slotNumber: SlotNumber,
|
|
849
884
|
timestamp: bigint,
|
|
850
885
|
) {
|
|
851
886
|
const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
|
|
852
|
-
|
|
887
|
+
if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
|
|
888
|
+
this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
|
|
889
|
+
return false;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
const cachedLastActionSlot = this.lastActions[action];
|
|
893
|
+
this.lastActions[action] = slotNumber;
|
|
894
|
+
|
|
895
|
+
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
853
896
|
|
|
854
|
-
|
|
897
|
+
let gasUsed: bigint;
|
|
855
898
|
try {
|
|
856
899
|
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], ErrorsAbi)); // TODO(palla/slash): Check the timestamp logic
|
|
857
900
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
@@ -875,6 +918,7 @@ export class SequencerPublisher {
|
|
|
875
918
|
const success = result && result.receipt && result.receipt.status === 'success' && checkSuccess(result.receipt);
|
|
876
919
|
if (!success) {
|
|
877
920
|
this.log.warn(`Action ${action} at ${slotNumber} failed`, { ...result, ...logData });
|
|
921
|
+
this.lastActions[action] = cachedLastActionSlot;
|
|
878
922
|
} else {
|
|
879
923
|
this.log.info(`Action ${action} at ${slotNumber} succeeded`, { ...result, ...logData });
|
|
880
924
|
}
|
|
@@ -904,54 +948,58 @@ export class SequencerPublisher {
|
|
|
904
948
|
private async prepareProposeTx(
|
|
905
949
|
encodedData: L1ProcessArgs,
|
|
906
950
|
timestamp: bigint,
|
|
907
|
-
options: { forcePendingBlockNumber?:
|
|
951
|
+
options: { forcePendingBlockNumber?: BlockNumber },
|
|
908
952
|
) {
|
|
909
953
|
const kzg = Blob.getViemKzgInstance();
|
|
910
|
-
const blobInput =
|
|
954
|
+
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
911
955
|
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
|
-
|
|
935
|
-
const attestations = encodedData.attestations ? encodedData.attestations.map(attest => attest.toViem()) : [];
|
|
936
|
-
const txHashes = encodedData.txHashes ? encodedData.txHashes.map(txHash => txHash.toString()) : [];
|
|
937
956
|
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
957
|
+
// Get blob evaluation gas
|
|
958
|
+
let blobEvaluationGas: bigint;
|
|
959
|
+
if (this.config.fishermanMode) {
|
|
960
|
+
// In fisherman mode, we can't estimate blob gas because estimateGas doesn't support state overrides
|
|
961
|
+
// Use a fixed estimate.
|
|
962
|
+
blobEvaluationGas = BigInt(encodedData.blobs.length) * 21_000n;
|
|
963
|
+
this.log.debug(`Using fixed blob evaluation gas estimate in fisherman mode: ${blobEvaluationGas}`);
|
|
964
|
+
} else {
|
|
965
|
+
// Normal mode - use estimateGas with blob inputs
|
|
966
|
+
blobEvaluationGas = await this.l1TxUtils
|
|
967
|
+
.estimateGas(
|
|
968
|
+
this.getSenderAddress().toString(),
|
|
969
|
+
{
|
|
970
|
+
to: this.rollupContract.address,
|
|
971
|
+
data: encodeFunctionData({
|
|
972
|
+
abi: RollupAbi,
|
|
973
|
+
functionName: 'validateBlobs',
|
|
974
|
+
args: [blobInput],
|
|
975
|
+
}),
|
|
976
|
+
},
|
|
977
|
+
{},
|
|
978
|
+
{
|
|
979
|
+
blobs: encodedData.blobs.map(b => b.data),
|
|
980
|
+
kzg,
|
|
981
|
+
},
|
|
982
|
+
)
|
|
983
|
+
.catch(err => {
|
|
984
|
+
const { message, metaMessages } = formatViemError(err);
|
|
985
|
+
this.log.error(`Failed to validate blobs`, message, { metaMessages });
|
|
986
|
+
throw new Error('Failed to validate blobs');
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
const signers = encodedData.attestationsAndSigners.getSigners().map(signer => signer.toString());
|
|
941
990
|
|
|
942
991
|
const args = [
|
|
943
992
|
{
|
|
944
993
|
header: encodedData.header.toViem(),
|
|
945
994
|
archive: toHex(encodedData.archive),
|
|
946
|
-
stateReference: encodedData.stateReference.toViem(),
|
|
947
995
|
oracleInput: {
|
|
948
996
|
// We are currently not modifying these. See #9963
|
|
949
997
|
feeAssetPriceModifier: 0n,
|
|
950
998
|
},
|
|
951
|
-
txHashes,
|
|
952
999
|
},
|
|
953
|
-
|
|
954
|
-
signers
|
|
1000
|
+
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
1001
|
+
signers,
|
|
1002
|
+
encodedData.attestationsAndSignersSignature.toViemSignature(),
|
|
955
1003
|
blobInput,
|
|
956
1004
|
] as const;
|
|
957
1005
|
|
|
@@ -971,18 +1019,17 @@ export class SequencerPublisher {
|
|
|
971
1019
|
{
|
|
972
1020
|
readonly header: ViemHeader;
|
|
973
1021
|
readonly archive: `0x${string}`;
|
|
974
|
-
readonly stateReference: ViemStateReference;
|
|
975
|
-
readonly txHashes: `0x${string}`[];
|
|
976
1022
|
readonly oracleInput: {
|
|
977
1023
|
readonly feeAssetPriceModifier: 0n;
|
|
978
1024
|
};
|
|
979
1025
|
},
|
|
980
1026
|
ViemCommitteeAttestations,
|
|
981
|
-
`0x${string}`[],
|
|
1027
|
+
`0x${string}`[], // Signers
|
|
1028
|
+
ViemSignature,
|
|
982
1029
|
`0x${string}`,
|
|
983
1030
|
],
|
|
984
1031
|
timestamp: bigint,
|
|
985
|
-
options: { forcePendingBlockNumber?:
|
|
1032
|
+
options: { forcePendingBlockNumber?: BlockNumber },
|
|
986
1033
|
) {
|
|
987
1034
|
const rollupData = encodeFunctionData({
|
|
988
1035
|
abi: RollupAbi,
|
|
@@ -990,19 +1037,42 @@ export class SequencerPublisher {
|
|
|
990
1037
|
args,
|
|
991
1038
|
});
|
|
992
1039
|
|
|
993
|
-
// override the pending
|
|
994
|
-
const
|
|
1040
|
+
// override the pending checkpoint number if requested
|
|
1041
|
+
const optsForcePendingCheckpointNumber =
|
|
995
1042
|
options.forcePendingBlockNumber !== undefined
|
|
996
|
-
?
|
|
1043
|
+
? CheckpointNumber.fromBlockNumber(options.forcePendingBlockNumber)
|
|
1044
|
+
: undefined;
|
|
1045
|
+
const forcePendingCheckpointNumberStateDiff = (
|
|
1046
|
+
optsForcePendingCheckpointNumber !== undefined
|
|
1047
|
+
? await this.rollupContract.makePendingCheckpointNumberOverride(optsForcePendingCheckpointNumber)
|
|
997
1048
|
: []
|
|
998
1049
|
).flatMap(override => override.stateDiff ?? []);
|
|
999
1050
|
|
|
1051
|
+
const stateOverrides: StateOverride = [
|
|
1052
|
+
{
|
|
1053
|
+
address: this.rollupContract.address,
|
|
1054
|
+
// @note we override checkBlob to false since blobs are not part simulate()
|
|
1055
|
+
stateDiff: [
|
|
1056
|
+
{ slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
|
|
1057
|
+
...forcePendingCheckpointNumberStateDiff,
|
|
1058
|
+
],
|
|
1059
|
+
},
|
|
1060
|
+
];
|
|
1061
|
+
// In fisherman mode, simulate as the proposer but with sufficient balance
|
|
1062
|
+
if (this.proposerAddressForSimulation) {
|
|
1063
|
+
stateOverrides.push({
|
|
1064
|
+
address: this.proposerAddressForSimulation.toString(),
|
|
1065
|
+
balance: 10n * WEI_CONST * WEI_CONST, // 10 ETH
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1000
1069
|
const simulationResult = await this.l1TxUtils
|
|
1001
1070
|
.simulate(
|
|
1002
1071
|
{
|
|
1003
1072
|
to: this.rollupContract.address,
|
|
1004
1073
|
data: rollupData,
|
|
1005
1074
|
gas: SequencerPublisher.PROPOSE_GAS_GUESS,
|
|
1075
|
+
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1006
1076
|
},
|
|
1007
1077
|
{
|
|
1008
1078
|
// @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 +1080,7 @@ export class SequencerPublisher {
|
|
|
1010
1080
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1011
1081
|
gasLimit: SequencerPublisher.PROPOSE_GAS_GUESS * 2n,
|
|
1012
1082
|
},
|
|
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
|
-
],
|
|
1083
|
+
stateOverrides,
|
|
1023
1084
|
RollupAbi,
|
|
1024
1085
|
{
|
|
1025
1086
|
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
@@ -1027,7 +1088,17 @@ export class SequencerPublisher {
|
|
|
1027
1088
|
},
|
|
1028
1089
|
)
|
|
1029
1090
|
.catch(err => {
|
|
1030
|
-
|
|
1091
|
+
// In fisherman mode, we expect ValidatorSelection__MissingProposerSignature since fisherman doesn't have proposer signature
|
|
1092
|
+
const viemError = formatViemError(err);
|
|
1093
|
+
if (this.config.fishermanMode && viemError.message?.includes('ValidatorSelection__MissingProposerSignature')) {
|
|
1094
|
+
this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
|
|
1095
|
+
// Return a minimal simulation result with the fallback gas estimate
|
|
1096
|
+
return {
|
|
1097
|
+
gasUsed: SequencerPublisher.PROPOSE_GAS_GUESS,
|
|
1098
|
+
logs: [],
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1031
1102
|
throw err;
|
|
1032
1103
|
});
|
|
1033
1104
|
|
|
@@ -1037,7 +1108,7 @@ export class SequencerPublisher {
|
|
|
1037
1108
|
private async addProposeTx(
|
|
1038
1109
|
block: L2Block,
|
|
1039
1110
|
encodedData: L1ProcessArgs,
|
|
1040
|
-
opts: { txTimeoutAt?: Date; forcePendingBlockNumber?:
|
|
1111
|
+
opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: BlockNumber } = {},
|
|
1041
1112
|
timestamp: bigint,
|
|
1042
1113
|
): Promise<void> {
|
|
1043
1114
|
const timer = new Timer();
|
|
@@ -1066,7 +1137,7 @@ export class SequencerPublisher {
|
|
|
1066
1137
|
to: this.rollupContract.address,
|
|
1067
1138
|
data: rollupData,
|
|
1068
1139
|
},
|
|
1069
|
-
lastValidL2Slot: block.header.globalVariables.slotNumber
|
|
1140
|
+
lastValidL2Slot: block.header.globalVariables.slotNumber,
|
|
1070
1141
|
gasConfig: { ...opts, gasLimit },
|
|
1071
1142
|
blobConfig: {
|
|
1072
1143
|
blobs: encodedData.blobs.map(b => b.data),
|
|
@@ -1080,17 +1151,20 @@ export class SequencerPublisher {
|
|
|
1080
1151
|
const success =
|
|
1081
1152
|
receipt &&
|
|
1082
1153
|
receipt.status === 'success' &&
|
|
1083
|
-
tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, '
|
|
1154
|
+
tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointProposed');
|
|
1084
1155
|
if (success) {
|
|
1085
1156
|
const endBlock = receipt.blockNumber;
|
|
1086
1157
|
const inclusionBlocks = Number(endBlock - startBlock);
|
|
1158
|
+
const { calldataGas, calldataSize, sender } = stats!;
|
|
1087
1159
|
const publishStats: L1PublishBlockStats = {
|
|
1088
1160
|
gasPrice: receipt.effectiveGasPrice,
|
|
1089
1161
|
gasUsed: receipt.gasUsed,
|
|
1090
1162
|
blobGasUsed: receipt.blobGasUsed ?? 0n,
|
|
1091
1163
|
blobDataGas: receipt.blobGasPrice ?? 0n,
|
|
1092
1164
|
transactionHash: receipt.transactionHash,
|
|
1093
|
-
|
|
1165
|
+
calldataGas,
|
|
1166
|
+
calldataSize,
|
|
1167
|
+
sender,
|
|
1094
1168
|
...block.getStats(),
|
|
1095
1169
|
eventName: 'rollup-published-to-l1',
|
|
1096
1170
|
blobCount: encodedData.blobs.length,
|
|
@@ -1106,7 +1180,7 @@ export class SequencerPublisher {
|
|
|
1106
1180
|
...block.getStats(),
|
|
1107
1181
|
receipt,
|
|
1108
1182
|
txHash: receipt.transactionHash,
|
|
1109
|
-
slotNumber: block.header.globalVariables.slotNumber
|
|
1183
|
+
slotNumber: block.header.globalVariables.slotNumber,
|
|
1110
1184
|
});
|
|
1111
1185
|
return false;
|
|
1112
1186
|
}
|