@aztec/sequencer-client 0.68.1 → 0.69.0-devnet

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.
@@ -16,24 +16,18 @@ import {
16
16
  type Proof,
17
17
  } from '@aztec/circuits.js';
18
18
  import { type FeeRecipient, type RootRollupPublicInputs } from '@aztec/circuits.js/rollup';
19
- import {
20
- type EthereumChain,
21
- type L1ContractsConfig,
22
- L1TxUtils,
23
- type L1TxUtilsConfig,
24
- createEthereumChain,
25
- } from '@aztec/ethereum';
19
+ import { type EthereumChain, type L1ContractsConfig, L1TxUtils, createEthereumChain } from '@aztec/ethereum';
26
20
  import { makeTuple } from '@aztec/foundation/array';
27
21
  import { toHex } from '@aztec/foundation/bigint-buffer';
28
22
  import { Blob } from '@aztec/foundation/blob';
29
23
  import { areArraysEqual, compactArray, times } from '@aztec/foundation/collection';
30
24
  import { type Signature } from '@aztec/foundation/eth-signature';
31
25
  import { Fr } from '@aztec/foundation/fields';
32
- import { createLogger } from '@aztec/foundation/log';
26
+ import { type Logger, createLogger } from '@aztec/foundation/log';
33
27
  import { type Tuple, serializeToBuffer } from '@aztec/foundation/serialize';
34
28
  import { InterruptibleSleep } from '@aztec/foundation/sleep';
35
29
  import { Timer } from '@aztec/foundation/timer';
36
- import { ExtRollupLibAbi, GovernanceProposerAbi, LeonidasLibAbi, RollupAbi } from '@aztec/l1-artifacts';
30
+ import { EmpireBaseAbi, ExtRollupLibAbi, LeonidasLibAbi, RollupAbi, SlasherAbi } from '@aztec/l1-artifacts';
37
31
  import { type TelemetryClient } from '@aztec/telemetry-client';
38
32
 
39
33
  import pick from 'lodash.pick';
@@ -135,6 +129,13 @@ export type L1SubmitEpochProofArgs = {
135
129
  proof: Proof;
136
130
  };
137
131
 
