@aztec/sequencer-client 3.0.0-canary.a9708bd → 3.0.0-devnet.3
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/sequencer-client.d.ts +5 -4
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +17 -12
- package/dest/config.d.ts +2 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +10 -0
- package/dest/publisher/config.d.ts +2 -8
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +7 -16
- package/dest/publisher/index.d.ts +1 -1
- package/dest/publisher/index.d.ts.map +1 -1
- package/dest/publisher/index.js +1 -1
- package/dest/publisher/sequencer-publisher-factory.d.ts +6 -1
- 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 +1 -1
- 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 +16 -22
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +72 -72
- package/dest/sequencer/block_builder.d.ts +2 -5
- package/dest/sequencer/block_builder.d.ts.map +1 -1
- package/dest/sequencer/block_builder.js +18 -6
- 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/metrics.d.ts +6 -18
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +22 -88
- package/dest/sequencer/sequencer.d.ts +8 -7
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +55 -33
- package/dest/sequencer/timetable.d.ts +1 -7
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +3 -10
- package/dest/sequencer/utils.d.ts +10 -24
- package/dest/sequencer/utils.d.ts.map +1 -1
- package/dest/sequencer/utils.js +9 -24
- package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
- package/dest/tx_validator/tx_validator_factory.js +11 -8
- package/package.json +29 -29
- package/src/client/sequencer-client.ts +18 -9
- package/src/config.ts +11 -0
- package/src/publisher/config.ts +13 -22
- package/src/publisher/index.ts +1 -1
- package/src/publisher/sequencer-publisher-factory.ts +13 -2
- package/src/publisher/sequencer-publisher-metrics.ts +1 -1
- package/src/publisher/sequencer-publisher.ts +101 -98
- package/src/sequencer/block_builder.ts +20 -21
- package/src/sequencer/errors.ts +21 -0
- package/src/sequencer/metrics.ts +25 -101
- package/src/sequencer/sequencer.ts +85 -58
- package/src/sequencer/timetable.ts +3 -14
- package/src/sequencer/utils.ts +10 -24
- package/src/tx_validator/tx_validator_factory.ts +10 -5
|
@@ -1,16 +1,15 @@
|
|
|
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
5
|
import {
|
|
6
6
|
type EmpireSlashingProposerContract,
|
|
7
7
|
FormattedViemError,
|
|
8
|
-
type GasPrice,
|
|
9
8
|
type GovernanceProposerContract,
|
|
10
9
|
type IEmpireBase,
|
|
11
10
|
type L1BlobInputs,
|
|
12
11
|
type L1ContractsConfig,
|
|
13
|
-
type
|
|
12
|
+
type L1TxConfig,
|
|
14
13
|
type L1TxRequest,
|
|
15
14
|
MULTI_CALL_3_ADDRESS,
|
|
16
15
|
Multicall3,
|
|
@@ -27,20 +26,20 @@ import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs'
|
|
|
27
26
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
28
27
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
29
28
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
29
|
+
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
30
30
|
import type { Fr } from '@aztec/foundation/fields';
|
|
31
|
-
import { createLogger } from '@aztec/foundation/log';
|
|
31
|
+
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
32
32
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
33
33
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
34
34
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
35
35
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
36
|
-
import { CommitteeAttestation, type ValidateBlockResult } from '@aztec/stdlib/block';
|
|
36
|
+
import { CommitteeAttestation, CommitteeAttestationsAndSigners, type ValidateBlockResult } from '@aztec/stdlib/block';
|
|
37
37
|
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
38
|
-
import {
|
|
38
|
+
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
39
39
|
import type { L1PublishBlockStats } from '@aztec/stdlib/stats';
|
|
40
|
-
import {
|
|
40
|
+
import { StateReference } from '@aztec/stdlib/tx';
|
|
41
41
|
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
42
42
|
|
|
43
|
-
import pick from 'lodash.pick';
|
|
44
43
|
import { type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
|
|
45
44
|
|
|
46
45
|
import type { PublisherConfig, TxSenderConfig } from './config.js';
|
|
@@ -49,24 +48,19 @@ 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
54
|
/** State reference after the L2 block is applied. */
|
|
56
55
|
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
|
|
|
@@ -95,11 +92,11 @@ interface RequestWithExpiry {
|
|
|
95
92
|
action: Action;
|
|
96
93
|
request: L1TxRequest;
|
|
97
94
|
lastValidL2Slot: bigint;
|
|
98
|
-
gasConfig?: Pick<
|
|
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,12 +108,9 @@ export class SequencerPublisher {
|
|
|
111
108
|
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
112
109
|
protected slashingLog = createLogger('sequencer:publisher:slashing');
|
|
113
110
|
|
|
114
|
-
|
|
115
|
-
[SignalType.GOVERNANCE]: 0n,
|
|
116
|
-
[SignalType.SLASHING]: 0n,
|
|
117
|
-
};
|
|
111
|
+
protected lastActions: Partial<Record<Action, bigint>> = {};
|
|
118
112
|
|
|
119
|
-
protected log
|
|
113
|
+
protected log: Logger;
|
|
120
114
|
protected ethereumSlotDuration: bigint;
|
|
121
115
|
|
|
122
116
|
private blobSinkClient: BlobSinkClientInterface;
|
|
@@ -152,10 +146,14 @@ export class SequencerPublisher {
|
|
|
152
146
|
epochCache: EpochCache;
|
|
153
147
|
dateProvider: DateProvider;
|
|
154
148
|
metrics: SequencerPublisherMetrics;
|
|
149
|
+
lastActions: Partial<Record<Action, bigint>>;
|
|
150
|
+
log?: Logger;
|
|
155
151
|
},
|
|
156
152
|
) {
|
|
153
|
+
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
157
154
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
158
155
|
this.epochCache = deps.epochCache;
|
|
156
|
+
this.lastActions = deps.lastActions;
|
|
159
157
|
|
|
160
158
|
this.blobSinkClient =
|
|
161
159
|
deps.blobSinkClient ?? createBlobSinkClient(config, { logger: createLogger('sequencer:blob-sink:client') });
|
|
@@ -249,18 +247,21 @@ export class SequencerPublisher {
|
|
|
249
247
|
const gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
|
|
250
248
|
const txTimeoutAts = gasConfigs.map(g => g?.txTimeoutAt).filter((g): g is Date => g !== undefined);
|
|
251
249
|
const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map(g => g.getTime()))) : undefined; // earliest
|
|
252
|
-
const
|
|
250
|
+
const txConfig: RequestWithExpiry['gasConfig'] = { gasLimit, txTimeoutAt };
|
|
253
251
|
|
|
254
252
|
// Sort the requests so that proposals always go first
|
|
255
253
|
// This ensures the committee gets precomputed correctly
|
|
256
254
|
validRequests.sort((a, b) => compareActions(a.action, b.action));
|
|
257
255
|
|
|
258
256
|
try {
|
|
259
|
-
this.log.debug('Forwarding transactions', {
|
|
257
|
+
this.log.debug('Forwarding transactions', {
|
|
258
|
+
validRequests: validRequests.map(request => request.action),
|
|
259
|
+
txConfig,
|
|
260
|
+
});
|
|
260
261
|
const result = await Multicall3.forward(
|
|
261
262
|
validRequests.map(request => request.request),
|
|
262
263
|
this.l1TxUtils,
|
|
263
|
-
|
|
264
|
+
txConfig,
|
|
264
265
|
blobConfig,
|
|
265
266
|
this.rollupContract.address,
|
|
266
267
|
this.log,
|
|
@@ -285,7 +286,7 @@ export class SequencerPublisher {
|
|
|
285
286
|
|
|
286
287
|
private callbackBundledTransactions(
|
|
287
288
|
requests: RequestWithExpiry[],
|
|
288
|
-
result?: { receipt: TransactionReceipt
|
|
289
|
+
result?: { receipt: TransactionReceipt } | FormattedViemError,
|
|
289
290
|
) {
|
|
290
291
|
const actionsListStr = requests.map(r => r.action).join(', ');
|
|
291
292
|
if (result instanceof FormattedViemError) {
|
|
@@ -338,16 +339,14 @@ export class SequencerPublisher {
|
|
|
338
339
|
* It will throw if the block header is invalid.
|
|
339
340
|
* @param header - The block header to validate
|
|
340
341
|
*/
|
|
341
|
-
public async validateBlockHeader(
|
|
342
|
-
header: ProposedBlockHeader,
|
|
343
|
-
opts?: { forcePendingBlockNumber: number | undefined },
|
|
344
|
-
) {
|
|
342
|
+
public async validateBlockHeader(header: CheckpointHeader, opts?: { forcePendingBlockNumber: number | undefined }) {
|
|
345
343
|
const flags = { ignoreDA: true, ignoreSignatures: true };
|
|
346
344
|
|
|
347
345
|
const args = [
|
|
348
346
|
header.toViem(),
|
|
349
|
-
|
|
347
|
+
CommitteeAttestationsAndSigners.empty().getPackedAttestations(),
|
|
350
348
|
[], // no signers
|
|
349
|
+
Signature.empty().toViemSignature(),
|
|
351
350
|
`0x${'0'.repeat(64)}`, // 32 empty bytes
|
|
352
351
|
header.contentCommitment.blobsHash.toString(),
|
|
353
352
|
flags,
|
|
@@ -385,11 +384,11 @@ export class SequencerPublisher {
|
|
|
385
384
|
}
|
|
386
385
|
|
|
387
386
|
const { reason, block } = validationResult;
|
|
388
|
-
const blockNumber = block.
|
|
389
|
-
const logData = { ...block
|
|
387
|
+
const blockNumber = block.blockNumber;
|
|
388
|
+
const logData = { ...block, reason };
|
|
390
389
|
|
|
391
390
|
const currentBlockNumber = await this.rollupContract.getBlockNumber();
|
|
392
|
-
if (currentBlockNumber < validationResult.block.
|
|
391
|
+
if (currentBlockNumber < validationResult.block.blockNumber) {
|
|
393
392
|
this.log.verbose(
|
|
394
393
|
`Skipping block ${blockNumber} invalidation since it has already been removed from the pending chain`,
|
|
395
394
|
{ currentBlockNumber, ...logData },
|
|
@@ -398,7 +397,7 @@ export class SequencerPublisher {
|
|
|
398
397
|
}
|
|
399
398
|
|
|
400
399
|
const request = this.buildInvalidateBlockRequest(validationResult);
|
|
401
|
-
this.log.debug(`Simulating invalidate block ${blockNumber}`, logData);
|
|
400
|
+
this.log.debug(`Simulating invalidate block ${blockNumber}`, { ...logData, request });
|
|
402
401
|
|
|
403
402
|
try {
|
|
404
403
|
const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, ErrorsAbi);
|
|
@@ -443,20 +442,24 @@ export class SequencerPublisher {
|
|
|
443
442
|
}
|
|
444
443
|
|
|
445
444
|
const { block, committee, reason } = validationResult;
|
|
446
|
-
const logData = { ...block
|
|
447
|
-
this.log.debug(`Simulating invalidate block ${block.
|
|
445
|
+
const logData = { ...block, reason };
|
|
446
|
+
this.log.debug(`Simulating invalidate block ${block.blockNumber}`, logData);
|
|
447
|
+
|
|
448
|
+
const attestationsAndSigners = new CommitteeAttestationsAndSigners(
|
|
449
|
+
validationResult.attestations,
|
|
450
|
+
).getPackedAttestations();
|
|
448
451
|
|
|
449
452
|
if (reason === 'invalid-attestation') {
|
|
450
453
|
return this.rollupContract.buildInvalidateBadAttestationRequest(
|
|
451
|
-
block.
|
|
452
|
-
|
|
454
|
+
block.blockNumber,
|
|
455
|
+
attestationsAndSigners,
|
|
453
456
|
committee,
|
|
454
457
|
validationResult.invalidIndex,
|
|
455
458
|
);
|
|
456
459
|
} else if (reason === 'insufficient-attestations') {
|
|
457
460
|
return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
|
|
458
|
-
block.
|
|
459
|
-
|
|
461
|
+
block.blockNumber,
|
|
462
|
+
attestationsAndSigners,
|
|
460
463
|
committee,
|
|
461
464
|
);
|
|
462
465
|
} else {
|
|
@@ -476,48 +479,42 @@ export class SequencerPublisher {
|
|
|
476
479
|
*/
|
|
477
480
|
public async validateBlockForSubmission(
|
|
478
481
|
block: L2Block,
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
attestations: [],
|
|
482
|
-
},
|
|
482
|
+
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
483
|
+
attestationsAndSignersSignature: Signature,
|
|
483
484
|
options: { forcePendingBlockNumber?: number },
|
|
484
485
|
): Promise<bigint> {
|
|
485
486
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
486
487
|
|
|
487
488
|
// If we have no attestations, we still need to provide the empty attestations
|
|
488
489
|
// so that the committee is recalculated correctly
|
|
489
|
-
const ignoreSignatures =
|
|
490
|
+
const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
490
491
|
if (ignoreSignatures) {
|
|
491
492
|
const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber.toBigInt());
|
|
492
493
|
if (!committee) {
|
|
493
494
|
this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber.toBigInt()}`);
|
|
494
495
|
throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber.toBigInt()}`);
|
|
495
496
|
}
|
|
496
|
-
|
|
497
|
+
attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
497
498
|
CommitteeAttestation.fromAddress(committeeMember),
|
|
498
499
|
);
|
|
499
500
|
}
|
|
500
501
|
|
|
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());
|
|
502
|
+
const blobFields = block.getCheckpointBlobFields();
|
|
503
|
+
const blobs = getBlobsPerL1Block(blobFields);
|
|
504
|
+
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
508
505
|
|
|
509
506
|
const args = [
|
|
510
507
|
{
|
|
511
|
-
header: block.
|
|
508
|
+
header: block.getCheckpointHeader().toViem(),
|
|
512
509
|
archive: toHex(block.archive.root.toBuffer()),
|
|
513
510
|
stateReference: block.header.state.toViem(),
|
|
514
|
-
txHashes: block.body.txEffects.map(txEffect => txEffect.txHash.toString()),
|
|
515
511
|
oracleInput: {
|
|
516
512
|
feeAssetPriceModifier: 0n,
|
|
517
513
|
},
|
|
518
514
|
},
|
|
519
|
-
|
|
520
|
-
|
|
515
|
+
attestationsAndSigners.getPackedAttestations(),
|
|
516
|
+
attestationsAndSigners.getSigners().map(signer => signer.toString()),
|
|
517
|
+
attestationsAndSignersSignature.toViemSignature(),
|
|
521
518
|
blobInput,
|
|
522
519
|
] as const;
|
|
523
520
|
|
|
@@ -528,13 +525,14 @@ export class SequencerPublisher {
|
|
|
528
525
|
private async enqueueCastSignalHelper(
|
|
529
526
|
slotNumber: bigint,
|
|
530
527
|
timestamp: bigint,
|
|
531
|
-
signalType:
|
|
528
|
+
signalType: GovernanceSignalAction,
|
|
532
529
|
payload: EthAddress,
|
|
533
530
|
base: IEmpireBase,
|
|
534
531
|
signerAddress: EthAddress,
|
|
535
532
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
536
533
|
): Promise<boolean> {
|
|
537
|
-
if (this.
|
|
534
|
+
if (this.lastActions[signalType] && this.lastActions[signalType] === slotNumber) {
|
|
535
|
+
this.log.debug(`Skipping duplicate vote cast signal ${signalType} for slot ${slotNumber}`);
|
|
538
536
|
return false;
|
|
539
537
|
}
|
|
540
538
|
if (payload.equals(EthAddress.ZERO)) {
|
|
@@ -551,10 +549,9 @@ export class SequencerPublisher {
|
|
|
551
549
|
return false;
|
|
552
550
|
}
|
|
553
551
|
|
|
554
|
-
const cachedLastVote = this.
|
|
555
|
-
this.
|
|
556
|
-
|
|
557
|
-
const action = signalType === SignalType.GOVERNANCE ? 'governance-signal' : 'empire-slashing-signal';
|
|
552
|
+
const cachedLastVote = this.lastActions[signalType];
|
|
553
|
+
this.lastActions[signalType] = slotNumber;
|
|
554
|
+
const action = signalType;
|
|
558
555
|
|
|
559
556
|
const request = await base.createSignalRequestWithSignature(
|
|
560
557
|
payload.toString(),
|
|
@@ -597,7 +594,7 @@ export class SequencerPublisher {
|
|
|
597
594
|
`Signaling in [${action}] for ${payload} at slot ${slotNumber} in round ${round} failed`,
|
|
598
595
|
logData,
|
|
599
596
|
);
|
|
600
|
-
this.
|
|
597
|
+
this.lastActions[signalType] = cachedLastVote;
|
|
601
598
|
return false;
|
|
602
599
|
} else {
|
|
603
600
|
this.log.info(
|
|
@@ -627,7 +624,7 @@ export class SequencerPublisher {
|
|
|
627
624
|
return this.enqueueCastSignalHelper(
|
|
628
625
|
slotNumber,
|
|
629
626
|
timestamp,
|
|
630
|
-
|
|
627
|
+
'governance-signal',
|
|
631
628
|
governancePayload,
|
|
632
629
|
this.govProposerContract,
|
|
633
630
|
signerAddress,
|
|
@@ -661,7 +658,7 @@ export class SequencerPublisher {
|
|
|
661
658
|
await this.enqueueCastSignalHelper(
|
|
662
659
|
slotNumber,
|
|
663
660
|
timestamp,
|
|
664
|
-
|
|
661
|
+
'empire-slashing-signal',
|
|
665
662
|
action.payload,
|
|
666
663
|
this.slashingProposerContract,
|
|
667
664
|
signerAddress,
|
|
@@ -766,24 +763,23 @@ export class SequencerPublisher {
|
|
|
766
763
|
*/
|
|
767
764
|
public async enqueueProposeL2Block(
|
|
768
765
|
block: L2Block,
|
|
769
|
-
|
|
770
|
-
|
|
766
|
+
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
767
|
+
attestationsAndSignersSignature: Signature,
|
|
771
768
|
opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: number } = {},
|
|
772
769
|
): Promise<boolean> {
|
|
773
|
-
const
|
|
770
|
+
const checkpointHeader = block.getCheckpointHeader();
|
|
774
771
|
|
|
775
|
-
const
|
|
776
|
-
const
|
|
772
|
+
const blobFields = block.getCheckpointBlobFields();
|
|
773
|
+
const blobs = getBlobsPerL1Block(blobFields);
|
|
777
774
|
|
|
778
|
-
const blobs = await Blob.getBlobsPerBlock(block.body.toBlobFields());
|
|
779
775
|
const proposeTxArgs = {
|
|
780
|
-
header:
|
|
776
|
+
header: checkpointHeader,
|
|
781
777
|
archive: block.archive.root.toBuffer(),
|
|
782
778
|
stateReference: block.header.state,
|
|
783
779
|
body: block.body.toBuffer(),
|
|
784
780
|
blobs,
|
|
785
|
-
|
|
786
|
-
|
|
781
|
+
attestationsAndSigners,
|
|
782
|
+
attestationsAndSignersSignature,
|
|
787
783
|
};
|
|
788
784
|
|
|
789
785
|
let ts: bigint;
|
|
@@ -793,9 +789,8 @@ export class SequencerPublisher {
|
|
|
793
789
|
// This means that we can avoid the simulation issues in later checks.
|
|
794
790
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
795
791
|
// make time consistency checks break.
|
|
796
|
-
const attestationData = { digest: digest.toBuffer(), attestations: attestations ?? [] };
|
|
797
792
|
// 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,
|
|
793
|
+
ts = await this.validateBlockForSubmission(block, attestationsAndSigners, attestationsAndSignersSignature, opts);
|
|
799
794
|
} catch (err: any) {
|
|
800
795
|
this.log.error(`Block validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
801
796
|
...block.getStats(),
|
|
@@ -818,7 +813,8 @@ export class SequencerPublisher {
|
|
|
818
813
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
819
814
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(request.gasUsed) * 64) / 63)));
|
|
820
815
|
|
|
821
|
-
const
|
|
816
|
+
const { gasUsed, blockNumber } = request;
|
|
817
|
+
const logData = { gasUsed, blockNumber, gasLimit, opts };
|
|
822
818
|
this.log.verbose(`Enqueuing invalidate block request`, logData);
|
|
823
819
|
this.addRequest({
|
|
824
820
|
action: `invalidate-by-${request.reason}`,
|
|
@@ -842,16 +838,24 @@ export class SequencerPublisher {
|
|
|
842
838
|
}
|
|
843
839
|
|
|
844
840
|
private async simulateAndEnqueueRequest(
|
|
845
|
-
action:
|
|
841
|
+
action: Action,
|
|
846
842
|
request: L1TxRequest,
|
|
847
843
|
checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
|
|
848
844
|
slotNumber: bigint,
|
|
849
845
|
timestamp: bigint,
|
|
850
846
|
) {
|
|
851
847
|
const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
|
|
852
|
-
|
|
848
|
+
if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
|
|
849
|
+
this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
|
|
850
|
+
return false;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
const cachedLastActionSlot = this.lastActions[action];
|
|
854
|
+
this.lastActions[action] = slotNumber;
|
|
853
855
|
|
|
854
|
-
this.log.debug(`Simulating ${action}`, logData);
|
|
856
|
+
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
857
|
+
|
|
858
|
+
let gasUsed: bigint;
|
|
855
859
|
try {
|
|
856
860
|
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], ErrorsAbi)); // TODO(palla/slash): Check the timestamp logic
|
|
857
861
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
@@ -875,6 +879,7 @@ export class SequencerPublisher {
|
|
|
875
879
|
const success = result && result.receipt && result.receipt.status === 'success' && checkSuccess(result.receipt);
|
|
876
880
|
if (!success) {
|
|
877
881
|
this.log.warn(`Action ${action} at ${slotNumber} failed`, { ...result, ...logData });
|
|
882
|
+
this.lastActions[action] = cachedLastActionSlot;
|
|
878
883
|
} else {
|
|
879
884
|
this.log.info(`Action ${action} at ${slotNumber} succeeded`, { ...result, ...logData });
|
|
880
885
|
}
|
|
@@ -907,7 +912,7 @@ export class SequencerPublisher {
|
|
|
907
912
|
options: { forcePendingBlockNumber?: number },
|
|
908
913
|
) {
|
|
909
914
|
const kzg = Blob.getViemKzgInstance();
|
|
910
|
-
const blobInput =
|
|
915
|
+
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
911
916
|
this.log.debug('Validating blob input', { blobInput });
|
|
912
917
|
const blobEvaluationGas = await this.l1TxUtils
|
|
913
918
|
.estimateGas(
|
|
@@ -932,12 +937,7 @@ export class SequencerPublisher {
|
|
|
932
937
|
throw new Error('Failed to validate blobs');
|
|
933
938
|
});
|
|
934
939
|
|
|
935
|
-
const
|
|
936
|
-
const txHashes = encodedData.txHashes ? encodedData.txHashes.map(txHash => txHash.toString()) : [];
|
|
937
|
-
|
|
938
|
-
const signers = encodedData.attestations
|
|
939
|
-
?.filter(attest => !attest.signature.isEmpty())
|
|
940
|
-
.map(attest => attest.address.toString());
|
|
940
|
+
const signers = encodedData.attestationsAndSigners.getSigners().map(signer => signer.toString());
|
|
941
941
|
|
|
942
942
|
const args = [
|
|
943
943
|
{
|
|
@@ -948,10 +948,10 @@ export class SequencerPublisher {
|
|
|
948
948
|
// We are currently not modifying these. See #9963
|
|
949
949
|
feeAssetPriceModifier: 0n,
|
|
950
950
|
},
|
|
951
|
-
txHashes,
|
|
952
951
|
},
|
|
953
|
-
|
|
954
|
-
signers
|
|
952
|
+
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
953
|
+
signers,
|
|
954
|
+
encodedData.attestationsAndSignersSignature.toViemSignature(),
|
|
955
955
|
blobInput,
|
|
956
956
|
] as const;
|
|
957
957
|
|
|
@@ -972,13 +972,13 @@ export class SequencerPublisher {
|
|
|
972
972
|
readonly header: ViemHeader;
|
|
973
973
|
readonly archive: `0x${string}`;
|
|
974
974
|
readonly stateReference: ViemStateReference;
|
|
975
|
-
readonly txHashes: `0x${string}`[];
|
|
976
975
|
readonly oracleInput: {
|
|
977
976
|
readonly feeAssetPriceModifier: 0n;
|
|
978
977
|
};
|
|
979
978
|
},
|
|
980
979
|
ViemCommitteeAttestations,
|
|
981
|
-
`0x${string}`[],
|
|
980
|
+
`0x${string}`[], // Signers
|
|
981
|
+
ViemSignature,
|
|
982
982
|
`0x${string}`,
|
|
983
983
|
],
|
|
984
984
|
timestamp: bigint,
|
|
@@ -1084,13 +1084,16 @@ export class SequencerPublisher {
|
|
|
1084
1084
|
if (success) {
|
|
1085
1085
|
const endBlock = receipt.blockNumber;
|
|
1086
1086
|
const inclusionBlocks = Number(endBlock - startBlock);
|
|
1087
|
+
const { calldataGas, calldataSize, sender } = stats!;
|
|
1087
1088
|
const publishStats: L1PublishBlockStats = {
|
|
1088
1089
|
gasPrice: receipt.effectiveGasPrice,
|
|
1089
1090
|
gasUsed: receipt.gasUsed,
|
|
1090
1091
|
blobGasUsed: receipt.blobGasUsed ?? 0n,
|
|
1091
1092
|
blobDataGas: receipt.blobGasPrice ?? 0n,
|
|
1092
1093
|
transactionHash: receipt.transactionHash,
|
|
1093
|
-
|
|
1094
|
+
calldataGas,
|
|
1095
|
+
calldataSize,
|
|
1096
|
+
sender,
|
|
1094
1097
|
...block.getStats(),
|
|
1095
1098
|
eventName: 'rollup-published-to-l1',
|
|
1096
1099
|
blobCount: encodedData.blobs.length,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { MerkleTreeId
|
|
2
|
-
import { pick } from '@aztec/foundation/collection';
|
|
1
|
+
import { MerkleTreeId } from '@aztec/aztec.js/trees';
|
|
2
|
+
import { merge, pick } from '@aztec/foundation/collection';
|
|
3
3
|
import type { Fr } from '@aztec/foundation/fields';
|
|
4
4
|
import { createLogger } from '@aztec/foundation/log';
|
|
5
5
|
import { retryUntil } from '@aztec/foundation/retry';
|
|
6
6
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
7
|
-
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
7
|
+
import { DateProvider, Timer, elapsed } from '@aztec/foundation/timer';
|
|
8
8
|
import { getDefaultAllowedSetupFunctions } from '@aztec/p2p/msg_validators';
|
|
9
9
|
import { LightweightBlockFactory } from '@aztec/prover-client/block-factory';
|
|
10
10
|
import {
|
|
@@ -13,12 +13,12 @@ import {
|
|
|
13
13
|
PublicProcessor,
|
|
14
14
|
TelemetryPublicTxSimulator,
|
|
15
15
|
} from '@aztec/simulator/server';
|
|
16
|
-
import type { ChainConfig, SequencerConfig } from '@aztec/stdlib/config';
|
|
17
16
|
import type { ContractDataSource } from '@aztec/stdlib/contract';
|
|
18
17
|
import { type L1RollupConstants, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
19
18
|
import { Gas } from '@aztec/stdlib/gas';
|
|
20
19
|
import type {
|
|
21
20
|
BuildBlockResult,
|
|
21
|
+
FullNodeBlockBuilderConfig,
|
|
22
22
|
IFullNodeBlockBuilder,
|
|
23
23
|
MerkleTreeWriteOperations,
|
|
24
24
|
PublicProcessorLimits,
|
|
@@ -89,9 +89,14 @@ export async function buildBlock(
|
|
|
89
89
|
return res;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
const FullNodeBlockBuilderConfigKeys = [
|
|
93
|
+
'l1GenesisTime',
|
|
94
|
+
'slotDuration',
|
|
95
|
+
'l1ChainId',
|
|
96
|
+
'rollupVersion',
|
|
97
|
+
'txPublicSetupAllowList',
|
|
98
|
+
'fakeProcessingDelayPerTxMs',
|
|
99
|
+
] as const;
|
|
95
100
|
|
|
96
101
|
export class FullNodeBlockBuilder implements IFullNodeBlockBuilder {
|
|
97
102
|
constructor(
|
|
@@ -103,19 +108,11 @@ export class FullNodeBlockBuilder implements IFullNodeBlockBuilder {
|
|
|
103
108
|
) {}
|
|
104
109
|
|
|
105
110
|
public getConfig(): FullNodeBlockBuilderConfig {
|
|
106
|
-
return pick(
|
|
107
|
-
this.config,
|
|
108
|
-
'l1GenesisTime',
|
|
109
|
-
'slotDuration',
|
|
110
|
-
'l1ChainId',
|
|
111
|
-
'rollupVersion',
|
|
112
|
-
'txPublicSetupAllowList',
|
|
113
|
-
'fakeProcessingDelayPerTxMs',
|
|
114
|
-
);
|
|
111
|
+
return pick(this.config, ...FullNodeBlockBuilderConfigKeys);
|
|
115
112
|
}
|
|
116
113
|
|
|
117
|
-
public updateConfig(config: FullNodeBlockBuilderConfig) {
|
|
118
|
-
this.config = config;
|
|
114
|
+
public updateConfig(config: Partial<FullNodeBlockBuilderConfig>) {
|
|
115
|
+
this.config = merge(this.config, pick(config, ...FullNodeBlockBuilderConfigKeys));
|
|
119
116
|
}
|
|
120
117
|
|
|
121
118
|
public async makeBlockBuilderDeps(globalVariables: GlobalVariables, fork: MerkleTreeWriteOperations) {
|
|
@@ -127,10 +124,12 @@ export class FullNodeBlockBuilder implements IFullNodeBlockBuilder {
|
|
|
127
124
|
guardedFork,
|
|
128
125
|
contractsDB,
|
|
129
126
|
globalVariables,
|
|
130
|
-
/*doMerkleOperations=*/ true,
|
|
131
|
-
/*skipFeeEnforcement=*/ true,
|
|
132
|
-
/*clientInitiatedSimulation=*/ false,
|
|
133
127
|
this.telemetryClient,
|
|
128
|
+
{
|
|
129
|
+
doMerkleOperations: true,
|
|
130
|
+
skipFeeEnforcement: true,
|
|
131
|
+
clientInitiatedSimulation: false,
|
|
132
|
+
},
|
|
134
133
|
);
|
|
135
134
|
|
|
136
135
|
const processor = new PublicProcessor(
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SequencerState } from './utils.js';
|
|
2
|
+
|
|
3
|
+
export class SequencerTooSlowError extends Error {
|
|
4
|
+
constructor(
|
|
5
|
+
public readonly proposedState: SequencerState,
|
|
6
|
+
public readonly maxAllowedTime: number,
|
|
7
|
+
public readonly currentTime: number,
|
|
8
|
+
) {
|
|
9
|
+
super(
|
|
10
|
+
`Too far into slot for ${proposedState} (time into slot ${currentTime}s greater than ${maxAllowedTime}s allowance)`,
|
|
11
|
+
);
|
|
12
|
+
this.name = 'SequencerTooSlowError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class SequencerInterruptedError extends Error {
|
|
17
|
+
constructor() {
|
|
18
|
+
super(`Sequencer was interrupted`);
|
|
19
|
+
this.name = 'SequencerInterruptedError';
|
|
20
|
+
}
|
|
21
|
+
}
|