@aztec/validator-client 0.0.1-commit.f295ac2 → 0.0.1-commit.f2ce05ee

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
@@ -18,19 +18,20 @@ import { RunningPromise } from '@aztec/foundation/running-promise';
18
18
  import { sleep } from '@aztec/foundation/sleep';
19
19
  import { DateProvider } from '@aztec/foundation/timer';
20
20
  import type { KeystoreManager } from '@aztec/node-keystore';
21
- import type { P2P, PeerId, TxProvider } from '@aztec/p2p';
21
+ import type { DuplicateProposalInfo, P2P, PeerId } from '@aztec/p2p';
22
22
  import { AuthRequest, AuthResponse, BlockProposalValidator, ReqRespSubProtocol } from '@aztec/p2p';
23
23
  import { OffenseType, WANT_TO_SLASH_EVENT, type Watcher, type WatcherEmitter } from '@aztec/slasher';
24
24
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
25
- import type { CommitteeAttestationsAndSigners, L2BlockNew, L2BlockSink, L2BlockSource } from '@aztec/stdlib/block';
25
+ import type { CommitteeAttestationsAndSigners, L2Block, L2BlockSink, L2BlockSource } from '@aztec/stdlib/block';
26
26
  import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
27
27
  import type {
28
28
  CreateCheckpointProposalLastBlockData,
29
+ ITxProvider,
29
30
  Validator,
30
31
  ValidatorClientFullConfig,
31
32
  WorldStateSynchronizer,
32
33
  } from '@aztec/stdlib/interfaces/server';
33
- import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
34
+ import { type L1ToL2MessageSource, accumulateCheckpointOutHashes } from '@aztec/stdlib/messaging';
34
35
  import type {
35
36
  BlockProposal,
36
37
  BlockProposalOptions,
@@ -87,11 +88,6 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
87
88
 
88
89
  private proposersOfInvalidBlocks: Set<string> = new Set();
89
90
 
90
- // TODO(palla/mbps): Remove this once checkpoint validation is stable and we can validate all blocks properly.
91
- // Tracks slots for which we have successfully validated a block proposal, so we can attest to checkpoint proposals for those slots.
92
- // eslint-disable-next-line aztec-custom/no-non-primitive-in-collections
93
- private validatedBlockSlots: Set<SlotNumber> = new Set();
94
-
95
91
  protected constructor(
96
92
  private keyStore: ExtendedValidatorKeyStore,
97
93
  private epochCache: EpochCache,
@@ -184,7 +180,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
184
180
  p2pClient: P2P,
185
181
  blockSource: L2BlockSource & L2BlockSink,
186
182
  l1ToL2MessageSource: L1ToL2MessageSource,
187
- txProvider: TxProvider,
183
+ txProvider: ITxProvider,
188
184
  keyStoreManager: KeystoreManager,
189
185
  blobClient: BlobClientInterface,
190
186
  dateProvider: DateProvider = new DateProvider(),
@@ -313,6 +309,11 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
313
309
  ): Promise<CheckpointAttestation[] | undefined> => this.attestToCheckpointProposal(checkpoint, proposalSender);
314
310
  this.p2pClient.registerCheckpointProposalHandler(checkpointHandler);
315
311
 
312
+ // Duplicate proposal handler - triggers slashing for equivocation
313
+ this.p2pClient.registerDuplicateProposalCallback((info: DuplicateProposalInfo) => {
314
+ this.handleDuplicateProposal(info);
315
+ });
316
+
316
317
  const myAddresses = this.getValidatorAddresses();
317
318
  this.p2pClient.registerThisValidatorAddresses(myAddresses);
318
319
 
@@ -413,10 +414,6 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
413
414
  return false;
414
415
  }
415
416
 
416
- // TODO(palla/mbps): Remove this once checkpoint validation is stable.
417
- // Track that we successfully validated a block for this slot, so we can attest to checkpoint proposals for it.
418
- this.validatedBlockSlots.add(slotNumber);
419
-
420
417
  return true;
421
418
  }
422
419
 
@@ -461,17 +458,9 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
461
458
  fishermanMode: this.config.fishermanMode || false,
462
459
  });
463
460
 
464
- // TODO(palla/mbps): Remove this once checkpoint validation is stable.
465
- // Check that we have successfully validated a block for this slot before attesting to the checkpoint.
466
- if (!this.validatedBlockSlots.has(slotNumber)) {
467
- this.log.warn(`No validated block found for slot ${slotNumber}, refusing to attest to checkpoint`, proposalInfo);
468
- return undefined;
469
- }
470
-
471
461
  // Validate the checkpoint proposal before attesting (unless skipCheckpointProposalValidation is set)