132
+ export enum VoteType {
133
+ GOVERNANCE,
134
+ SLASHING,
135
+ }
136
+
137
+ type GetSlashPayloadCallBack = (slotNumber: bigint) => Promise<EthAddress | undefined>;
138
+
138
139
  /**
139
140
  * Publishes L2 blocks to L1. This implementation does *not* retry a transaction in
140
141
  * the event of network congestion, but should work for local development.
@@ -149,20 +150,25 @@ export class L1Publisher {
149
150
  private interrupted = false;
150
151
  private metrics: L1PublisherMetrics;
151
152
 
152
- private payload: EthAddress = EthAddress.ZERO;
153
- private myLastVote: bigint = 0n;
153
+ protected governanceLog = createLogger('sequencer:publisher:governance');
154
+ protected governanceProposerAddress?: EthAddress;
155
+ private governancePayload: EthAddress = EthAddress.ZERO;
156
+
157
+ protected slashingLog = createLogger('sequencer:publisher:slashing');
158
+ protected slashingProposerAddress?: EthAddress;
159
+ private getSlashPayload?: GetSlashPayloadCallBack = undefined;
160
+
161
+ private myLastVotes: Record<VoteType, bigint> = {
162
+ [VoteType.GOVERNANCE]: 0n,
163
+ [VoteType.SLASHING]: 0n,
164
+ };
154
165
 
155
166
  protected log = createLogger('sequencer:publisher');
156
- protected governanceLog = createLogger('sequencer:publisher:governance');
157
167
 
158
168
  protected rollupContract: GetContractReturnType<
159
169
  typeof RollupAbi,
160
170
  WalletClient<HttpTransport, Chain, PrivateKeyAccount>
161
171
  >;
162
- protected governanceProposerContract?: GetContractReturnType<
163
- typeof GovernanceProposerAbi,
164
- WalletClient<HttpTransport, Chain, PrivateKeyAccount>
165
- > = undefined;
166
172
 
167
173
  protected publicClient: PublicClient<HttpTransport, Chain>;
168
174
  protected walletClient: WalletClient<HttpTransport, Chain, PrivateKeyAccount>;
@@ -178,7 +184,7 @@ export class L1Publisher {
178
184
  private readonly l1TxUtils: L1TxUtils;
179
185
 
180
186
  constructor(
181
- config: TxSenderConfig & PublisherConfig & Pick<L1ContractsConfig, 'ethereumSlotDuration'> & L1TxUtilsConfig,
187
+ config: TxSenderConfig & PublisherConfig & Pick<L1ContractsConfig, 'ethereumSlotDuration'>,
182
188
  client: TelemetryClient,
183
189
  ) {
184
190
  this.sleepTimeMs = config?.l1PublishRetryIntervalMS ?? 60_000;
@@ -205,16 +211,31 @@ export class L1Publisher {
205
211
  });
206
212
 
207
213
  if (l1Contracts.governanceProposerAddress) {
208
- this.governanceProposerContract = getContract({
209
- address: getAddress(l1Contracts.governanceProposerAddress.toString()),
210
- abi: GovernanceProposerAbi,
211
- client: this.walletClient,
212
- });
214
+ this.governanceProposerAddress = EthAddress.fromString(l1Contracts.governanceProposerAddress.toString());
213
215
  }
214
216
 
215
217
  this.l1TxUtils = new L1TxUtils(this.publicClient, this.walletClient, this.log, config);
216
218
  }
217
219
 
220
+ public registerSlashPayloadGetter(callback: GetSlashPayloadCallBack) {
221
+ this.getSlashPayload = callback;
222
+ }
223
+
224
+ private async getSlashingProposerAddress() {
225
+ if (this.slashingProposerAddress) {
226
+ return this.slashingProposerAddress;
227
+ }
228
+
229
+ const slasherAddress = await this.rollupContract.read.SLASHER();
230
+ const slasher = getContract({
231
+ address: getAddress(slasherAddress.toString()),
232
+ abi: SlasherAbi,
233
+ client: this.walletClient,
234
+ });
235
+ this.slashingProposerAddress = EthAddress.fromString(await slasher.read.PROPOSER());
236
+ return this.slashingProposerAddress;
237
+ }
238
+
218
239
  get publisherAddress() {
219
240
  return this.account.address;
220
241
  }
@@ -230,12 +251,12 @@ export class L1Publisher {
230
251
  });
231
252
  }
232
253
 
233
- public getPayLoad() {
234
- return this.payload;
254
+ public getGovernancePayload() {
255
+ return this.governancePayload;
235
256
  }
236
257
 
237
- public setPayload(payload: EthAddress) {
238
- this.payload = payload;
258
+ public setGovernancePayload(payload: EthAddress) {
259
+ this.governancePayload = payload;
239
260
  }
240
261
 
241
262
  public getSenderAddress(): EthAddress {
@@ -450,68 +471,106 @@ export class L1Publisher {
450
471
  calldataGas: getCalldataGasUsage(calldata),
451
472
  };
452
473
  }
453
-
454
- public async castVote(slotNumber: bigint, timestamp: bigint): Promise<boolean> {
455
- if (this.payload.equals(EthAddress.ZERO)) {
474
+ public async castVote(slotNumber: bigint, timestamp: bigint, voteType: VoteType) {
475
+ // @todo This function can be optimized by doing some of the computations locally instead of calling the L1 contracts
476
+ if (this.myLastVotes[voteType] >= slotNumber) {
456
477
  return false;
457
478
  }
458
479
 
459
- if (!this.governanceProposerContract) {
460
- return false;
461
- }
480
+ const voteConfig = async (): Promise<
481
+ { payload: EthAddress; voteContractAddress: EthAddress; logger: Logger } | undefined
482
+ > => {
483
+ if (voteType === VoteType.GOVERNANCE) {
484
+ if (this.governancePayload.equals(EthAddress.ZERO)) {
485
+ return undefined;
486
+ }
487
+ if (!this.governanceProposerAddress) {
488
+ return undefined;
489
+ }
490
+ return {
491
+ payload: this.governancePayload,
492
+ voteContractAddress: this.governanceProposerAddress,
493
+ logger: this.governanceLog,
494
+ };
495
+ } else if (voteType === VoteType.SLASHING) {
496
+ if (!this.getSlashPayload) {
497
+ return undefined;
498
+ }
499
+ const slashingProposerAddress = await this.getSlashingProposerAddress();
500
+ if (!slashingProposerAddress) {
501
+ return undefined;
502
+ }
503
+
504
+ const slashPayload = await this.getSlashPayload(slotNumber);
505
+
506
+ if (!slashPayload) {
507
+ return undefined;
508
+ }
509
+
510
+ return {
511
+ payload: slashPayload,
512
+ voteContractAddress: slashingProposerAddress,
513
+ logger: this.slashingLog,
514
+ };
515
+ } else {
516
+ throw new Error('Invalid vote type');
517
+ }
518
+ };
462
519
 
463
- if (this.myLastVote >= slotNumber) {
520
+ const vConfig = await voteConfig();
521
+
522
+ if (!vConfig) {
464
523
  return false;
465
524
  }
466
525
 
467
- // @todo This can be optimized A LOT by doing the computation instead of making calls to L1, but it is very convenient
468
- // for when we keep changing the values and don't want to have multiple versions of the same logic implemented.
526
+ const { payload, voteContractAddress, logger } = vConfig;
527
+
528
+ const voteContract = getContract({
529
+ address: getAddress(voteContractAddress.toString()),
530
+ abi: EmpireBaseAbi,
531
+ client: this.walletClient,
532
+ });
469
533
 
470
534
  const [proposer, roundNumber] = await Promise.all([
471
535
  this.rollupContract.read.getProposerAt([timestamp]),
472
- this.governanceProposerContract.read.computeRound([slotNumber]),
536
+ voteContract.read.computeRound([slotNumber]),
473
537
  ]);
474
538
 
475
539
  if (proposer.toLowerCase() !== this.account.address.toLowerCase()) {
476
540
  return false;
477
541
  }
478
542
 
479
- const [slotForLastVote] = await this.governanceProposerContract.read.rounds([
480
- this.rollupContract.address,
481
- roundNumber,
482
- ]);
543
+ const [slotForLastVote] = await voteContract.read.rounds([this.rollupContract.address, roundNumber]);
483
544
 
484
545
  if (slotForLastVote >= slotNumber) {
485
546
  return false;
486
547
  }
487
548
 
488
- // Storing these early such that a quick entry again would not send another tx,
489
- // revert the state if there is a failure.
490
- const cachedMyLastVote = this.myLastVote;
491
- this.myLastVote = slotNumber;
492
-
493
- this.governanceLog.verbose(`Casting vote for ${this.payload}`);
549
+ const cachedMyLastVote = this.myLastVotes[voteType];
550
+ this.myLastVotes[voteType] = slotNumber;
494
551
 
495
552
  let txHash;
496
553
  try {
497
- txHash = await this.governanceProposerContract.write.vote([this.payload.toString()], { account: this.account });
554
+ txHash = await voteContract.write.vote([payload.toString()], {
555
+ account: this.account,
556
+ });
498
557
  } catch (err) {
499
558
  const msg = prettyLogViemErrorMsg(err);
500
- this.governanceLog.error(`Failed to vote`, msg);
501
- this.myLastVote = cachedMyLastVote;
559
+ logger.error(`Failed to vote`, msg);
560
+ this.myLastVotes[voteType] = cachedMyLastVote;
502
561
  return false;
503
562
  }
504
563
 
505
564
  if (txHash) {
506
565
  const receipt = await this.getTransactionReceipt(txHash);
507
566
  if (!receipt) {
508
- this.governanceLog.warn(`Failed to get receipt for tx ${txHash}`);
509
- this.myLastVote = cachedMyLastVote;
567
+ logger.warn(`Failed to get receipt for tx ${txHash}`);
568
+ this.myLastVotes[voteType] = cachedMyLastVote;
510
569
  return false;
511
570
  }
512
571
  }
513
572
 
514
- this.governanceLog.info(`Cast vote for ${this.payload}`);
573
+ logger.info(`Cast vote for ${payload}`);
515
574
  return true;
516
575
  }
517
576
 
@@ -36,8 +36,9 @@ import { Attributes, type TelemetryClient, type Tracer, trackSpan } from '@aztec
36
36
  import { type ValidatorClient } from '@aztec/validator-client';
37
37
 
38
38
  import { type GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
39
- import { type L1Publisher } from '../publisher/l1-publisher.js';
39
+ import { type L1Publisher, VoteType } from '../publisher/l1-publisher.js';
40
40
  import { prettyLogViemErrorMsg } from '../publisher/utils.js';
41
+ import { type SlasherClient } from '../slasher/slasher_client.js';
41
42
  import { type TxValidatorFactory } from '../tx_validator/tx_validator_factory.js';
42
43
  import { getDefaultAllowedSetupFunctions } from './allowed.js';
43
44
  import { type SequencerConfig } from './config.js';
@@ -60,7 +61,7 @@ export class SequencerTooSlowError extends Error {
60
61
  public readonly currentTime: number,
61
62
  ) {
62
63
  super(
63
- `Too far into slot to transition to ${proposedState}. max allowed: ${maxAllowedTime}s, time into slot: ${currentTime}s`,
64
+ `Too far into slot to transition to ${proposedState} (max allowed: ${maxAllowedTime}s, time into slot: ${currentTime}s)`,
64
65
  );
65
66
  this.name = 'SequencerTooSlowError';
66
67
  }
@@ -106,6 +107,7 @@ export class Sequencer {
106
107
  private globalsBuilder: GlobalVariableBuilder,
107
108
  private p2pClient: P2P,
108
109
  private worldState: WorldStateSynchronizer,
110
+ private slasherClient: SlasherClient,
109
111
  private blockBuilderFactory: BlockBuilderFactory,
110
112
  private l2BlockSource: L2BlockSource,
111
113
  private l1ToL2MessageSource: L1ToL2MessageSource,
@@ -122,6 +124,9 @@ export class Sequencer {
122
124
 
123
125
  // Register the block builder with the validator client for re-execution
124
126
  this.validatorClient?.registerBlockBuilder(this.buildBlock.bind(this));
127
+
128
+ // Register the slasher on the publisher to fetch slashing payloads
129
+ this.publisher.registerSlashPayloadGetter(this.slasherClient.getSlashPayload.bind(this.slasherClient));
125
130
  }
126
131
 
127
132
  get tracer(): Tracer {
@@ -157,7 +162,7 @@ export class Sequencer {
157
162
  this.maxBlockSizeInBytes = config.maxBlockSizeInBytes;
158
163
  }
159
164
  if (config.governanceProposerPayload) {
160
- this.publisher.setPayload(config.governanceProposerPayload);
165
+ this.publisher.setGovernancePayload(config.governanceProposerPayload);
161
166
  }
162
167
  if (config.maxL1TxInclusionTimeIntoSlot !== undefined) {
163
168
  this.maxL1TxInclusionTimeIntoSlot = config.maxL1TxInclusionTimeIntoSlot;
@@ -172,10 +177,10 @@ export class Sequencer {
172
177
 
173
178
  private setTimeTable() {
174
179
  // How late into the slot can we be to start working
175
- const initialTime = 1;
180
+ const initialTime = 2;
176
181
 
177
182
  // How long it takes to validate the txs collected and get ready to start building
178
- const blockPrepareTime = 2;
183
+ const blockPrepareTime = 1;
179
184
 
180
185
  // How long it takes to for attestations to travel across the p2p layer.
181
186
  const attestationPropagationTime = 2;
@@ -245,6 +250,7 @@ export class Sequencer {
245
250
  this.log.debug(`Stopping sequencer`);
246
251
  await this.validatorClient?.stop();
247
252
  await this.runningPromise?.stop();
253
+ await this.slasherClient?.stop();
248
254
  this.publisher.interrupt();
249
255
  this.setState(SequencerState.STOPPED, 0n, true /** force */);
