@aztec/validator-client 3.0.0-devnet.5 → 3.0.0-devnet.6-patch.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dest/block_proposal_handler.d.ts +7 -6
  2. package/dest/block_proposal_handler.d.ts.map +1 -1
  3. package/dest/block_proposal_handler.js +17 -13
  4. package/dest/config.d.ts +1 -1
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +5 -0
  7. package/dest/duties/validation_service.d.ts +4 -4
  8. package/dest/duties/validation_service.d.ts.map +1 -1
  9. package/dest/duties/validation_service.js +7 -8
  10. package/dest/factory.d.ts +1 -1
  11. package/dest/index.d.ts +1 -1
  12. package/dest/key_store/index.d.ts +1 -1
  13. package/dest/key_store/interface.d.ts +1 -1
  14. package/dest/key_store/local_key_store.d.ts +1 -1
  15. package/dest/key_store/local_key_store.d.ts.map +1 -1
  16. package/dest/key_store/local_key_store.js +1 -1
  17. package/dest/key_store/node_keystore_adapter.d.ts +1 -1
  18. package/dest/key_store/node_keystore_adapter.d.ts.map +1 -1
  19. package/dest/key_store/web3signer_key_store.d.ts +1 -7
  20. package/dest/key_store/web3signer_key_store.d.ts.map +1 -1
  21. package/dest/key_store/web3signer_key_store.js +7 -8
  22. package/dest/metrics.d.ts +3 -3
  23. package/dest/metrics.d.ts.map +1 -1
  24. package/dest/metrics.js +6 -4
  25. package/dest/validator.d.ts +7 -6
  26. package/dest/validator.d.ts.map +1 -1
  27. package/dest/validator.js +72 -38
  28. package/package.json +14 -14
  29. package/src/block_proposal_handler.ts +26 -23
  30. package/src/config.ts +6 -0
  31. package/src/duties/validation_service.ts +7 -10
  32. package/src/key_store/local_key_store.ts +1 -1
  33. package/src/key_store/node_keystore_adapter.ts +1 -1
  34. package/src/key_store/web3signer_key_store.ts +7 -10
  35. package/src/metrics.ts +4 -2
  36. package/src/validator.ts +87 -52
package/src/validator.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import type { EpochCache } from '@aztec/epoch-cache';
2
+ import { BlockNumber, EpochNumber } from '@aztec/foundation/branded-types';
3
+ import { Fr } from '@aztec/foundation/curves/bn254';
2
4
  import type { EthAddress } from '@aztec/foundation/eth-address';
3
5
  import type { Signature } from '@aztec/foundation/eth-signature';
4
- import { Fr } from '@aztec/foundation/fields';
5
6
  import { type Logger, createLogger } from '@aztec/foundation/log';
6
7
  import { RunningPromise } from '@aztec/foundation/running-promise';
7
8
  import { sleep } from '@aztec/foundation/sleep';
@@ -16,7 +17,7 @@ import type { IFullNodeBlockBuilder, Validator, ValidatorClientFullConfig } from
16
17
  import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
17
18
  import type { BlockAttestation, BlockProposal, BlockProposalOptions } from '@aztec/stdlib/p2p';
18
19
  import type { CheckpointHeader } from '@aztec/stdlib/rollup';
19
- import type { StateReference, Tx } from '@aztec/stdlib/tx';
20
+ import type { Tx } from '@aztec/stdlib/tx';
20
21
  import { AttestationTimeoutError } from '@aztec/stdlib/validators';
21
22
  import { type TelemetryClient, type Tracer, getTelemetryClient } from '@aztec/telemetry-client';
22
23
 
@@ -45,6 +46,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
45
46
  public readonly tracer: Tracer;
46
47
  private validationService: ValidationService;
47
48
  private metrics: ValidatorMetrics;
49
+ private log: Logger;
48
50
 
49
51
  // Whether it has already registered handlers on the p2p client
50
52
  private hasRegisteredHandlers = false;
@@ -52,7 +54,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
52
54
  // Used to check if we are sending the same proposal twice
53
55
  private previousProposal?: BlockProposal;
54
56
 
