@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,25 +1,22 @@
|
|
|
1
|
-
import { Blob } from '@aztec/blob-lib';
|
|
1
|
+
import { Blob, getBlobsPerL1Block, getPrefixedEthBlobCommitments } from '@aztec/blob-lib';
|
|
2
2
|
import { createBlobSinkClient } from '@aztec/blob-sink/client';
|
|
3
|
-
import {
|
|
3
|
+
import { MULTI_CALL_3_ADDRESS, Multicall3, RollupContract } from '@aztec/ethereum/contracts';
|
|
4
|
+
import { WEI_CONST } from '@aztec/ethereum/l1-tx-utils';
|
|
5
|
+
import { FormattedViemError, formatViemError, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
4
6
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
5
7
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
8
|
+
import { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
6
9
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
10
|
+
import { Signature } from '@aztec/foundation/eth-signature';
|
|
7
11
|
import { createLogger } from '@aztec/foundation/log';
|
|
8
12
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
9
13
|
import { Timer } from '@aztec/foundation/timer';
|
|
10
14
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
11
15
|
import { encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
12
|
-
import { CommitteeAttestation } from '@aztec/stdlib/block';
|
|
13
|
-
import { ConsensusPayload, SignatureDomainSeparator, getHashedSignaturePayload } from '@aztec/stdlib/p2p';
|
|
16
|
+
import { CommitteeAttestation, CommitteeAttestationsAndSigners } from '@aztec/stdlib/block';
|
|
14
17
|
import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
15
|
-
import pick from 'lodash.pick';
|
|
16
18
|
import { encodeFunctionData, toHex } from 'viem';
|
|
17
19
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
18
|
-
export var SignalType = /*#__PURE__*/ function(SignalType) {
|
|
19
|
-
SignalType[SignalType["GOVERNANCE"] = 0] = "GOVERNANCE";
|
|
20
|
-
SignalType[SignalType["SLASHING"] = 1] = "SLASHING";
|
|
21
|
-
return SignalType;
|
|
22
|
-
}({});
|
|
23
20
|
export const Actions = [
|
|
24
21
|
'invalidate-by-invalid-attestation',
|
|
25
22
|
'invalidate-by-insufficient-attestations',
|
|
@@ -40,10 +37,11 @@ export class SequencerPublisher {
|
|
|
40
37
|
epochCache;
|
|
41
38
|
governanceLog;
|
|
42
39
|
slashingLog;
|
|
43
|
-
|
|
40
|
+
lastActions;
|
|
44
41
|
log;
|
|
45
42
|
ethereumSlotDuration;
|
|
46
43
|
blobSinkClient;
|
|
44
|
+
/** Address to use for simulations in fisherman mode (actual proposer's address) */ proposerAddressForSimulation;
|
|
47
45
|
// @note - with blobs, the below estimate seems too large.
|
|
48
46
|
// Total used for full block from int_l1_pub e2e test: 1m (of which 86k is 1x blob)
|
|
49
47
|
// Total used for emptier block from above test: 429k (of which 84k is 1x blob)
|
|
@@ -63,14 +61,12 @@ export class SequencerPublisher {
|
|
|
63
61
|
this.interrupted = false;
|
|
64
62
|
this.governanceLog = createLogger('sequencer:publisher:governance');
|
|
65
63
|
this.slashingLog = createLogger('sequencer:publisher:slashing');
|
|
66
|
-
this.
|
|
67
|
-
[0]: 0n,
|
|
68
|
-
[1]: 0n
|
|
69
|
-
};
|
|
70
|
-
this.log = createLogger('sequencer:publisher');
|
|
64
|
+
this.lastActions = {};
|
|
71
65
|
this.requests = [];
|
|
66
|
+
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
72
67
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
73
68
|
this.epochCache = deps.epochCache;
|
|
69
|
+
this.lastActions = deps.lastActions;
|
|
74
70
|
this.blobSinkClient = deps.blobSinkClient ?? createBlobSinkClient(config, {
|
|
75
71
|
logger: createLogger('sequencer:blob-sink:client')
|
|
76
72
|
});
|
|
@@ -93,6 +89,12 @@ export class SequencerPublisher {
|
|
|
93
89
|
getSenderAddress() {
|
|
94
90
|
return this.l1TxUtils.getSenderAddress();
|
|
95
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Sets the proposer address to use for simulations in fisherman mode.
|
|
94
|
+
* @param proposerAddress - The actual proposer's address to use for balance lookups in simulations
|
|
95
|
+
*/ setProposerAddressForSimulation(proposerAddress) {
|
|
96
|
+
this.proposerAddressForSimulation = proposerAddress;
|
|
97
|
+
}
|
|
96
98
|
addRequest(request) {
|
|
97
99
|
this.requests.push(request);
|
|
98
100
|
}
|
|
@@ -100,6 +102,15 @@ export class SequencerPublisher {
|
|
|
100
102
|
return this.epochCache.getEpochAndSlotNow().slot;
|
|
101
103
|
}
|
|
102
104
|
/**
|
|
105
|
+
* Clears all pending requests without sending them.
|
|
106
|
+
*/ clearPendingRequests() {
|
|
107
|
+
const count = this.requests.length;
|
|
108
|
+
this.requests = [];
|
|
109
|
+
if (count > 0) {
|
|
110
|
+
this.log.debug(`Cleared ${count} pending request(s)`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
103
114
|
* Sends all requests that are still valid.
|
|
104
115
|
* @returns one of:
|
|
105
116
|
* - A receipt and stats if the tx succeeded
|
|
@@ -148,7 +159,7 @@ export class SequencerPublisher {
|
|
|
148
159
|
const gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
|
|
149
160
|
const txTimeoutAts = gasConfigs.map((g)=>g?.txTimeoutAt).filter((g)=>g !== undefined);
|
|
150
161
|
const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map((g)=>g.getTime()))) : undefined; // earliest
|
|
151
|
-
const
|
|
162
|
+
const txConfig = {
|
|
152
163
|
gasLimit,
|
|
153
164
|
txTimeoutAt
|
|
154
165
|
};
|
|
@@ -157,9 +168,10 @@ export class SequencerPublisher {
|
|
|
157
168
|
validRequests.sort((a, b)=>compareActions(a.action, b.action));
|
|
158
169
|
try {
|
|
159
170
|
this.log.debug('Forwarding transactions', {
|
|
160
|
-
validRequests: validRequests.map((request)=>request.action)
|
|
171
|
+
validRequests: validRequests.map((request)=>request.action),
|
|
172
|
+
txConfig
|
|
161
173
|
});
|
|
162
|
-
const result = await Multicall3.forward(validRequests.map((request)=>request.request), this.l1TxUtils,
|
|
174
|
+
const result = await Multicall3.forward(validRequests.map((request)=>request.request), this.l1TxUtils, txConfig, blobConfig, this.rollupContract.address, this.log);
|
|
163
175
|
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(validRequests, result);
|
|
164
176
|
return {
|
|
165
177
|
result,
|
|
@@ -218,7 +230,9 @@ export class SequencerPublisher {
|
|
|
218
230
|
'InvalidProposer',
|
|
219
231
|
'InvalidArchive'
|
|
220
232
|
];
|
|
221
|
-
return this.rollupContract.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), this.ethereumSlotDuration,
|
|
233
|
+
return this.rollupContract.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
|
|
234
|
+
forcePendingCheckpointNumber: opts.forcePendingBlockNumber !== undefined ? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber) : undefined
|
|
235
|
+
}).catch((err)=>{
|
|
222
236
|
if (err instanceof FormattedViemError && ignoredErrors.find((e)=>err.message.includes(e))) {
|
|
223
237
|
this.log.warn(`Failed canProposeAtTime check with ${ignoredErrors.find((e)=>err.message.includes(e))}`, {
|
|
224
238
|
error: err.message
|
|
@@ -241,16 +255,28 @@ export class SequencerPublisher {
|
|
|
241
255
|
};
|
|
242
256
|
const args = [
|
|
243
257
|
header.toViem(),
|
|
244
|
-
|
|
258
|
+
CommitteeAttestationsAndSigners.empty().getPackedAttestations(),
|
|
245
259
|
[],
|
|
260
|
+
Signature.empty().toViemSignature(),
|
|
246
261
|
`0x${'0'.repeat(64)}`,
|
|
247
262
|
header.contentCommitment.blobsHash.toString(),
|
|
248
263
|
flags
|
|
249
264
|
];
|
|
250
265
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
251
|
-
|
|
252
|
-
const
|
|
253
|
-
|
|
266
|
+
const optsForcePendingCheckpointNumber = opts?.forcePendingBlockNumber !== undefined ? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber) : undefined;
|
|
267
|
+
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(optsForcePendingCheckpointNumber);
|
|
268
|
+
let balance = 0n;
|
|
269
|
+
if (this.config.fishermanMode) {
|
|
270
|
+
// In fisherman mode, we can't know where the proposer is publishing from
|
|
271
|
+
// so we just add sufficient balance to the multicall3 address
|
|
272
|
+
balance = 10n * WEI_CONST * WEI_CONST; // 10 ETH
|
|
273
|
+
} else {
|
|
274
|
+
balance = await this.l1TxUtils.getSenderBalance();
|
|
275
|
+
}
|
|
276
|
+
stateOverrides.push({
|
|
277
|
+
address: MULTI_CALL_3_ADDRESS,
|
|
278
|
+
balance
|
|
279
|
+
});
|
|
254
280
|
await this.l1TxUtils.simulate({
|
|
255
281
|
to: this.rollupContract.address,
|
|
256
282
|
data: encodeFunctionData({
|
|
@@ -261,13 +287,7 @@ export class SequencerPublisher {
|
|
|
261
287
|
from: MULTI_CALL_3_ADDRESS
|
|
262
288
|
}, {
|
|
263
289
|
time: ts + 1n
|
|
264
|
-
},
|
|
265
|
-
{
|
|
266
|
-
address: MULTI_CALL_3_ADDRESS,
|
|
267
|
-
balance
|
|
268
|
-
},
|
|
269
|
-
...await this.rollupContract.makePendingBlockNumberOverride(opts?.forcePendingBlockNumber)
|
|
270
|
-
]);
|
|
290
|
+
}, stateOverrides);
|
|
271
291
|
this.log.debug(`Simulated validateHeader`);
|
|
272
292
|
}
|
|
273
293
|
/**
|
|
@@ -278,13 +298,13 @@ export class SequencerPublisher {
|
|
|
278
298
|
return undefined;
|
|
279
299
|
}
|
|
280
300
|
const { reason, block } = validationResult;
|
|
281
|
-
const blockNumber = block.
|
|
301
|
+
const blockNumber = block.blockNumber;
|
|
282
302
|
const logData = {
|
|
283
|
-
...block
|
|
303
|
+
...block,
|
|
284
304
|
reason
|
|
285
305
|
};
|
|
286
|
-
const currentBlockNumber = await this.rollupContract.
|
|
287
|
-
if (currentBlockNumber < validationResult.block.
|
|
306
|
+
const currentBlockNumber = await this.rollupContract.getCheckpointNumber();
|
|
307
|
+
if (currentBlockNumber < validationResult.block.blockNumber) {
|
|
288
308
|
this.log.verbose(`Skipping block ${blockNumber} invalidation since it has already been removed from the pending chain`, {
|
|
289
309
|
currentBlockNumber,
|
|
290
310
|
...logData
|
|
@@ -292,7 +312,10 @@ export class SequencerPublisher {
|
|
|
292
312
|
return undefined;
|
|
293
313
|
}
|
|
294
314
|
const request = this.buildInvalidateBlockRequest(validationResult);
|
|
295
|
-
this.log.debug(`Simulating invalidate block ${blockNumber}`,
|
|
315
|
+
this.log.debug(`Simulating invalidate block ${blockNumber}`, {
|
|
316
|
+
...logData,
|
|
317
|
+
request
|
|
318
|
+
});
|
|
296
319
|
try {
|
|
297
320
|
const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, ErrorsAbi);
|
|
298
321
|
this.log.verbose(`Simulation for invalidate block ${blockNumber} succeeded`, {
|
|
@@ -304,7 +327,7 @@ export class SequencerPublisher {
|
|
|
304
327
|
request,
|
|
305
328
|
gasUsed,
|
|
306
329
|
blockNumber,
|
|
307
|
-
forcePendingBlockNumber: blockNumber - 1,
|
|
330
|
+
forcePendingBlockNumber: BlockNumber(blockNumber - 1),
|
|
308
331
|
reason
|
|
309
332
|
};
|
|
310
333
|
} catch (err) {
|
|
@@ -317,7 +340,7 @@ export class SequencerPublisher {
|
|
|
317
340
|
request,
|
|
318
341
|
error: viemError.message
|
|
319
342
|
});
|
|
320
|
-
const latestPendingBlockNumber = await this.rollupContract.
|
|
343
|
+
const latestPendingBlockNumber = await this.rollupContract.getCheckpointNumber();
|
|
321
344
|
if (latestPendingBlockNumber < blockNumber) {
|
|
322
345
|
this.log.verbose(`Block number ${blockNumber} has already been invalidated`, {
|
|
323
346
|
...logData
|
|
@@ -343,14 +366,15 @@ export class SequencerPublisher {
|
|
|
343
366
|
}
|
|
344
367
|
const { block, committee, reason } = validationResult;
|
|
345
368
|
const logData = {
|
|
346
|
-
...block
|
|
369
|
+
...block,
|
|
347
370
|
reason
|
|
348
371
|
};
|
|
349
|
-
this.log.debug(`Simulating invalidate block ${block.
|
|
372
|
+
this.log.debug(`Simulating invalidate block ${block.blockNumber}`, logData);
|
|
373
|
+
const attestationsAndSigners = new CommitteeAttestationsAndSigners(validationResult.attestations).getPackedAttestations();
|
|
350
374
|
if (reason === 'invalid-attestation') {
|
|
351
|
-
return this.rollupContract.buildInvalidateBadAttestationRequest(
|
|
375
|
+
return this.rollupContract.buildInvalidateBadAttestationRequest(CheckpointNumber.fromBlockNumber(block.blockNumber), attestationsAndSigners, committee, validationResult.invalidIndex);
|
|
352
376
|
} else if (reason === 'insufficient-attestations') {
|
|
353
|
-
return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
|
|
377
|
+
return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(CheckpointNumber.fromBlockNumber(block.blockNumber), attestationsAndSigners, committee);
|
|
354
378
|
} else {
|
|
355
379
|
const _ = reason;
|
|
356
380
|
throw new Error(`Unknown reason for invalidation`);
|
|
@@ -364,45 +388,41 @@ export class SequencerPublisher {
|
|
|
364
388
|
* @param block - The block to propose
|
|
365
389
|
* @param attestationData - The block's attestation data
|
|
366
390
|
*
|
|
367
|
-
*/ async validateBlockForSubmission(block,
|
|
368
|
-
digest: Buffer.alloc(32),
|
|
369
|
-
attestations: []
|
|
370
|
-
}, options) {
|
|
391
|
+
*/ async validateBlockForSubmission(block, attestationsAndSigners, attestationsAndSignersSignature, options) {
|
|
371
392
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
372
393
|
// If we have no attestations, we still need to provide the empty attestations
|
|
373
394
|
// so that the committee is recalculated correctly
|
|
374
|
-
const ignoreSignatures =
|
|
395
|
+
const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
375
396
|
if (ignoreSignatures) {
|
|
376
|
-
const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber
|
|
397
|
+
const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
|
|
377
398
|
if (!committee) {
|
|
378
|
-
this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber
|
|
379
|
-
throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber
|
|
399
|
+
this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
400
|
+
throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
380
401
|
}
|
|
381
|
-
|
|
402
|
+
attestationsAndSigners.attestations = committee.map((committeeMember)=>CommitteeAttestation.fromAddress(committeeMember));
|
|
382
403
|
}
|
|
383
|
-
const
|
|
384
|
-
const
|
|
385
|
-
const
|
|
386
|
-
const signers = attestationData.attestations.filter((attest)=>!attest.signature.isEmpty()).map((attest)=>attest.address.toString());
|
|
404
|
+
const blobFields = block.getCheckpointBlobFields();
|
|
405
|
+
const blobs = getBlobsPerL1Block(blobFields);
|
|
406
|
+
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
387
407
|
const args = [
|
|
388
408
|
{
|
|
389
|
-
header: block.
|
|
409
|
+
header: block.getCheckpointHeader().toViem(),
|
|
390
410
|
archive: toHex(block.archive.root.toBuffer()),
|
|
391
|
-
stateReference: block.header.state.toViem(),
|
|
392
|
-
txHashes: block.body.txEffects.map((txEffect)=>txEffect.txHash.toString()),
|
|
393
411
|
oracleInput: {
|
|
394
412
|
feeAssetPriceModifier: 0n
|
|
395
413
|
}
|
|
396
414
|
},
|
|
397
|
-
|
|
398
|
-
|
|
415
|
+
attestationsAndSigners.getPackedAttestations(),
|
|
416
|
+
attestationsAndSigners.getSigners().map((signer)=>signer.toString()),
|
|
417
|
+
attestationsAndSignersSignature.toViemSignature(),
|
|
399
418
|
blobInput
|
|
400
419
|
];
|
|
401
420
|
await this.simulateProposeTx(args, ts, options);
|
|
402
421
|
return ts;
|
|
403
422
|
}
|
|
404
423
|
async enqueueCastSignalHelper(slotNumber, timestamp, signalType, payload, base, signerAddress, signer) {
|
|
405
|
-
if (this.
|
|
424
|
+
if (this.lastActions[signalType] && this.lastActions[signalType] === slotNumber) {
|
|
425
|
+
this.log.debug(`Skipping duplicate vote cast signal ${signalType} for slot ${slotNumber}`);
|
|
406
426
|
return false;
|
|
407
427
|
}
|
|
408
428
|
if (payload.equals(EthAddress.ZERO)) {
|
|
@@ -417,9 +437,9 @@ export class SequencerPublisher {
|
|
|
417
437
|
if (roundInfo.lastSignalSlot >= slotNumber) {
|
|
418
438
|
return false;
|
|
419
439
|
}
|
|
420
|
-
const cachedLastVote = this.
|
|
421
|
-
this.
|
|
422
|
-
const action = signalType
|
|
440
|
+
const cachedLastVote = this.lastActions[signalType];
|
|
441
|
+
this.lastActions[signalType] = slotNumber;
|
|
442
|
+
const action = signalType;
|
|
423
443
|
const request = await base.createSignalRequestWithSignature(payload.toString(), slotNumber, this.config.l1ChainId, signerAddress.toString(), signer);
|
|
424
444
|
this.log.debug(`Created ${action} request with signature`, {
|
|
425
445
|
request,
|
|
@@ -456,7 +476,7 @@ export class SequencerPublisher {
|
|
|
456
476
|
};
|
|
457
477
|
if (!success) {
|
|
458
478
|
this.log.error(`Signaling in [${action}] for ${payload} at slot ${slotNumber} in round ${round} failed`, logData);
|
|
459
|
-
this.
|
|
479
|
+
this.lastActions[signalType] = cachedLastVote;
|
|
460
480
|
return false;
|
|
461
481
|
} else {
|
|
462
482
|
this.log.info(`Signaling in [${action}] for ${payload} at slot ${slotNumber} in round ${round} succeeded`, logData);
|
|
@@ -472,7 +492,7 @@ export class SequencerPublisher {
|
|
|
472
492
|
* @param timestamp - The timestamp of the slot to cast a signal for.
|
|
473
493
|
* @returns True if the signal was successfully enqueued, false otherwise.
|
|
474
494
|
*/ enqueueGovernanceCastSignal(governancePayload, slotNumber, timestamp, signerAddress, signer) {
|
|
475
|
-
return this.enqueueCastSignalHelper(slotNumber, timestamp,
|
|
495
|
+
return this.enqueueCastSignalHelper(slotNumber, timestamp, 'governance-signal', governancePayload, this.govProposerContract, signerAddress, signer);
|
|
476
496
|
}
|
|
477
497
|
/** Enqueues all slashing actions as returned by the slasher client. */ async enqueueSlashingActions(actions, slotNumber, timestamp, signerAddress, signer) {
|
|
478
498
|
if (actions.length === 0) {
|
|
@@ -490,7 +510,7 @@ export class SequencerPublisher {
|
|
|
490
510
|
this.log.debug(`Enqueuing slashing vote for payload ${action.payload} at slot ${slotNumber}`, {
|
|
491
511
|
signerAddress
|
|
492
512
|
});
|
|
493
|
-
await this.enqueueCastSignalHelper(slotNumber, timestamp,
|
|
513
|
+
await this.enqueueCastSignalHelper(slotNumber, timestamp, 'empire-slashing-signal', action.payload, this.slashingProposerContract, signerAddress, signer);
|
|
494
514
|
break;
|
|
495
515
|
}
|
|
496
516
|
case 'create-empire-payload':
|
|
@@ -566,19 +586,17 @@ export class SequencerPublisher {
|
|
|
566
586
|
*
|
|
567
587
|
* @param block - L2 block to propose.
|
|
568
588
|
* @returns True if the tx has been enqueued, throws otherwise. See #9315
|
|
569
|
-
*/ async enqueueProposeL2Block(block,
|
|
570
|
-
const
|
|
571
|
-
const
|
|
572
|
-
const
|
|
573
|
-
const blobs = await Blob.getBlobsPerBlock(block.body.toBlobFields());
|
|
589
|
+
*/ async enqueueProposeL2Block(block, attestationsAndSigners, attestationsAndSignersSignature, opts = {}) {
|
|
590
|
+
const checkpointHeader = block.getCheckpointHeader();
|
|
591
|
+
const blobFields = block.getCheckpointBlobFields();
|
|
592
|
+
const blobs = getBlobsPerL1Block(blobFields);
|
|
574
593
|
const proposeTxArgs = {
|
|
575
|
-
header:
|
|
594
|
+
header: checkpointHeader,
|
|
576
595
|
archive: block.archive.root.toBuffer(),
|
|
577
|
-
stateReference: block.header.state,
|
|
578
596
|
body: block.body.toBuffer(),
|
|
579
597
|
blobs,
|
|
580
|
-
|
|
581
|
-
|
|
598
|
+
attestationsAndSigners,
|
|
599
|
+
attestationsAndSignersSignature
|
|
582
600
|
};
|
|
583
601
|
let ts;
|
|
584
602
|
try {
|
|
@@ -586,16 +604,12 @@ export class SequencerPublisher {
|
|
|
586
604
|
// This means that we can avoid the simulation issues in later checks.
|
|
587
605
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
588
606
|
// make time consistency checks break.
|
|
589
|
-
const attestationData = {
|
|
590
|
-
digest: digest.toBuffer(),
|
|
591
|
-
attestations: attestations ?? []
|
|
592
|
-
};
|
|
593
607
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
594
|
-
ts = await this.validateBlockForSubmission(block,
|
|
608
|
+
ts = await this.validateBlockForSubmission(block, attestationsAndSigners, attestationsAndSignersSignature, opts);
|
|
595
609
|
} catch (err) {
|
|
596
610
|
this.log.error(`Block validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
597
611
|
...block.getStats(),
|
|
598
|
-
slotNumber: block.header.globalVariables.slotNumber
|
|
612
|
+
slotNumber: block.header.globalVariables.slotNumber,
|
|
599
613
|
forcePendingBlockNumber: opts.forcePendingBlockNumber
|
|
600
614
|
});
|
|
601
615
|
throw err;
|
|
@@ -613,8 +627,10 @@ export class SequencerPublisher {
|
|
|
613
627
|
}
|
|
614
628
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
615
629
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil(Number(request.gasUsed) * 64 / 63)));
|
|
630
|
+
const { gasUsed, blockNumber } = request;
|
|
616
631
|
const logData = {
|
|
617
|
-
|
|
632
|
+
gasUsed,
|
|
633
|
+
blockNumber,
|
|
618
634
|
gasLimit,
|
|
619
635
|
opts
|
|
620
636
|
};
|
|
@@ -626,9 +642,9 @@ export class SequencerPublisher {
|
|
|
626
642
|
gasLimit,
|
|
627
643
|
txTimeoutAt: opts.txTimeoutAt
|
|
628
644
|
},
|
|
629
|
-
lastValidL2Slot: this.getCurrentL2Slot() +
|
|
645
|
+
lastValidL2Slot: SlotNumber(this.getCurrentL2Slot() + 2),
|
|
630
646
|
checkSuccess: (_req, result)=>{
|
|
631
|
-
const success = result && result.receipt && result.receipt.status === 'success' && tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, '
|
|
647
|
+
const success = result && result.receipt && result.receipt.status === 'success' && tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointInvalidated');
|
|
632
648
|
if (!success) {
|
|
633
649
|
this.log.warn(`Invalidate block ${request.blockNumber} failed`, {
|
|
634
650
|
...result,
|
|
@@ -650,8 +666,14 @@ export class SequencerPublisher {
|
|
|
650
666
|
timestamp,
|
|
651
667
|
gasLimit: undefined
|
|
652
668
|
};
|
|
669
|
+
if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
|
|
670
|
+
this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
|
|
671
|
+
return false;
|
|
672
|
+
}
|
|
673
|
+
const cachedLastActionSlot = this.lastActions[action];
|
|
674
|
+
this.lastActions[action] = slotNumber;
|
|
675
|
+
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
653
676
|
let gasUsed;
|
|
654
|
-
this.log.debug(`Simulating ${action}`, logData);
|
|
655
677
|
try {
|
|
656
678
|
({ gasUsed } = await this.l1TxUtils.simulate(request, {
|
|
657
679
|
time: timestamp
|
|
@@ -684,6 +706,7 @@ export class SequencerPublisher {
|
|
|
684
706
|
...result,
|
|
685
707
|
...logData
|
|
686
708
|
});
|
|
709
|
+
this.lastActions[action] = cachedLastActionSlot;
|
|
687
710
|
} else {
|
|
688
711
|
this.log.info(`Action ${action} at ${slotNumber} succeeded`, {
|
|
689
712
|
...result,
|
|
@@ -710,45 +733,52 @@ export class SequencerPublisher {
|
|
|
710
733
|
}
|
|
711
734
|
async prepareProposeTx(encodedData, timestamp, options) {
|
|
712
735
|
const kzg = Blob.getViemKzgInstance();
|
|
713
|
-
const blobInput =
|
|
736
|
+
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
714
737
|
this.log.debug('Validating blob input', {
|
|
715
738
|
blobInput
|
|
716
739
|
});
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
740
|
+
// Get blob evaluation gas
|
|
741
|
+
let blobEvaluationGas;
|
|
742
|
+
if (this.config.fishermanMode) {
|
|
743
|
+
// In fisherman mode, we can't estimate blob gas because estimateGas doesn't support state overrides
|
|
744
|
+
// Use a fixed estimate.
|
|
745
|
+
blobEvaluationGas = BigInt(encodedData.blobs.length) * 21_000n;
|
|
746
|
+
this.log.debug(`Using fixed blob evaluation gas estimate in fisherman mode: ${blobEvaluationGas}`);
|
|
747
|
+
} else {
|
|
748
|
+
// Normal mode - use estimateGas with blob inputs
|
|
749
|
+
blobEvaluationGas = await this.l1TxUtils.estimateGas(this.getSenderAddress().toString(), {
|
|
750
|
+
to: this.rollupContract.address,
|
|
751
|
+
data: encodeFunctionData({
|
|
752
|
+
abi: RollupAbi,
|
|
753
|
+
functionName: 'validateBlobs',
|
|
754
|
+
args: [
|
|
755
|
+
blobInput
|
|
756
|
+
]
|
|
757
|
+
})
|
|
758
|
+
}, {}, {
|
|
759
|
+
blobs: encodedData.blobs.map((b)=>b.data),
|
|
760
|
+
kzg
|
|
761
|
+
}).catch((err)=>{
|
|
762
|
+
const { message, metaMessages } = formatViemError(err);
|
|
763
|
+
this.log.error(`Failed to validate blobs`, message, {
|
|
764
|
+
metaMessages
|
|
765
|
+
});
|
|
766
|
+
throw new Error('Failed to validate blobs');
|
|
733
767
|
});
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
const attestations = encodedData.attestations ? encodedData.attestations.map((attest)=>attest.toViem()) : [];
|
|
737
|
-
const txHashes = encodedData.txHashes ? encodedData.txHashes.map((txHash)=>txHash.toString()) : [];
|
|
738
|
-
const signers = encodedData.attestations?.filter((attest)=>!attest.signature.isEmpty()).map((attest)=>attest.address.toString());
|
|
768
|
+
}
|
|
769
|
+
const signers = encodedData.attestationsAndSigners.getSigners().map((signer)=>signer.toString());
|
|
739
770
|
const args = [
|
|
740
771
|
{
|
|
741
772
|
header: encodedData.header.toViem(),
|
|
742
773
|
archive: toHex(encodedData.archive),
|
|
743
|
-
stateReference: encodedData.stateReference.toViem(),
|
|
744
774
|
oracleInput: {
|
|
745
775
|
// We are currently not modifying these. See #9963
|
|
746
776
|
feeAssetPriceModifier: 0n
|
|
747
|
-
}
|
|
748
|
-
txHashes
|
|
777
|
+
}
|
|
749
778
|
},
|
|
750
|
-
|
|
751
|
-
signers
|
|
779
|
+
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
780
|
+
signers,
|
|
781
|
+
encodedData.attestationsAndSignersSignature.toViemSignature(),
|
|
752
782
|
blobInput
|
|
753
783
|
];
|
|
754
784
|
const { rollupData, simulationResult } = await this.simulateProposeTx(args, timestamp, options);
|
|
@@ -770,18 +800,10 @@ export class SequencerPublisher {
|
|
|
770
800
|
functionName: 'propose',
|
|
771
801
|
args
|
|
772
802
|
});
|
|
773
|
-
// override the pending
|
|
774
|
-
const
|
|
775
|
-
const
|
|
776
|
-
|
|
777
|
-
data: rollupData,
|
|
778
|
-
gas: SequencerPublisher.PROPOSE_GAS_GUESS
|
|
779
|
-
}, {
|
|
780
|
-
// @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
|
|
781
|
-
time: timestamp + 1n,
|
|
782
|
-
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
783
|
-
gasLimit: SequencerPublisher.PROPOSE_GAS_GUESS * 2n
|
|
784
|
-
}, [
|
|
803
|
+
// override the pending checkpoint number if requested
|
|
804
|
+
const optsForcePendingCheckpointNumber = options.forcePendingBlockNumber !== undefined ? CheckpointNumber.fromBlockNumber(options.forcePendingBlockNumber) : undefined;
|
|
805
|
+
const forcePendingCheckpointNumberStateDiff = (optsForcePendingCheckpointNumber !== undefined ? await this.rollupContract.makePendingCheckpointNumberOverride(optsForcePendingCheckpointNumber) : []).flatMap((override)=>override.stateDiff ?? []);
|
|
806
|
+
const stateOverrides = [
|
|
785
807
|
{
|
|
786
808
|
address: this.rollupContract.address,
|
|
787
809
|
// @note we override checkBlob to false since blobs are not part simulate()
|
|
@@ -790,14 +812,44 @@ export class SequencerPublisher {
|
|
|
790
812
|
slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true),
|
|
791
813
|
value: toPaddedHex(0n, true)
|
|
792
814
|
},
|
|
793
|
-
...
|
|
815
|
+
...forcePendingCheckpointNumberStateDiff
|
|
794
816
|
]
|
|
795
817
|
}
|
|
796
|
-
]
|
|
818
|
+
];
|
|
819
|
+
// In fisherman mode, simulate as the proposer but with sufficient balance
|
|
820
|
+
if (this.proposerAddressForSimulation) {
|
|
821
|
+
stateOverrides.push({
|
|
822
|
+
address: this.proposerAddressForSimulation.toString(),
|
|
823
|
+
balance: 10n * WEI_CONST * WEI_CONST
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
const simulationResult = await this.l1TxUtils.simulate({
|
|
827
|
+
to: this.rollupContract.address,
|
|
828
|
+
data: rollupData,
|
|
829
|
+
gas: SequencerPublisher.PROPOSE_GAS_GUESS,
|
|
830
|
+
...this.proposerAddressForSimulation && {
|
|
831
|
+
from: this.proposerAddressForSimulation.toString()
|
|
832
|
+
}
|
|
833
|
+
}, {
|
|
834
|
+
// @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
|
|
835
|
+
time: timestamp + 1n,
|
|
836
|
+
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
837
|
+
gasLimit: SequencerPublisher.PROPOSE_GAS_GUESS * 2n
|
|
838
|
+
}, stateOverrides, RollupAbi, {
|
|
797
839
|
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
798
840
|
fallbackGasEstimate: SequencerPublisher.PROPOSE_GAS_GUESS
|
|
799
841
|
}).catch((err)=>{
|
|
800
|
-
|
|
842
|
+
// In fisherman mode, we expect ValidatorSelection__MissingProposerSignature since fisherman doesn't have proposer signature
|
|
843
|
+
const viemError = formatViemError(err);
|
|
844
|
+
if (this.config.fishermanMode && viemError.message?.includes('ValidatorSelection__MissingProposerSignature')) {
|
|
845
|
+
this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
|
|
846
|
+
// Return a minimal simulation result with the fallback gas estimate
|
|
847
|
+
return {
|
|
848
|
+
gasUsed: SequencerPublisher.PROPOSE_GAS_GUESS,
|
|
849
|
+
logs: []
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
801
853
|
throw err;
|
|
802
854
|
});
|
|
803
855
|
return {
|
|
@@ -822,7 +874,7 @@ export class SequencerPublisher {
|
|
|
822
874
|
to: this.rollupContract.address,
|
|
823
875
|
data: rollupData
|
|
824
876
|
},
|
|
825
|
-
lastValidL2Slot: block.header.globalVariables.slotNumber
|
|
877
|
+
lastValidL2Slot: block.header.globalVariables.slotNumber,
|
|
826
878
|
gasConfig: {
|
|
827
879
|
...opts,
|
|
828
880
|
gasLimit
|
|
@@ -836,17 +888,20 @@ export class SequencerPublisher {
|
|
|
836
888
|
return false;
|
|
837
889
|
}
|
|
838
890
|
const { receipt, stats, errorMsg } = result;
|
|
839
|
-
const success = receipt && receipt.status === 'success' && tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, '
|
|
891
|
+
const success = receipt && receipt.status === 'success' && tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointProposed');
|
|
840
892
|
if (success) {
|
|
841
893
|
const endBlock = receipt.blockNumber;
|
|
842
894
|
const inclusionBlocks = Number(endBlock - startBlock);
|
|
895
|
+
const { calldataGas, calldataSize, sender } = stats;
|
|
843
896
|
const publishStats = {
|
|
844
897
|
gasPrice: receipt.effectiveGasPrice,
|
|
845
898
|
gasUsed: receipt.gasUsed,
|
|
846
899
|
blobGasUsed: receipt.blobGasUsed ?? 0n,
|
|
847
900
|
blobDataGas: receipt.blobGasPrice ?? 0n,
|
|
848
901
|
transactionHash: receipt.transactionHash,
|
|
849
|
-
|
|
902
|
+
calldataGas,
|
|
903
|
+
calldataSize,
|
|
904
|
+
sender,
|
|
850
905
|
...block.getStats(),
|
|
851
906
|
eventName: 'rollup-published-to-l1',
|
|
852
907
|
blobCount: encodedData.blobs.length,
|
|
@@ -865,7 +920,7 @@ export class SequencerPublisher {
|
|
|
865
920
|
...block.getStats(),
|
|
866
921
|
receipt,
|
|
867
922
|
txHash: receipt.transactionHash,
|
|
868
|
-
slotNumber: block.header.globalVariables.slotNumber
|
|
923
|
+
slotNumber: block.header.globalVariables.slotNumber
|
|
869
924
|
});
|
|
870
925
|
return false;
|
|
871
926
|
}
|