250
256
  this.log.info('Stopped sequencer');
@@ -314,7 +320,8 @@ export class Sequencer {
314
320
  slot,
315
321
  );
316
322
 
317
- void this.publisher.castVote(slot, newGlobalVariables.timestamp.toBigInt());
323
+ void this.publisher.castVote(slot, newGlobalVariables.timestamp.toBigInt(), VoteType.GOVERNANCE);
324
+ void this.publisher.castVote(slot, newGlobalVariables.timestamp.toBigInt(), VoteType.SLASHING);
318
325
 
319
326
  if (!this.shouldProposeBlock(historicalHeader, {})) {
320
327
  return;
@@ -430,7 +437,7 @@ export class Sequencer {
430
437
  const bufferSeconds = maxAllowedTime - secondsIntoSlot;
431
438
 
432
439
  if (bufferSeconds < 0) {
433
- this.log.warn(`Too far into slot to transition to ${proposedState}`, { maxAllowedTime, secondsIntoSlot });
440
+ this.log.debug(`Too far into slot to transition to ${proposedState}`, { maxAllowedTime, secondsIntoSlot });
434
441
  return false;
435
442
  }
436
443
 
@@ -0,0 +1,22 @@
1
+ import type { L2BlockSource } from '@aztec/circuit-types';
2
+ import { type L1ContractsConfig, type L1ReaderConfig } from '@aztec/ethereum';
3
+ import { createLogger } from '@aztec/foundation/log';
4
+ import { type AztecKVStore } from '@aztec/kv-store';
5
+ import { type DataStoreConfig } from '@aztec/kv-store/config';
6
+ import { createStore } from '@aztec/kv-store/lmdb';
7
+ import { type TelemetryClient } from '@aztec/telemetry-client';
8
+ import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
9
+
10
+ import { SlasherClient } from './slasher_client.js';
11
+ import { type SlasherConfig } from './slasher_client.js';
12
+
13
+ export const createSlasherClient = async (
14
+ _config: SlasherConfig & DataStoreConfig & L1ContractsConfig & L1ReaderConfig,
15
+ l2BlockSource: L2BlockSource,
16
+ telemetry: TelemetryClient = new NoopTelemetryClient(),
17
+ deps: { store?: AztecKVStore } = {},
18
+ ) => {
19
+ const config = { ..._config };
20
+ const store = deps.store ?? (await createStore('slasher', config, createLogger('slasher:lmdb')));
21
+ return new SlasherClient(config, store, l2BlockSource, telemetry);
22
+ };
@@ -0,0 +1,2 @@
1
+ export * from './slasher_client.js';
2
+ export { createSlasherClient } from './factory.js';