55
- private lastEpochForCommitteeUpdateLoop: bigint | undefined;
57
+ private lastEpochForCommitteeUpdateLoop: EpochNumber | undefined;
56
58
  private epochCacheUpdateLoop: RunningPromise;
57
59
 
58
60
  private proposersOfInvalidBlocks: Set<string> = new Set();
@@ -65,16 +67,20 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
65
67
  private config: ValidatorClientFullConfig,
66
68
  private dateProvider: DateProvider = new DateProvider(),
67
69
  telemetry: TelemetryClient = getTelemetryClient(),
68
- private log = createLogger('validator'),
70
+ log = createLogger('validator'),
69
71
  ) {
70
72
  super();
73
+
74
+ // Create child logger with fisherman prefix if in fisherman mode
75
+ this.log = config.fishermanMode ? log.createChild('[FISHERMAN]') : log;
76
+
71
77
  this.tracer = telemetry.getTracer('Validator');
72
78
  this.metrics = new ValidatorMetrics(telemetry);
73
79
 
74
- this.validationService = new ValidationService(keyStore, log.createChild('validation-service'));
80
+ this.validationService = new ValidationService(keyStore, this.log.createChild('validation-service'));
75
81
 
76
82
  // Refresh epoch cache every second to trigger alert if participation in committee changes
77
- this.epochCacheUpdateLoop = new RunningPromise(this.handleEpochCommitteeUpdate.bind(this), log, 1000);
83
+ this.epochCacheUpdateLoop = new RunningPromise(this.handleEpochCommitteeUpdate.bind(this), this.log, 1000);
78
84
 
79
85
  const myAddresses = this.getValidatorAddresses();
80
86
  this.log.verbose(`Initialized validator with addresses: ${myAddresses.map(a => a.toString()).join(', ')}`);
@@ -186,7 +192,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
186
192
  // Proxy method for backwards compatibility with tests
187
193
  public reExecuteTransactions(
188
194
  proposal: BlockProposal,
189
- blockNumber: number,
195
+ blockNumber: BlockNumber,
190
196
  txs: any[],
191
197
  l1ToL2Messages: Fr[],
192
198
  ): Promise<any> {
@@ -223,14 +229,9 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
223
229
 
224
230
  const myAddresses = this.getValidatorAddresses();
225
231
  const inCommittee = await this.epochCache.filterInCommittee('now', myAddresses);
232
+ this.log.info(`Started validator with addresses: ${myAddresses.map(a => a.toString()).join(', ')}`);
226
233
  if (inCommittee.length > 0) {
227
- this.log.info(
228
- `Started validator with addresses in current validator committee: ${inCommittee
229
- .map(a => a.toString())
230
- .join(', ')}`,
231
- );
232
- } else {
233
- this.log.info(`Started validator with addresses: ${myAddresses.map(a => a.toString()).join(', ')}`);
234
+ this.log.info(`Addresses in current validator committee: ${inCommittee.map(a => a.toString()).join(', ')}`);
234
235
  }
235
236
  this.epochCacheUpdateLoop.start();
236
237
 
@@ -259,7 +260,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
259
260
  }
260
261
 
261
262
  async attestToProposal(proposal: BlockProposal, proposalSender: PeerId): Promise<BlockAttestation[] | undefined> {
262
- const slotNumber = proposal.slotNumber.toBigInt();
263
+ const slotNumber = proposal.slotNumber;
263
264
  const proposer = proposal.getSender();
264
265
 
265
266
  // Reject proposals with invalid signatures
@@ -276,12 +277,16 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
276
277
  this.log.info(`Received proposal for slot ${slotNumber}`, {
277
278
  ...proposalInfo,
278
279
  txHashes: proposal.txHashes.map(t => t.toString()),
280
+ fishermanMode: this.config.fishermanMode || false,
279
281
  });
280
282
 
281
283
  // Reexecute txs if we are part of the committee so we can attest, or if slashing is enabled so we can slash
282
284
  // invalid proposals even when not in the committee, or if we are configured to always reexecute for monitoring purposes.
283
- const { validatorReexecute, slashBroadcastedInvalidBlockPenalty, alwaysReexecuteBlockProposals } = this.config;
285
+ // In fisherman mode, we always reexecute to validate proposals.
286
+ const { validatorReexecute, slashBroadcastedInvalidBlockPenalty, alwaysReexecuteBlockProposals, fishermanMode } =
287
+ this.config;
284
288
  const shouldReexecute =
289
+ fishermanMode ||
285
290
  (slashBroadcastedInvalidBlockPenalty > 0n && validatorReexecute) ||
286
291
  (partOfCommittee && validatorReexecute) ||
287
292
  alwaysReexecuteBlockProposals;
@@ -295,24 +300,21 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
295
300
  if (!validationResult.isValid) {
296
301
  this.log.warn(`Proposal validation failed: ${validationResult.reason}`, proposalInfo);
297
302
 
298
- // Only track attestation failure metrics if we're actually in the committee
299
- if (partOfCommittee) {
300
- const reason = validationResult.reason || 'unknown';
301
- // Classify failure reason: bad proposal vs node issue
302
- const badProposalReasons: BlockProposalValidationFailureReason[] = [
303
- 'invalid_proposal',
304
- 'state_mismatch',
305
- 'failed_txs',
306
- 'in_hash_mismatch',
307
- 'parent_block_wrong_slot',
308
- ];
309
-
310
- if (badProposalReasons.includes(reason as BlockProposalValidationFailureReason)) {
311
- this.metrics.incFailedAttestationsBadProposal(1, reason);
312
- } else {
313
- // Node issues: parent_block_not_found, block_number_already_exists, txs_not_available, timeout, unknown_error
314
- this.metrics.incFailedAttestationsNodeIssue(1, reason);
315
- }
303
+ const reason = validationResult.reason || 'unknown';
304
+ // Classify failure reason: bad proposal vs node issue
305
+ const badProposalReasons: BlockProposalValidationFailureReason[] = [
306
+ 'invalid_proposal',
307
+ 'state_mismatch',
308
+ 'failed_txs',
309
+ 'in_hash_mismatch',
310
+ 'parent_block_wrong_slot',
311
+ ];
312
+
313
+ if (badProposalReasons.includes(reason as BlockProposalValidationFailureReason)) {
314
+ this.metrics.incFailedAttestationsBadProposal(1, reason, partOfCommittee);
315
+ } else {
316
+ // Node issues so we can't attest
317
+ this.metrics.incFailedAttestationsNodeIssue(1, reason, partOfCommittee);
316
318
  }
317
319
 
318
320
  // Slash invalid block proposals (can happen even when not in committee)
@@ -328,17 +330,47 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
328
330
  }
329
331
 
330
332
  // Check that I have any address in current committee before attesting
331
- if (!partOfCommittee) {
333
+ // In fisherman mode, we still create attestations for validation even if not in committee
334
+ if (!partOfCommittee && !this.config.fishermanMode) {
332
335
  this.log.verbose(`No validator in the current committee, skipping attestation`, proposalInfo);
333
336
  return undefined;
334
337
  }
335
338
 
336
339
  // Provided all of the above checks pass, we can attest to the proposal
337
- this.log.info(`Attesting to proposal for slot ${slotNumber}`, proposalInfo);
340
+ this.log.info(`${partOfCommittee ? 'Attesting to' : 'Validated'} proposal for slot ${slotNumber}`, {
341
+ ...proposalInfo,
342
+ inCommittee: partOfCommittee,
343
+ fishermanMode: this.config.fishermanMode || false,
344
+ });
345
+
338
346
  this.metrics.incSuccessfulAttestations(inCommittee.length);
339
347
 
340
348
  // If the above function does not throw an error, then we can attest to the proposal
341
- return this.createBlockAttestationsFromProposal(proposal, inCommittee);
349
+ // Determine which validators should attest
350
+ let attestors: EthAddress[];
351
+ if (partOfCommittee) {
352
+ attestors = inCommittee;
353
+ } else if (this.config.fishermanMode) {
354
+ // In fisherman mode, create attestations for validation purposes even if not in committee. These won't be broadcast.
355
+ attestors = this.getValidatorAddresses();
356
+ } else {
357
+ attestors = [];
358
+ }
359
+
360
+ // Only create attestations if we have attestors
361
+ if (attestors.length === 0) {
362
+ return undefined;
363
+ }
364
+
365
+ if (this.config.fishermanMode) {
366
+ // bail out early and don't save attestations to the pool in fisherman mode
367
+ this.log.info(`Creating attestations for proposal for slot ${slotNumber}`, {
368
+ ...proposalInfo,
369
+ attestors: attestors.map(a => a.toString()),
370
+ });
371
+ return undefined;
372
+ }
373
+ return this.createBlockAttestationsFromProposal(proposal, attestors);
342
374
  }
343
375
 
344
376
  private slashInvalidBlock(proposal: BlockProposal) {
@@ -363,33 +395,28 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
363
395
  validator: proposer,
364
396
  amount: this.config.slashBroadcastedInvalidBlockPenalty,
365
397
  offenseType: OffenseType.BROADCASTED_INVALID_BLOCK_PROPOSAL,
366
- epochOrSlot: proposal.slotNumber.toBigInt(),
398
+ epochOrSlot: BigInt(proposal.slotNumber),
367
399
  },
368
400
  ]);
369
401
  }