472
- // TODO(palla/mbps): Change default to false once checkpoint validation is stable.
473
- if (this.config.skipCheckpointProposalValidation !== false) {
474
- this.log.verbose(`Skipping checkpoint proposal validation for slot ${slotNumber}`, proposalInfo);
462
+ if (this.config.skipCheckpointProposalValidation) {
463
+ this.log.warn(`Skipping checkpoint proposal validation for slot ${slotNumber}`, proposalInfo);
475
464
  } else {
476
465
  const validationResult = await this.validateCheckpointProposal(proposal, proposalInfo);
477
466
  if (!validationResult.isValid) {
@@ -534,7 +523,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
534
523
  attestors: EthAddress[] = [],
535
524
  ): Promise<CheckpointAttestation[]> {
536
525
  const attestations = await this.validationService.attestToCheckpointProposal(proposal, attestors);
537
- await this.p2pClient.addCheckpointAttestations(attestations);
526
+ await this.p2pClient.addOwnCheckpointAttestations(attestations);
538
527
  return attestations;
539
528
  }
540
529
 
@@ -547,7 +536,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
547
536
  proposalInfo: LogData,
548
537
  ): Promise<{ isValid: true } | { isValid: false; reason: string }> {
549
538
  const slot = proposal.slotNumber;
550
- const timeoutSeconds = 10;
539
+ const timeoutSeconds = 10; // TODO(palla/mbps): This should map to the timetable settings
551
540
 
552
541
  // Wait for last block to sync by archive
553
542
  let lastBlockHeader: BlockHeader | undefined;
@@ -617,6 +606,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
617
606
  previousCheckpointOutHashes,
618
607
  fork,
619
608
  blocks,
609
+ this.log.getBindings(),
620
610
  );
621
611
 
622
612
  // Complete the checkpoint to get computed values
@@ -642,13 +632,17 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
642
632
  return { isValid: false, reason: 'archive_mismatch' };
643
633
  }
644
634
 
645
- // Check that the accumulated out hash matches the value in the proposal.
646
- const computedOutHash = computedCheckpoint.getCheckpointOutHash();
647
- const proposalOutHash = proposal.checkpointHeader.epochOutHash;
648
- if (!computedOutHash.equals(proposalOutHash)) {
635
+ // Check that the accumulated epoch out hash matches the value in the proposal.
636
+ // The epoch out hash is the accumulated hash of all checkpoint out hashes in the epoch.
637
+ const checkpointOutHash = computedCheckpoint.getCheckpointOutHash();
638
+ const computedEpochOutHash = accumulateCheckpointOutHashes([...previousCheckpointOutHashes, checkpointOutHash]);
639
+ const proposalEpochOutHash = proposal.checkpointHeader.epochOutHash;
640
+ if (!computedEpochOutHash.equals(proposalEpochOutHash)) {
649
641
  this.log.warn(`Epoch out hash mismatch`, {
650
- proposalOutHash: proposalOutHash.toString(),
651
- computedOutHash: computedOutHash.toString(),
642
+ proposalEpochOutHash: proposalEpochOutHash.toString(),
643
+ computedEpochOutHash: computedEpochOutHash.toString(),
644
+ checkpointOutHash: checkpointOutHash.toString(),
645
+ previousCheckpointOutHashes: previousCheckpointOutHashes.map(h => h.toString()),
652
646
  ...proposalInfo,
653
647
  });
654
648
  return { isValid: false, reason: 'out_hash_mismatch' };
@@ -664,7 +658,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
664
658
  /**
665
659
  * Extract checkpoint global variables from a block.
666
660
  */
667
- private extractCheckpointConstants(block: L2BlockNew): CheckpointGlobalVariables {
661
+ private extractCheckpointConstants(block: L2Block): CheckpointGlobalVariables {
668
662
  const gv = block.header.globalVariables;
669
663
  return {
670
664
  chainId: gv.chainId,
@@ -732,6 +726,30 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
732
726
  ]);
733
727
  }
734
728
 
729
+ /**
730
+ * Handle detection of a duplicate proposal (equivocation).
731
+ * Emits a slash event when a proposer sends multiple proposals for the same position.
732
+ */
733
+ private handleDuplicateProposal(info: DuplicateProposalInfo): void {
734
+ const { slot, proposer, type } = info;
735
+
736
+ this.log.warn(`Triggering slash event for duplicate ${type} proposal from ${proposer.toString()} at slot ${slot}`, {
737
+ proposer: proposer.toString(),
738
+ slot,
739
+ type,
740
+ });
741
+
742
+ // Emit slash event
743
+ this.emit(WANT_TO_SLASH_EVENT, [
744
+ {
745
+ validator: proposer,
746
+ amount: this.config.slashDuplicateProposalPenalty,
747
+ offenseType: OffenseType.DUPLICATE_PROPOSAL,
748
+ epochOrSlot: BigInt(slot),
749
+ },
750
+ ]);
751
+ }
752
+
735
753
  async createBlockProposal(
736
754
  blockHeader: BlockHeader,
737
755
  indexWithinCheckpoint: IndexWithinCheckpoint,
@@ -739,7 +757,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
739
757
  archive: Fr,
740
758
  txs: Tx[],
741
759
  proposerAddress: EthAddress | undefined,
742
- options: BlockProposalOptions,
760
+ options: BlockProposalOptions = {},
743
761
  ): Promise<BlockProposal> {
744
762
  // TODO(palla/mbps): Prevent double proposals properly
745
763
  // if (this.previousProposal?.slotNumber === blockHeader.globalVariables.slotNumber) {
@@ -771,7 +789,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
771
789
  archive: Fr,
772
790
  lastBlockInfo: CreateCheckpointProposalLastBlockData | undefined,
773
791
  proposerAddress: EthAddress | undefined,
774
- options: CheckpointProposalOptions,
792
+ options: CheckpointProposalOptions = {},
775
793
  ): Promise<CheckpointProposal> {
776
794
  this.log.info(`Assembling checkpoint proposal for slot ${checkpointHeader.slotNumber}`);
777
795
  return await this.validationService.createCheckpointProposal(