@aztec/validator-client 0.0.1-commit.358457c → 0.0.1-commit.381b1a9

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/src/validator.ts CHANGED
@@ -24,6 +24,7 @@ import { AuthRequest, AuthResponse, BlockProposalValidator, ReqRespSubProtocol }
24
24
  import { OffenseType, WANT_TO_SLASH_EVENT, type Watcher, type WatcherEmitter } from '@aztec/slasher';
25
25
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
26
26
  import type { CommitteeAttestationsAndSigners, L2Block, L2BlockSink, L2BlockSource } from '@aztec/stdlib/block';
27
+ import { validateCheckpoint } from '@aztec/stdlib/checkpoint';
27
28
  import { getEpochAtSlot, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
28
29
  import type {
29
30
  CreateCheckpointProposalLastBlockData,
@@ -89,6 +90,8 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
89
90
 
90
91
  private lastEpochForCommitteeUpdateLoop: EpochNumber | undefined;
91
92
  private epochCacheUpdateLoop: RunningPromise;
93
+ /** Tracks the last epoch in which each attester successfully submitted at least one attestation. */
94
+ private lastAttestedEpochByAttester: Map<string, EpochNumber> = new Map();
92
95
 
93
96
  private proposersOfInvalidBlocks: Set<string> = new Set();
94
97
 
@@ -160,6 +163,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
160
163
  this.log.trace(`No committee found for slot`);
161
164
  return;
162
165
  }
166
+ this.metrics.setCurrentEpoch(epoch);
163
167
  if (epoch !== this.lastEpochForCommitteeUpdateLoop) {
164
168
  const me = this.getValidatorAddresses();
165
169
  const committeeSet = new Set(committee.map(v => v.toString()));
@@ -197,6 +201,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
197
201
  const metrics = new ValidatorMetrics(telemetry);
198
202
  const blockProposalValidator = new BlockProposalValidator(epochCache, {
199
203
  txsPermitted: !config.disableTransactions,
204
+ maxTxsPerBlock: config.validateMaxTxsPerBlock,
200
205
  });
201
206
  const blockProposalHandler = new BlockProposalHandler(
202
207
  checkpointsBuilder,
@@ -221,7 +226,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
221
226
  ...config,
222
227
  maxStuckDutiesAgeMs: config.maxStuckDutiesAgeMs ?? epochCache.getL1Constants().slotDuration * 2 * 1000,
223
228
  };
224
- const { signer } = await createHASigner(haConfig, { telemetryClient: telemetry, dateProvider });
229
+ const { signer } = await createHASigner(haConfig);
225
230
  haSigner = signer;
226
231
  validatorKeyStore = new HAKeyStore(nodeKeystoreAdapter, signer);
227
232
  }
@@ -382,7 +387,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
382
387
 
383
388
  // Ignore proposals from ourselves (may happen in HA setups)
384
389
  if (this.getValidatorAddresses().some(addr => addr.equals(proposer))) {
385
- this.log.warn(`Ignoring block proposal from self for slot ${slotNumber}`, {
390
+ this.log.debug(`Ignoring block proposal from self for slot ${slotNumber}`, {
386
391
  proposer: proposer.toString(),
387
392
  slotNumber,
388
393
  });
@@ -418,9 +423,10 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
418
423
  );
419
424
 
420
425
  if (!validationResult.isValid) {
421
- this.log.warn(`Block proposal validation failed: ${validationResult.reason}`, proposalInfo);
422
-
423
426
  const reason = validationResult.reason || 'unknown';
427
+
428
+ this.log.warn(`Block proposal validation failed: ${reason}`, proposalInfo);
429
+
424
430
  // Classify failure reason: bad proposal vs node issue
425
431
  const badProposalReasons: BlockProposalValidationFailureReason[] = [
426
432
  'invalid_proposal',
@@ -492,7 +498,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
492
498
 
493
499
  // Ignore proposals from ourselves (may happen in HA setups)
494
500
  if (this.getValidatorAddresses().some(addr => addr.equals(proposer))) {
495
- this.log.warn(`Ignoring block proposal from self for slot ${slotNumber}`, {
501
+ this.log.debug(`Ignoring block proposal from self for slot ${slotNumber}`, {
496
502
  proposer: proposer.toString(),
497
503
  slotNumber,
498
504
  });
@@ -515,11 +521,9 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
515
521
  slotNumber,
516
522
  archive: proposal.archive.toString(),
517
523
  proposer: proposer.toString(),
518
- txCount: proposal.txHashes.length,
519
524
  };
520
525
  this.log.info(`Received checkpoint proposal for slot ${slotNumber}`, {
521
526
  ...proposalInfo,
522
- txHashes: proposal.txHashes.map(t => t.toString()),
523
527
  fishermanMode: this.config.fishermanMode || false,
524
528
  });
525
529
 
@@ -555,6 +559,17 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
555
559
 
556
560
  this.metrics.incSuccessfulAttestations(inCommittee.length);
557
561
 
562
+ // Track epoch participation per attester: count each (attester, epoch) pair at most once
563
+ const proposalEpoch = getEpochAtSlot(slotNumber, this.epochCache.getL1Constants());
564
+ for (const attester of inCommittee) {
565
+ const key = attester.toString();
566
+ const lastEpoch = this.lastAttestedEpochByAttester.get(key);
567
+ if (lastEpoch === undefined || proposalEpoch > lastEpoch) {
568
+ this.lastAttestedEpochByAttester.set(key, proposalEpoch);
569
+ this.metrics.incAttestedEpochCount(attester);
570
+ }
571
+ }
572
+
558
573
  // Determine which validators should attest
559
574
  let attestors: EthAddress[];
560
575
  if (partOfCommittee) {
@@ -751,6 +766,20 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
751
766
  return { isValid: false, reason: 'out_hash_mismatch' };
752
767
  }
753
768
 
769
+ // Final round of validations on the checkpoint, just in case.
770
+ try {
771
+ validateCheckpoint(computedCheckpoint, {
772
+ rollupManaLimit: this.checkpointsBuilder.getConfig().rollupManaLimit,
773
+ maxDABlockGas: this.config.validateMaxDABlockGas,
774
+ maxL2BlockGas: this.config.validateMaxL2BlockGas,
775
+ maxTxsPerBlock: this.config.validateMaxTxsPerBlock,
776
+ maxTxsPerCheckpoint: this.config.validateMaxTxsPerCheckpoint,
777
+ });
778
+ } catch (err) {
779
+ this.log.warn(`Checkpoint validation failed: ${err}`, proposalInfo);
780
+ return { isValid: false, reason: 'checkpoint_validation_failed' };
781
+ }
782
+
754
783
  this.log.verbose(`Checkpoint proposal validation successful for slot ${slot}`, proposalInfo);
755
784
  return { isValid: true };
756
785
  } finally {