370
402
 
371
403
  async createBlockProposal(
372
- blockNumber: number,
404
+ blockNumber: BlockNumber,
373
405
  header: CheckpointHeader,
374
406
  archive: Fr,
375
- stateReference: StateReference,
376
407
  txs: Tx[],
377
408
  proposerAddress: EthAddress | undefined,
378
409
  options: BlockProposalOptions,
379
410
  ): Promise<BlockProposal | undefined> {
380
- if (this.previousProposal?.slotNumber.equals(header.slotNumber)) {
411
+ if (this.previousProposal?.slotNumber === header.slotNumber) {
381
412
  this.log.verbose(`Already made a proposal for the same slot, skipping proposal`);
382
413
  return Promise.resolve(undefined);
383
414
  }
384
415
 
385
- const newProposal = await this.validationService.createBlockProposal(
386
- header,
387
- archive,
388
- stateReference,
389
- txs,
390
- proposerAddress,
391
- { ...options, broadcastInvalidBlockProposal: this.config.broadcastInvalidBlockProposal },
392
- );
416
+ const newProposal = await this.validationService.createBlockProposal(header, archive, txs, proposerAddress, {
417
+ ...options,
418
+ broadcastInvalidBlockProposal: this.config.broadcastInvalidBlockProposal,
419
+ });
393
420
  this.previousProposal = newProposal;
394
421
  return newProposal;
395
422
  }
@@ -406,15 +433,23 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
406
433
  }
