@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.
Files changed (37) hide show
  1. package/dest/client/sequencer-client.d.ts +2 -1
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +3 -0
  4. package/dest/global_variable_builder/global_builder.d.ts +4 -1
  5. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  6. package/dest/global_variable_builder/global_builder.js +14 -2
  7. package/dest/index.d.ts +1 -2
  8. package/dest/index.d.ts.map +1 -1
  9. package/dest/index.js +1 -2
  10. package/dest/publisher/sequencer-publisher.d.ts +4 -4
  11. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  12. package/dest/publisher/sequencer-publisher.js +14 -6
  13. package/dest/sequencer/sequencer.d.ts +5 -12
  14. package/dest/sequencer/sequencer.d.ts.map +1 -1
  15. package/dest/sequencer/sequencer.js +26 -34
  16. package/dest/sequencer/utils.d.ts +2 -2
  17. package/dest/sequencer/utils.d.ts.map +1 -1
  18. package/dest/sequencer/utils.js +6 -4
  19. package/package.json +26 -25
  20. package/src/client/sequencer-client.ts +5 -1
  21. package/src/global_variable_builder/global_builder.ts +16 -2
  22. package/src/index.ts +1 -2
  23. package/src/publisher/sequencer-publisher.ts +22 -13
  24. package/src/sequencer/sequencer.ts +32 -42
  25. package/src/sequencer/utils.ts +14 -6
  26. package/dest/slasher/factory.d.ts +0 -7
  27. package/dest/slasher/factory.d.ts.map +0 -1
  28. package/dest/slasher/factory.js +0 -8
  29. package/dest/slasher/index.d.ts +0 -3
  30. package/dest/slasher/index.d.ts.map +0 -1
  31. package/dest/slasher/index.js +0 -2
  32. package/dest/slasher/slasher_client.d.ts +0 -75
  33. package/dest/slasher/slasher_client.d.ts.map +0 -1
  34. package/dest/slasher/slasher_client.js +0 -135
  35. package/src/slasher/factory.ts +0 -15
  36. package/src/slasher/index.ts +0 -2
  37. 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 expected next block
51
+ * @returns Base fees for the next block
50
52
  */
51
- public async getCurrentBaseFees(): Promise<GasFees> {
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?: Signature[];
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
- private epochCache: EpochCache;
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; signatures: Signature[] } = {
292
+ attestationData: { digest: Buffer; attestations: CommitteeAttestation[] } = {
292
293
  digest: Buffer.alloc(32),
293
- signatures: [],
294
+ attestations: [],
294
295
  },
295
296
  ): Promise<bigint> {
296
297
  const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
297
298
 
298
- const formattedSignatures = attestationData.signatures.map(attest => attest.toViemSignature());
299
- const flags = { ignoreDA: true, ignoreSignatures: formattedSignatures.length == 0 };
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
- formattedSignatures,
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?: Signature[],
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
- signatures: attestations ?? [],
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 { ViemPublicClient } from '@aztec/ethereum';
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 = await this.slotForProposal(chainTipArchive.toBuffer(), BigInt(newBlockNumber));
273
+ const { slot } = this.publisher.epochCache.getEpochAndSlotInNextSlot();
275
274
  this.metrics.observeSlotChange(slot, this.publisher.getSenderAddress().toString());
276
- if (!slot) {
277
- this.log.debug(`Cannot propose block ${newBlockNumber}`);
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
- this.log.error(`Error building/enqueuing block`, err, { blockNumber: newBlockNumber, slot });
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
- () => this.worldState.syncImmediate(blockNumber - 1, true).then(syncedTo => syncedTo >= blockNumber - 1),
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 ?? orchestratorDBFork.getInitialHeader();
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(orchestratorDBFork);
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<Signature[] | undefined> {
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?: Signature[],
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
  }
@@ -1,5 +1,5 @@
1
1
  import type { EthAddress } from '@aztec/foundation/eth-address';
2
- import { Signature } from '@aztec/foundation/eth-signature';
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(attestations: BlockAttestation[], orderAddresses: EthAddress[]): Signature[] {
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, BlockAttestation>();
58
+ const attestationMap = new Map<string, CommitteeAttestation>();
56
59
 
57
60
  for (const attestation of attestations) {
58
61
  const sender = attestation.getSender();
59
- attestationMap.set(sender.toString(), attestation);
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 signature
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)?.signature || Signature.empty();
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"}
@@ -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
- };
@@ -1,3 +0,0 @@
1
- export * from './slasher_client.js';
2
- export { createSlasherClient } from './factory.js';
3
- //# sourceMappingURL=index.d.ts.map
@@ -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"}
@@ -1,2 +0,0 @@
1
- export * from './slasher_client.js';
2
- export { createSlasherClient } from './factory.js';
@@ -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
- }
@@ -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
- };
@@ -1,2 +0,0 @@
1
- export * from './slasher_client.js';
2
- export { createSlasherClient } from './factory.js';