@aztec/sequencer-client 0.87.2 → 0.87.3-nightly.20250528
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 +2 -1
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +3 -0
- package/dest/global_variable_builder/global_builder.d.ts +4 -1
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +14 -2
- package/dest/index.d.ts +1 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -2
- package/dest/publisher/sequencer-publisher.d.ts +4 -4
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +14 -6
- package/dest/sequencer/sequencer.d.ts +5 -12
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +26 -34
- package/dest/sequencer/utils.d.ts +2 -2
- package/dest/sequencer/utils.d.ts.map +1 -1
- package/dest/sequencer/utils.js +6 -4
- package/package.json +26 -25
- package/src/client/sequencer-client.ts +5 -1
- package/src/global_variable_builder/global_builder.ts +16 -2
- package/src/index.ts +1 -2
- package/src/publisher/sequencer-publisher.ts +22 -13
- package/src/sequencer/sequencer.ts +32 -42
- package/src/sequencer/utils.ts +14 -6
- package/dest/slasher/factory.d.ts +0 -7
- package/dest/slasher/factory.d.ts.map +0 -1
- package/dest/slasher/factory.js +0 -8
- package/dest/slasher/index.d.ts +0 -3
- package/dest/slasher/index.d.ts.map +0 -1
- package/dest/slasher/index.js +0 -2
- package/dest/slasher/slasher_client.d.ts +0 -75
- package/dest/slasher/slasher_client.d.ts.map +0 -1
- package/dest/slasher/slasher_client.js +0 -135
- package/src/slasher/factory.ts +0 -15
- package/src/slasher/index.ts +0 -2
- package/src/slasher/slasher_client.ts +0 -199
|
@@ -20,6 +20,8 @@ import { createPublicClient, fallback, http } from 'viem';
|
|
|
20
20
|
*/
|
|
21
21
|
export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
|
|
22
22
|
private log = createLogger('sequencer:global_variable_builder');
|
|
23
|
+
private currentBaseFees: Promise<GasFees> = Promise.resolve(new GasFees(Fr.ZERO, Fr.ZERO));
|
|
24
|
+
private currentL1BlockNumber: bigint | undefined = undefined;
|
|
23
25
|
|
|
24
26
|
private readonly rollupContract: RollupContract;
|
|
25
27
|
private readonly publicClient: ViemPublicClient;
|
|
@@ -46,9 +48,9 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
|
|
|
46
48
|
|
|
47
49
|
/**
|
|
48
50
|
* Computes the "current" base fees, e.g., the price that you currently should pay to get include in the next block
|
|
49
|
-
* @returns Base fees for the
|
|
51
|
+
* @returns Base fees for the next block
|
|
50
52
|
*/
|
|
51
|
-
|
|
53
|
+
private async computeCurrentBaseFees(): Promise<GasFees> {
|
|
52
54
|
// Since this might be called in the middle of a slot where a block might have been published,
|
|
53
55
|
// we need to fetch the last block written, and estimate the earliest timestamp for the next block.
|
|
54
56
|
// The timestamp of that last block will act as a lower bound for the next block.
|
|
@@ -61,6 +63,18 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
|
|
|
61
63
|
return new GasFees(Fr.ZERO, new Fr(await this.rollupContract.getManaBaseFeeAt(timestamp, true)));
|
|
62
64
|
}
|
|
63
65
|
|
|
66
|
+
public async getCurrentBaseFees(): Promise<GasFees> {
|
|
67
|
+
// Get the current block number
|
|
68
|
+
const blockNumber = await this.publicClient.getBlockNumber();
|
|
69
|
+
|
|
70
|
+
// If the L1 block number has changed then chain a new promise to get the current base fees
|
|
71
|
+
if (this.currentL1BlockNumber === undefined || blockNumber > this.currentL1BlockNumber) {
|
|
72
|
+
this.currentL1BlockNumber = blockNumber;
|
|
73
|
+
this.currentBaseFees = this.currentBaseFees.then(() => this.computeCurrentBaseFees());
|
|
74
|
+
}
|
|
75
|
+
return this.currentBaseFees;
|
|
76
|
+
}
|
|
77
|
+
|
|
64
78
|
public async getGlobalConstantVariables(): Promise<Pick<GlobalVariables, 'chainId' | 'version'>> {
|
|
65
79
|
if (!this.chainId) {
|
|
66
80
|
this.chainId = new Fr(this.publicClient.chain.id);
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
export * from './client/index.js';
|
|
2
2
|
export * from './config.js';
|
|
3
3
|
export * from './publisher/index.js';
|
|
4
|
-
export * from './tx_validator/tx_validator_factory.js';
|
|
5
|
-
export * from './slasher/index.js';
|
|
6
4
|
export { Sequencer, SequencerState } from './sequencer/index.js';
|
|
5
|
+
export * from './tx_validator/tx_validator_factory.js';
|
|
7
6
|
|
|
8
7
|
// Used by the node to simulate public parts of transactions. Should these be moved to a shared library?
|
|
9
8
|
// ISSUE(#9832)
|
|
@@ -20,10 +20,10 @@ import {
|
|
|
20
20
|
import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs';
|
|
21
21
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
22
22
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
23
|
-
import type { Signature } from '@aztec/foundation/eth-signature';
|
|
24
23
|
import { createLogger } from '@aztec/foundation/log';
|
|
25
24
|
import { Timer } from '@aztec/foundation/timer';
|
|
26
25
|
import { ForwarderAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
26
|
+
import { CommitteeAttestation } from '@aztec/stdlib/block';
|
|
27
27
|
import { ConsensusPayload, SignatureDomainSeparator, getHashedSignaturePayload } from '@aztec/stdlib/p2p';
|
|
28
28
|
import type { L1PublishBlockStats } from '@aztec/stdlib/stats';
|
|
29
29
|
import { type ProposedBlockHeader, TxHash } from '@aztec/stdlib/tx';
|
|
@@ -48,7 +48,7 @@ type L1ProcessArgs = {
|
|
|
48
48
|
/** L2 block tx hashes */
|
|
49
49
|
txHashes: TxHash[];
|
|
50
50
|
/** Attestations */
|
|
51
|
-
attestations?:
|
|
51
|
+
attestations?: CommitteeAttestation[];
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
export enum VoteType {
|
|
@@ -74,7 +74,7 @@ interface RequestWithExpiry {
|
|
|
74
74
|
export class SequencerPublisher {
|
|
75
75
|
private interrupted = false;
|
|
76
76
|
private metrics: SequencerPublisherMetrics;
|
|
77
|
-
|
|
77
|
+
public epochCache: EpochCache;
|
|
78
78
|
private forwarderContract: ForwarderContract;
|
|
79
79
|
|
|
80
80
|
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
@@ -265,6 +265,7 @@ export class SequencerPublisher {
|
|
|
265
265
|
*/
|
|
266
266
|
public canProposeAtNextEthBlock(tipArchive: Buffer) {
|
|
267
267
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
268
|
+
|
|
268
269
|
return this.rollupContract
|
|
269
270
|
.canProposeAtNextEthBlock(tipArchive, this.getForwarderAddress().toString(), this.ethereumSlotDuration)
|
|
270
271
|
.catch(err => {
|
|
@@ -288,19 +289,29 @@ export class SequencerPublisher {
|
|
|
288
289
|
*/
|
|
289
290
|
public async validateBlockForSubmission(
|
|
290
291
|
header: ProposedBlockHeader,
|
|
291
|
-
attestationData: { digest: Buffer;
|
|
292
|
+
attestationData: { digest: Buffer; attestations: CommitteeAttestation[] } = {
|
|
292
293
|
digest: Buffer.alloc(32),
|
|
293
|
-
|
|
294
|
+
attestations: [],
|
|
294
295
|
},
|
|
295
296
|
): Promise<bigint> {
|
|
296
297
|
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
297
298
|
|
|
298
|
-
|
|
299
|
-
|
|
299
|
+
// If we have no attestations, we still need to provide the empty attestations
|
|
300
|
+
// so that the committee is recalculated correctly
|
|
301
|
+
const ignoreSignatures = attestationData.attestations.length === 0;
|
|
302
|
+
if (ignoreSignatures) {
|
|
303
|
+
const committee = await this.epochCache.getCommittee(header.slotNumber.toBigInt());
|
|
304
|
+
attestationData.attestations = committee.committee.map(committeeMember =>
|
|
305
|
+
CommitteeAttestation.fromAddress(committeeMember),
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const formattedAttestations = attestationData.attestations.map(attest => attest.toViem());
|
|
310
|
+
const flags = { ignoreDA: true, ignoreSignatures };
|
|
300
311
|
|
|
301
312
|
const args = [
|
|
302
313
|
toHex(header.toBuffer()),
|
|
303
|
-
|
|
314
|
+
formattedAttestations,
|
|
304
315
|
toHex(attestationData.digest),
|
|
305
316
|
ts,
|
|
306
317
|
toHex(header.contentCommitment.blobsHash),
|
|
@@ -405,7 +416,7 @@ export class SequencerPublisher {
|
|
|
405
416
|
*/
|
|
406
417
|
public async enqueueProposeL2Block(
|
|
407
418
|
block: L2Block,
|
|
408
|
-
attestations?:
|
|
419
|
+
attestations?: CommitteeAttestation[],
|
|
409
420
|
txHashes?: TxHash[],
|
|
410
421
|
opts: { txTimeoutAt?: Date } = {},
|
|
411
422
|
): Promise<boolean> {
|
|
@@ -431,7 +442,7 @@ export class SequencerPublisher {
|
|
|
431
442
|
// make time consistency checks break.
|
|
432
443
|
const ts = await this.validateBlockForSubmission(proposedBlockHeader, {
|
|
433
444
|
digest: digest.toBuffer(),
|
|
434
|
-
|
|
445
|
+
attestations: attestations ?? [],
|
|
435
446
|
});
|
|
436
447
|
|
|
437
448
|
this.log.debug(`Submitting propose transaction`);
|
|
@@ -486,9 +497,7 @@ export class SequencerPublisher {
|
|
|
486
497
|
throw new Error('Failed to validate blobs');
|
|
487
498
|
});
|
|
488
499
|
|
|
489
|
-
const attestations = encodedData.attestations
|
|
490
|
-
? encodedData.attestations.map(attest => attest.toViemSignature())
|
|
491
|
-
: [];
|
|
500
|
+
const attestations = encodedData.attestations ? encodedData.attestations.map(attest => attest.toViem()) : [];
|
|
492
501
|
const txHashes = encodedData.txHashes ? encodedData.txHashes.map(txHash => txHash.toString()) : [];
|
|
493
502
|
const args = [
|
|
494
503
|
{
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { type L2Block, retryUntil } from '@aztec/aztec.js';
|
|
2
2
|
import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
|
|
3
|
-
import type
|
|
3
|
+
import { FormattedViemError, type ViemPublicClient } from '@aztec/ethereum';
|
|
4
4
|
import { omit } from '@aztec/foundation/collection';
|
|
5
5
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
6
|
-
import type { Signature } from '@aztec/foundation/eth-signature';
|
|
7
6
|
import { Fr } from '@aztec/foundation/fields';
|
|
8
7
|
import { createLogger } from '@aztec/foundation/log';
|
|
9
8
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
@@ -12,8 +11,9 @@ import type { P2P } from '@aztec/p2p';
|
|
|
12
11
|
import { getDefaultAllowedSetupFunctions } from '@aztec/p2p/msg_validators';
|
|
13
12
|
import type { BlockBuilderFactory } from '@aztec/prover-client/block-builder';
|
|
14
13
|
import type { PublicProcessorFactory } from '@aztec/simulator/server';
|
|
14
|
+
import type { SlasherClient } from '@aztec/slasher';
|
|
15
15
|
import { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
16
|
-
import type { L2BlockSource } from '@aztec/stdlib/block';
|
|
16
|
+
import type { CommitteeAttestation, L2BlockSource } from '@aztec/stdlib/block';
|
|
17
17
|
import type { ContractDataSource } from '@aztec/stdlib/contract';
|
|
18
18
|
import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
|
|
19
19
|
import { Gas } from '@aztec/stdlib/gas';
|
|
@@ -40,7 +40,6 @@ import type { ValidatorClient } from '@aztec/validator-client';
|
|
|
40
40
|
|
|
41
41
|
import type { GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
|
|
42
42
|
import { type SequencerPublisher, VoteType } from '../publisher/sequencer-publisher.js';
|
|
43
|
-
import type { SlasherClient } from '../slasher/slasher_client.js';
|
|
44
43
|
import { createValidatorForBlockBuilding } from '../tx_validator/tx_validator_factory.js';
|
|
45
44
|
import type { SequencerConfig } from './config.js';
|
|
46
45
|
import { SequencerMetrics } from './metrics.js';
|
|
@@ -217,7 +216,7 @@ export class Sequencer {
|
|
|
217
216
|
this.metrics.stop();
|
|
218
217
|
await this.validatorClient?.stop();
|
|
219
218
|
await this.runningPromise?.stop();
|
|
220
|
-
this.slasherClient.stop();
|
|
219
|
+
await this.slasherClient.stop();
|
|
221
220
|
this.publisher.interrupt();
|
|
222
221
|
this.setState(SequencerState.STOPPED, 0n, true /** force */);
|
|
223
222
|
this.l1Metrics.stop();
|
|
@@ -271,10 +270,18 @@ export class Sequencer {
|
|
|
271
270
|
// If we cannot find a tip archive, assume genesis.
|
|
272
271
|
const chainTipArchive = chainTip.archive;
|
|
273
272
|
|
|
274
|
-
const slot =
|
|
273
|
+
const { slot } = this.publisher.epochCache.getEpochAndSlotInNextSlot();
|
|
275
274
|
this.metrics.observeSlotChange(slot, this.publisher.getSenderAddress().toString());
|
|
276
|
-
|
|
277
|
-
|
|
275
|
+
|
|
276
|
+
const proposerInNextSlot = await this.publisher.epochCache.getProposerInNextSlot();
|
|
277
|
+
|
|
278
|
+
// If get proposer in next slot is undefined, then there is no proposer set, and it is in free for all (sandbox) so we continue
|
|
279
|
+
// If we calculate a proposer in the next slot, and it is not us, then stop
|
|
280
|
+
if (proposerInNextSlot !== undefined && !proposerInNextSlot.equals(this.validatorClient!.getValidatorAddress())) {
|
|
281
|
+
this.log.debug(`Cannot propose block ${newBlockNumber}`, {
|
|
282
|
+
us: this.validatorClient!.getValidatorAddress(),
|
|
283
|
+
proposer: proposerInNextSlot,
|
|
284
|
+
});
|
|
278
285
|
return;
|
|
279
286
|
}
|
|
280
287
|
|
|
@@ -322,7 +329,12 @@ export class Sequencer {
|
|
|
322
329
|
const pendingTxs = this.p2pClient.iteratePendingTxs();
|
|
323
330
|
|
|
324
331
|
await this.buildBlockAndEnqueuePublish(pendingTxs, proposalHeader, newGlobalVariables).catch(err => {
|
|
325
|
-
|
|
332
|
+
if (err instanceof FormattedViemError) {
|
|
333
|
+
this.log.verbose(`Unable to build/enqueue block ${err.message}`);
|
|
334
|
+
return;
|
|
335
|
+
} else {
|
|
336
|
+
this.log.error(`Error building/enqueuing block`, err, { blockNumber: newBlockNumber, slot });
|
|
337
|
+
}
|
|
326
338
|
});
|
|
327
339
|
finishedFlushing = true;
|
|
328
340
|
} else {
|
|
@@ -374,29 +386,6 @@ export class Sequencer {
|
|
|
374
386
|
return this.publisher.getForwarderAddress();
|
|
375
387
|
}
|
|
376
388
|
|
|
377
|
-
/**
|
|
378
|
-
* Checks if we can propose at the next block and returns the slot number if we can.
|
|
379
|
-
* @param tipArchive - The archive of the previous block.
|
|
380
|
-
* @param proposalBlockNumber - The block number of the proposal.
|
|
381
|
-
* @returns The slot number if we can propose at the next block, otherwise undefined.
|
|
382
|
-
*/
|
|
383
|
-
async slotForProposal(tipArchive: Buffer, proposalBlockNumber: bigint): Promise<bigint | undefined> {
|
|
384
|
-
const result = await this.publisher.canProposeAtNextEthBlock(tipArchive);
|
|
385
|
-
|
|
386
|
-
if (!result) {
|
|
387
|
-
return undefined;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
const [slot, blockNumber] = result;
|
|
391
|
-
|
|
392
|
-
if (proposalBlockNumber !== blockNumber) {
|
|
393
|
-
const msg = `Sequencer block number mismatch. Expected ${proposalBlockNumber} but got ${blockNumber}.`;
|
|
394
|
-
this.log.warn(msg);
|
|
395
|
-
throw new Error(msg);
|
|
396
|
-
}
|
|
397
|
-
return slot;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
389
|
/**
|
|
401
390
|
* Sets the sequencer state and checks if we have enough time left in the slot to transition to the new state.
|
|
402
391
|
* @param proposedState - The new state to transition to.
|
|
@@ -446,7 +435,9 @@ export class Sequencer {
|
|
|
446
435
|
// we keep retrying until the reexecution deadline. Note that this could only happen when we are a validator,
|
|
447
436
|
// for if we are the proposer, then world-state should already be caught up, as we check this earlier.
|
|
448
437
|
await retryUntil(
|
|
449
|
-
() =>
|
|
438
|
+
() =>
|
|
439
|
+
!opts.validateOnly ||
|
|
440
|
+
this.worldState.syncImmediate(blockNumber - 1, true).then(syncedTo => syncedTo >= blockNumber - 1),
|
|
450
441
|
'sync to previous block',
|
|
451
442
|
this.timetable.getValidatorReexecTimeEnd(),
|
|
452
443
|
0.1,
|
|
@@ -455,15 +446,14 @@ export class Sequencer {
|
|
|
455
446
|
|
|
456
447
|
// NB: separating the dbs because both should update the state
|
|
457
448
|
const publicProcessorDBFork = await this.worldState.fork();
|
|
458
|
-
const orchestratorDBFork = await this.worldState.fork();
|
|
459
449
|
|
|
460
450
|
const previousBlockHeader =
|
|
461
|
-
(await this.l2BlockSource.getBlock(blockNumber - 1))?.header ??
|
|
451
|
+
(await this.l2BlockSource.getBlock(blockNumber - 1))?.header ?? publicProcessorDBFork.getInitialHeader();
|
|
462
452
|
|
|
463
453
|
try {
|
|
464
454
|
const processor = this.publicProcessorFactory.create(publicProcessorDBFork, newGlobalVariables, true);
|
|
465
455
|
const blockBuildingTimer = new Timer();
|
|
466
|
-
const blockBuilder = this.blockBuilderFactory.create(
|
|
456
|
+
const blockBuilder = this.blockBuilderFactory.create(publicProcessorDBFork);
|
|
467
457
|
await blockBuilder.startNewBlock(newGlobalVariables, l1ToL2Messages, previousBlockHeader);
|
|
468
458
|
|
|
469
459
|
// Deadline for processing depends on whether we're proposing a block
|
|
@@ -491,9 +481,6 @@ export class Sequencer {
|
|
|
491
481
|
this.txPublicSetupAllowList,
|
|
492
482
|
);
|
|
493
483
|
|
|
494
|
-
// TODO(#11000): Public processor should just handle processing, one tx at a time. It should be responsibility
|
|
495
|
-
// of the sequencer to update world state and iterate over txs. We should refactor this along with unifying the
|
|
496
|
-
// publicProcessorFork and orchestratorFork, to avoid doing tree insertions twice when building the block.
|
|
497
484
|
const proposerLimits = {
|
|
498
485
|
maxTransactions: this.maxTxsPerBlock,
|
|
499
486
|
maxBlockSize: this.maxBlockSizeInBytes,
|
|
@@ -554,7 +541,6 @@ export class Sequencer {
|
|
|
554
541
|
setTimeout(async () => {
|
|
555
542
|
try {
|
|
556
543
|
await publicProcessorDBFork.close();
|
|
557
|
-
await orchestratorDBFork.close();
|
|
558
544
|
} catch (err) {
|
|
559
545
|
// This can happen if the sequencer is stopped before we hit this timeout.
|
|
560
546
|
this.log.warn(`Error closing forks for block processing`, err);
|
|
@@ -666,7 +652,7 @@ export class Sequencer {
|
|
|
666
652
|
[Attributes.BLOCK_ARCHIVE]: block.archive.toString(),
|
|
667
653
|
[Attributes.BLOCK_TXS_COUNT]: txHashes.length,
|
|
668
654
|
}))
|
|
669
|
-
protected async collectAttestations(block: L2Block, txs: Tx[]): Promise<
|
|
655
|
+
protected async collectAttestations(block: L2Block, txs: Tx[]): Promise<CommitteeAttestation[] | undefined> {
|
|
670
656
|
// TODO(https://github.com/AztecProtocol/aztec-packages/issues/7962): inefficient to have a round trip in here - this should be cached
|
|
671
657
|
const committee = await this.publisher.getCurrentEpochCommittee();
|
|
672
658
|
|
|
@@ -728,7 +714,7 @@ export class Sequencer {
|
|
|
728
714
|
}))
|
|
729
715
|
protected async enqueuePublishL2Block(
|
|
730
716
|
block: L2Block,
|
|
731
|
-
attestations?:
|
|
717
|
+
attestations?: CommitteeAttestation[],
|
|
732
718
|
txHashes?: TxHash[],
|
|
733
719
|
): Promise<void> {
|
|
734
720
|
// Publishes new block to the network and awaits the tx to be mined
|
|
@@ -823,4 +809,8 @@ export class Sequencer {
|
|
|
823
809
|
get maxL2BlockGas(): number | undefined {
|
|
824
810
|
return this.config.maxL2BlockGas;
|
|
825
811
|
}
|
|
812
|
+
|
|
813
|
+
public getSlasherClient(): SlasherClient {
|
|
814
|
+
return this.slasherClient;
|
|
815
|
+
}
|
|
826
816
|
}
|
package/src/sequencer/utils.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
2
|
-
import {
|
|
2
|
+
import { CommitteeAttestation } from '@aztec/stdlib/block';
|
|
3
3
|
import type { BlockAttestation } from '@aztec/stdlib/p2p';
|
|
4
4
|
|
|
5
5
|
export enum SequencerState {
|
|
@@ -50,19 +50,27 @@ export function sequencerStateToNumber(state: SequencerState): number {
|
|
|
50
50
|
*
|
|
51
51
|
* @todo: perform this logic within the memory attestation store instead?
|
|
52
52
|
*/
|
|
53
|
-
export function orderAttestations(
|
|
53
|
+
export function orderAttestations(
|
|
54
|
+
attestations: BlockAttestation[],
|
|
55
|
+
orderAddresses: EthAddress[],
|
|
56
|
+
): CommitteeAttestation[] {
|
|
54
57
|
// Create a map of sender addresses to BlockAttestations
|
|
55
|
-
const attestationMap = new Map<string,
|
|
58
|
+
const attestationMap = new Map<string, CommitteeAttestation>();
|
|
56
59
|
|
|
57
60
|
for (const attestation of attestations) {
|
|
58
61
|
const sender = attestation.getSender();
|
|
59
|
-
|
|
62
|
+
if (sender) {
|
|
63
|
+
attestationMap.set(
|
|
64
|
+
sender.toString(),
|
|
65
|
+
CommitteeAttestation.fromAddressAndSignature(sender, attestation.signature),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
60
68
|
}
|
|
61
69
|
|
|
62
|
-
// Create the ordered array based on the orderAddresses, else return an empty
|
|
70
|
+
// Create the ordered array based on the orderAddresses, else return an empty attestation
|
|
63
71
|
const orderedAttestations = orderAddresses.map(address => {
|
|
64
72
|
const addressString = address.toString();
|
|
65
|
-
return attestationMap.get(addressString)
|
|
73
|
+
return attestationMap.get(addressString) || CommitteeAttestation.fromAddress(address);
|
|
66
74
|
});
|
|
67
75
|
|
|
68
76
|
return orderedAttestations;
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { L1ContractsConfig, L1ReaderConfig } from '@aztec/ethereum';
|
|
2
|
-
import type { L2BlockSourceEventEmitter } from '@aztec/stdlib/block';
|
|
3
|
-
import { type TelemetryClient } from '@aztec/telemetry-client';
|
|
4
|
-
import { SlasherClient } from './slasher_client.js';
|
|
5
|
-
import type { SlasherConfig } from './slasher_client.js';
|
|
6
|
-
export declare const createSlasherClient: (_config: SlasherConfig & L1ContractsConfig & L1ReaderConfig, l2BlockSource: L2BlockSourceEventEmitter, telemetry?: TelemetryClient) => SlasherClient;
|
|
7
|
-
//# sourceMappingURL=factory.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/slasher/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACzE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAAE,KAAK,eAAe,EAAsB,MAAM,yBAAyB,CAAC;AAEnF,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,eAAO,MAAM,mBAAmB,GAC9B,SAAS,aAAa,GAAG,iBAAiB,GAAG,cAAc,EAC3D,eAAe,yBAAyB,EACxC,YAAW,eAAsC,kBAIlD,CAAC"}
|
package/dest/slasher/factory.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
2
|
-
import { SlasherClient } from './slasher_client.js';
|
|
3
|
-
export const createSlasherClient = (_config, l2BlockSource, telemetry = getTelemetryClient())=>{
|
|
4
|
-
const config = {
|
|
5
|
-
..._config
|
|
6
|
-
};
|
|
7
|
-
return new SlasherClient(config, l2BlockSource, telemetry);
|
|
8
|
-
};
|
package/dest/slasher/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/slasher/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC"}
|
package/dest/slasher/index.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { type L1ContractsConfig, type L1ReaderConfig, type ViemPublicClient } from '@aztec/ethereum';
|
|
2
|
-
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
3
|
-
import { SlashFactoryAbi } from '@aztec/l1-artifacts';
|
|
4
|
-
import { type L2BlockId, type L2BlockSourceEvent, type L2BlockSourceEventEmitter } from '@aztec/stdlib/block';
|
|
5
|
-
import { type TelemetryClient, WithTracer } from '@aztec/telemetry-client';
|
|
6
|
-
import { type GetContractReturnType } from 'viem';
|
|
7
|
-
/**
|
|
8
|
-
* Enum defining the possible states of the Slasher client.
|
|
9
|
-
*/
|
|
10
|
-
export declare enum SlasherClientState {
|
|
11
|
-
IDLE = 0,
|
|
12
|
-
RUNNING = 1,
|
|
13
|
-
STOPPED = 2
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* The synchronization status of the Slasher client.
|
|
17
|
-
*/
|
|
18
|
-
export interface SlasherSyncState {
|
|
19
|
-
/**
|
|
20
|
-
* The current state of the slasher client.
|
|
21
|
-
*/
|
|
22
|
-
state: SlasherClientState;
|
|
23
|
-
/**
|
|
24
|
-
* The block number that the slasher client is synced to.
|
|
25
|
-
*/
|
|
26
|
-
syncedToL2Block: L2BlockId;
|
|
27
|
-
}
|
|
28
|
-
export interface SlasherConfig {
|
|
29
|
-
blockCheckIntervalMS: number;
|
|
30
|
-
blockRequestBatchSize: number;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* @notice A Hypomeiones slasher client implementation
|
|
34
|
-
*
|
|
35
|
-
* Hypomeiones: a class of individuals in ancient Sparta who were considered inferior or lesser citizens compared
|
|
36
|
-
* to the full Spartan citizens.
|
|
37
|
-
*
|
|
38
|
-
* The implementation here is less than ideal. It exists, not to be the end all be all, but to show that
|
|
39
|
-
* slashing can be done with this mechanism.
|
|
40
|
-
*
|
|
41
|
-
* The implementation is VERY brute in the sense that it only looks for pruned blocks and then tries to slash
|
|
42
|
-
* the full committee of that.
|
|
43
|
-
* If it sees a prune, it will mark the full epoch as "to be slashed".
|
|
44
|
-
*
|
|
45
|
-
* Also, it is not particularly smart around what it should if there were to be multiple slashing events.
|
|
46
|
-
*
|
|
47
|
-
* A few improvements:
|
|
48
|
-
* - Only vote on the proposal if it is possible to reach, e.g., if 6 votes are needed and only 4 slots are left don't vote.
|
|
49
|
-
* - Stop voting on a payload once it is processed.
|
|
50
|
-
* - Only vote on the proposal if it have not already been executed
|
|
51
|
-
* - Caveat, we need to fully decide if it is acceptable to have the same payload address multiple times. In the current
|
|
52
|
-
* slash factory that could mean slashing the same committee for the same error multiple times.
|
|
53
|
-
* - Decide how to deal with multiple slashing events in the same round.
|
|
54
|
-
* - This could be that multiple epochs are pruned in the same round, but with the current naive implementation we could end up
|
|
55
|
-
* slashing only the first, because the "lifetime" of the second would have passed after that vote
|
|
56
|
-
*/
|
|
57
|
-
export declare class SlasherClient extends WithTracer {
|
|
58
|
-
private config;
|
|
59
|
-
private l2BlockSource;
|
|
60
|
-
private log;
|
|
61
|
-
private slashEvents;
|
|
62
|
-
protected slashFactoryContract?: GetContractReturnType<typeof SlashFactoryAbi, ViemPublicClient>;
|
|
63
|
-
private slashingAmount;
|
|
64
|
-
constructor(config: SlasherConfig & L1ContractsConfig & L1ReaderConfig, l2BlockSource: L2BlockSourceEventEmitter, telemetry?: TelemetryClient, log?: import("@aztec/foundation/log").Logger);
|
|
65
|
-
start(): void;
|
|
66
|
-
getSlashPayload(slotNumber: bigint): Promise<EthAddress | undefined>;
|
|
67
|
-
handleBlockStreamEvent(event: L2BlockSourceEvent): Promise<void>;
|
|
68
|
-
/**
|
|
69
|
-
* Allows consumers to stop the instance of the slasher client.
|
|
70
|
-
* 'ready' will now return 'false' and the running promise that keeps the client synced is interrupted.
|
|
71
|
-
*/
|
|
72
|
-
stop(): void;
|
|
73
|
-
private handlePruneL2Blocks;
|
|
74
|
-
}
|
|
75
|
-
//# sourceMappingURL=slasher_client.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"slasher_client.d.ts","sourceRoot":"","sources":["../../src/slasher/slasher_client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EAEtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAE3D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EACL,KAAK,SAAS,EACd,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAE/B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,eAAe,EAAE,UAAU,EAAsB,MAAM,yBAAyB,CAAC;AAE/F,OAAO,EAAE,KAAK,qBAAqB,EAA+D,MAAM,MAAM,CAAC;AAE/G;;GAEG;AACH,oBAAY,kBAAkB;IAC5B,IAAI,IAAA;IACJ,OAAO,IAAA;IACP,OAAO,IAAA;CACR;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,KAAK,EAAE,kBAAkB,CAAC;IAC1B;;OAEG;IACH,eAAe,EAAE,SAAS,CAAC;CAC5B;AAED,MAAM,WAAW,aAAa;IAC5B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAQD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,aAAc,SAAQ,UAAU;IAWzC,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,aAAa;IAErB,OAAO,CAAC,GAAG;IAbb,OAAO,CAAC,WAAW,CAAoB;IAEvC,SAAS,CAAC,oBAAoB,CAAC,EAAE,qBAAqB,CAAC,OAAO,eAAe,EAAE,gBAAgB,CAAC,CAAa;IAK7G,OAAO,CAAC,cAAc,CAAc;gBAG1B,MAAM,EAAE,aAAa,GAAG,iBAAiB,GAAG,cAAc,EAC1D,aAAa,EAAE,yBAAyB,EAChD,SAAS,GAAE,eAAsC,EACzC,GAAG,yCAA0B;IAwBhC,KAAK;IAMC,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAgC1E,sBAAsB,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAavE;;;OAGG;IACI,IAAI;IAMX,OAAO,CAAC,mBAAmB;CAqB5B"}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { createEthereumChain } from '@aztec/ethereum';
|
|
2
|
-
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
3
|
-
import { createLogger } from '@aztec/foundation/log';
|
|
4
|
-
import { SlashFactoryAbi } from '@aztec/l1-artifacts';
|
|
5
|
-
import { L2BlockSourceEvents } from '@aztec/stdlib/block';
|
|
6
|
-
import { WithTracer, getTelemetryClient } from '@aztec/telemetry-client';
|
|
7
|
-
import { createPublicClient, fallback, getAddress, getContract, http } from 'viem';
|
|
8
|
-
/**
|
|
9
|
-
* Enum defining the possible states of the Slasher client.
|
|
10
|
-
*/ export var SlasherClientState = /*#__PURE__*/ function(SlasherClientState) {
|
|
11
|
-
SlasherClientState[SlasherClientState["IDLE"] = 0] = "IDLE";
|
|
12
|
-
SlasherClientState[SlasherClientState["RUNNING"] = 1] = "RUNNING";
|
|
13
|
-
SlasherClientState[SlasherClientState["STOPPED"] = 2] = "STOPPED";
|
|
14
|
-
return SlasherClientState;
|
|
15
|
-
}({});
|
|
16
|
-
/**
|
|
17
|
-
* @notice A Hypomeiones slasher client implementation
|
|
18
|
-
*
|
|
19
|
-
* Hypomeiones: a class of individuals in ancient Sparta who were considered inferior or lesser citizens compared
|
|
20
|
-
* to the full Spartan citizens.
|
|
21
|
-
*
|
|
22
|
-
* The implementation here is less than ideal. It exists, not to be the end all be all, but to show that
|
|
23
|
-
* slashing can be done with this mechanism.
|
|
24
|
-
*
|
|
25
|
-
* The implementation is VERY brute in the sense that it only looks for pruned blocks and then tries to slash
|
|
26
|
-
* the full committee of that.
|
|
27
|
-
* If it sees a prune, it will mark the full epoch as "to be slashed".
|
|
28
|
-
*
|
|
29
|
-
* Also, it is not particularly smart around what it should if there were to be multiple slashing events.
|
|
30
|
-
*
|
|
31
|
-
* A few improvements:
|
|
32
|
-
* - Only vote on the proposal if it is possible to reach, e.g., if 6 votes are needed and only 4 slots are left don't vote.
|
|
33
|
-
* - Stop voting on a payload once it is processed.
|
|
34
|
-
* - Only vote on the proposal if it have not already been executed
|
|
35
|
-
* - Caveat, we need to fully decide if it is acceptable to have the same payload address multiple times. In the current
|
|
36
|
-
* slash factory that could mean slashing the same committee for the same error multiple times.
|
|
37
|
-
* - Decide how to deal with multiple slashing events in the same round.
|
|
38
|
-
* - This could be that multiple epochs are pruned in the same round, but with the current naive implementation we could end up
|
|
39
|
-
* slashing only the first, because the "lifetime" of the second would have passed after that vote
|
|
40
|
-
*/ export class SlasherClient extends WithTracer {
|
|
41
|
-
config;
|
|
42
|
-
l2BlockSource;
|
|
43
|
-
log;
|
|
44
|
-
slashEvents;
|
|
45
|
-
slashFactoryContract;
|
|
46
|
-
// The amount to slash for a prune.
|
|
47
|
-
// Note that we set it to 0, such that no actual slashing will happen, but the event will be fired,
|
|
48
|
-
// showing that the slashing mechanism is working.
|
|
49
|
-
slashingAmount;
|
|
50
|
-
constructor(config, l2BlockSource, telemetry = getTelemetryClient(), log = createLogger('slasher')){
|
|
51
|
-
super(telemetry, 'slasher'), this.config = config, this.l2BlockSource = l2BlockSource, this.log = log, this.slashEvents = [], this.slashFactoryContract = undefined, this.slashingAmount = 0n;
|
|
52
|
-
if (config.l1Contracts.slashFactoryAddress && !config.l1Contracts.slashFactoryAddress.equals(EthAddress.ZERO)) {
|
|
53
|
-
const chain = createEthereumChain(config.l1RpcUrls, config.l1ChainId);
|
|
54
|
-
const publicClient = createPublicClient({
|
|
55
|
-
chain: chain.chainInfo,
|
|
56
|
-
transport: fallback(chain.rpcUrls.map((url)=>http(url))),
|
|
57
|
-
pollingInterval: config.viemPollingIntervalMS
|
|
58
|
-
});
|
|
59
|
-
this.slashFactoryContract = getContract({
|
|
60
|
-
address: getAddress(config.l1Contracts.slashFactoryAddress.toString()),
|
|
61
|
-
abi: SlashFactoryAbi,
|
|
62
|
-
client: publicClient
|
|
63
|
-
});
|
|
64
|
-
} else {
|
|
65
|
-
this.log.warn('No slash factory address found, slashing will not be enabled');
|
|
66
|
-
}
|
|
67
|
-
this.log.info(`Slasher client initialized`);
|
|
68
|
-
}
|
|
69
|
-
start() {
|
|
70
|
-
this.log.info('Starting Slasher client...');
|
|
71
|
-
this.l2BlockSource.on(L2BlockSourceEvents.L2PruneDetected, this.handlePruneL2Blocks.bind(this));
|
|
72
|
-
}
|
|
73
|
-
// This is where we should put a bunch of the improvements mentioned earlier.
|
|
74
|
-
async getSlashPayload(slotNumber) {
|
|
75
|
-
if (!this.slashFactoryContract) {
|
|
76
|
-
return undefined;
|
|
77
|
-
}
|
|
78
|
-
// As long as the slot is greater than the lifetime, we want to keep deleting the first element
|
|
79
|
-
// since it will not make sense to include anymore.
|
|
80
|
-
while(this.slashEvents.length > 0 && this.slashEvents[0].lifetime < slotNumber){
|
|
81
|
-
this.slashEvents.shift();
|
|
82
|
-
}
|
|
83
|
-
if (this.slashEvents.length == 0) {
|
|
84
|
-
return undefined;
|
|
85
|
-
}
|
|
86
|
-
const slashEvent = this.slashEvents[0];
|
|
87
|
-
const [payloadAddress, isDeployed] = await this.slashFactoryContract.read.getAddressAndIsDeployed([
|
|
88
|
-
slashEvent.epoch,
|
|
89
|
-
slashEvent.amount
|
|
90
|
-
]);
|
|
91
|
-
if (!isDeployed) {
|
|
92
|
-
// The proposal cannot be executed until it is deployed
|
|
93
|
-
this.log.verbose(`Voting on not yet deployed payload for epoch ${slashEvent.epoch} and amount ${slashEvent.amount} at: ${payloadAddress}`);
|
|
94
|
-
}
|
|
95
|
-
return EthAddress.fromString(payloadAddress);
|
|
96
|
-
}
|
|
97
|
-
handleBlockStreamEvent(event) {
|
|
98
|
-
this.log.debug(`Handling block stream event ${event.type}`);
|
|
99
|
-
switch(event.type){
|
|
100
|
-
case L2BlockSourceEvents.L2PruneDetected:
|
|
101
|
-
this.handlePruneL2Blocks(event);
|
|
102
|
-
break;
|
|
103
|
-
default:
|
|
104
|
-
{
|
|
105
|
-
break;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return Promise.resolve();
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Allows consumers to stop the instance of the slasher client.
|
|
112
|
-
* 'ready' will now return 'false' and the running promise that keeps the client synced is interrupted.
|
|
113
|
-
*/ stop() {
|
|
114
|
-
this.log.debug('Stopping Slasher client...');
|
|
115
|
-
this.l2BlockSource.removeListener(L2BlockSourceEvents.L2PruneDetected, this.handlePruneL2Blocks.bind(this));
|
|
116
|
-
this.log.info('Slasher client stopped.');
|
|
117
|
-
}
|
|
118
|
-
handlePruneL2Blocks(event) {
|
|
119
|
-
// We do not try to slash if the penalty is 0
|
|
120
|
-
if (this.slashingAmount == 0n) {
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
const { slotNumber, epochNumber } = event;
|
|
124
|
-
this.log.info(`Detected chain prune. Punishing the validators at epoch ${epochNumber}`, event);
|
|
125
|
-
// Set the lifetime such that we have a full round that we could vote throughout.
|
|
126
|
-
const slotsIntoRound = slotNumber % BigInt(this.config.slashingRoundSize);
|
|
127
|
-
const toNext = slotsIntoRound == 0n ? 0n : BigInt(this.config.slashingRoundSize) - slotsIntoRound;
|
|
128
|
-
const lifetime = slotNumber + toNext + BigInt(this.config.slashingRoundSize);
|
|
129
|
-
this.slashEvents.push({
|
|
130
|
-
epoch: epochNumber,
|
|
131
|
-
amount: this.slashingAmount,
|
|
132
|
-
lifetime
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
}
|
package/src/slasher/factory.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { L1ContractsConfig, L1ReaderConfig } from '@aztec/ethereum';
|
|
2
|
-
import type { L2BlockSourceEventEmitter } from '@aztec/stdlib/block';
|
|
3
|
-
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
4
|
-
|
|
5
|
-
import { SlasherClient } from './slasher_client.js';
|
|
6
|
-
import type { SlasherConfig } from './slasher_client.js';
|
|
7
|
-
|
|
8
|
-
export const createSlasherClient = (
|
|
9
|
-
_config: SlasherConfig & L1ContractsConfig & L1ReaderConfig,
|
|
10
|
-
l2BlockSource: L2BlockSourceEventEmitter,
|
|
11
|
-
telemetry: TelemetryClient = getTelemetryClient(),
|
|
12
|
-
) => {
|
|
13
|
-
const config = { ..._config };
|
|
14
|
-
return new SlasherClient(config, l2BlockSource, telemetry);
|
|
15
|
-
};
|
package/src/slasher/index.ts
DELETED