407
434
 
408
435
  async collectOwnAttestations(proposal: BlockProposal): Promise<BlockAttestation[]> {
409
- const slot = proposal.payload.header.slotNumber.toBigInt();
436
+ const slot = proposal.payload.header.slotNumber;
410
437
  const inCommittee = await this.epochCache.filterInCommittee(slot, this.getValidatorAddresses());
411
438
  this.log.debug(`Collecting ${inCommittee.length} self-attestations for slot ${slot}`, { inCommittee });
412
- return this.createBlockAttestationsFromProposal(proposal, inCommittee);
439
+ const attestations = await this.createBlockAttestationsFromProposal(proposal, inCommittee);
440
+
441
+ // We broadcast our own attestations to our peers so, in case our block does not get mined on L1,
442
+ // other nodes can see that our validators did attest to this block proposal, and do not slash us
443
+ // due to inactivity for missed attestations.
444
+ void this.p2pClient.broadcastAttestations(attestations).catch(err => {
445
+ this.log.error(`Failed to broadcast self-attestations for slot ${slot}`, err);
446
+ });
447
+ return attestations;
413
448
  }
414
449
 
415
450
  async collectAttestations(proposal: BlockProposal, required: number, deadline: Date): Promise<BlockAttestation[]> {
416
451
  // Wait and poll the p2pClient's attestation pool for this block until we have enough attestations
417
- const slot = proposal.payload.header.slotNumber.toBigInt();
452
+ const slot = proposal.payload.header.slotNumber;
418
453
  this.log.debug(`Collecting ${required} attestations for slot ${slot} with deadline ${deadline.toISOString()}`);
419
454
 
420
455
  if (+deadline < this.dateProvider.now()) {