@lodestar/validator 1.38.0 → 1.39.0-dev.39dac0f03d

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.
@@ -85,7 +85,7 @@ export class SyncCommitteeDutiesService {
85
85
  private readonly config: ChainForkConfig,
86
86
  private readonly logger: LoggerVc,
87
87
  private readonly api: ApiClient,
88
- clock: IClock,
88
+ private readonly clock: IClock,
89
89
  private readonly validatorStore: ValidatorStore,
90
90
  syncingStatusTracker: SyncingStatusTracker,
91
91
  metrics: Metrics | null,
@@ -134,6 +134,18 @@ export class SyncCommitteeDutiesService {
134
134
  selectionProofs: await this.getSelectionProofs(slot, dutyAtPeriod.duty),
135
135
  });
136
136
  }
137
+
138
+ if (this.opts?.distributedAggregationSelection) {
139
+ // Validator in distributed cluster only has a key share, not the full private key.
140
+ // The partial selection proofs must be exchanged for combined selection proofs by
141
+ // calling submitSyncCommitteeSelections on the distributed validator middleware client.
142
+ // This will run in parallel to other sync committee tasks but must be finished before starting
143
+ // sync committee contributions as it is required to correctly determine if validator is aggregator
144
+ // and to produce a ContributionAndProof that can be threshold aggregated by the middleware client.
145
+ this.runDistributedAggregationSelectionTasks(duties, slot).catch((e) =>
146
+ this.logger.error("Error on sync committee aggregation selection", {slot}, e)
147
+ );
148
+ }
137
149
  }
138
150
 
139
151
  return duties;
@@ -307,8 +319,8 @@ export class SyncCommitteeDutiesService {
307
319
  if (this.opts?.distributedAggregationSelection) {
308
320
  // Validator in distributed cluster only has a key share, not the full private key.
309
321
  // Passing a partial selection proof to `is_sync_committee_aggregator` would produce incorrect result.
310
- // SyncCommitteeService will exchange partial for combined selection proofs retrieved from
311
- // distributed validator middleware client and determine aggregators at beginning of every slot.
322
+ // For all duties in the slot, aggregators are determined by exchanging partial for combined selection
323
+ // proofs retrieved from distributed validator middleware client at beginning of every slot.
312
324
  dutiesAndProofs.push({
313
325
  selectionProof: null,
314
326
  partialSelectionProof: selectionProof,
@@ -334,4 +346,74 @@ export class SyncCommitteeDutiesService {
334
346
  }
335
347
  }
336
348
  }
349
+
350
+ /**
351
+ * Performs additional sync committee contribution tasks required if validator is part of distributed cluster
352
+ *
353
+ * 1. Exchange partial for combined selection proofs
354
+ * 2. Determine validators that should produce sync committee contribution
355
+ * 3. Mutate duty objects to set selection proofs for aggregators
356
+ */
357
+ private async runDistributedAggregationSelectionTasks(duties: SyncDutyAndProofs[], slot: number): Promise<void> {
358
+ if (duties.length === 0) {
359
+ return;
360
+ }
361
+
362
+ const partialSelections: routes.validator.SyncCommitteeSelection[] = [];
363
+
364
+ for (const {duty, selectionProofs} of duties) {
365
+ const validatorSelections: routes.validator.SyncCommitteeSelection[] = selectionProofs.map(
366
+ ({subcommitteeIndex, partialSelectionProof}) => ({
367
+ validatorIndex: duty.validatorIndex,
368
+ slot,
369
+ subcommitteeIndex,
370
+ selectionProof: partialSelectionProof as BLSSignature,
371
+ })
372
+ );
373
+ partialSelections.push(...validatorSelections);
374
+ }
375
+
376
+ this.logger.debug("Submitting partial sync committee selection proofs", {slot, count: partialSelections.length});
377
+
378
+ const res = await this.api.validator.submitSyncCommitteeSelections(
379
+ {selections: partialSelections},
380
+ {
381
+ // Exit sync committee contributions flow if there is no response until CONTRIBUTION_DUE_BPS of the slot.
382
+ // Note that the sync committee contributions flow is not explicitly exited but rather will be skipped
383
+ // due to the fact that calculation of `is_sync_committee_aggregator` in SyncCommitteeDutiesService is not done
384
+ // and selectionProof is set to null, meaning no validator will be considered an aggregator.
385
+ timeoutMs: this.config.getSyncContributionDueMs(this.config.getForkName(slot)) - this.clock.msFromSlot(slot),
386
+ }
387
+ );
388
+
389
+ const combinedSelections = res.value();
390
+ this.logger.debug("Received combined sync committee selection proofs", {slot, count: combinedSelections.length});
391
+
392
+ for (const dutyAndProofs of duties) {
393
+ const {validatorIndex, subnets} = dutyAndProofs.duty;
394
+
395
+ for (const subnet of subnets) {
396
+ const logCtxValidator = {slot, index: subnet, validatorIndex};
397
+
398
+ const combinedSelection = combinedSelections.find(
399
+ (s) => s.validatorIndex === validatorIndex && s.slot === slot && s.subcommitteeIndex === subnet
400
+ );
401
+
402
+ if (!combinedSelection) {
403
+ this.logger.warn("Did not receive combined sync committee selection proof", logCtxValidator);
404
+ continue;
405
+ }
406
+
407
+ const isAggregator = isSyncCommitteeAggregator(combinedSelection.selectionProof);
408
+
409
+ if (isAggregator) {
410
+ const selectionProofObject = dutyAndProofs.selectionProofs.find((p) => p.subcommitteeIndex === subnet);
411
+ if (selectionProofObject) {
412
+ // Update selection proof by mutating proof objects in duty object
413
+ selectionProofObject.selectionProof = combinedSelection.selectionProof;
414
+ }
415
+ }
416
+ }
417
+ }
418
+ }
337
419
  }
@@ -53,7 +53,7 @@ import {DoppelgangerService} from "./doppelgangerService.js";
53
53
  import {IndicesService} from "./indices.js";
54
54
 
55
55
  type BLSPubkeyMaybeHex = BLSPubkey | PubkeyHex;
56
- type Eth1Address = string;
56
+ type ExecutionAddress = string;
57
57
 
58
58
  export enum SignerType {
59
59
  Local,
@@ -74,7 +74,7 @@ export type SignerRemote = {
74
74
  type DefaultProposerConfig = {
75
75
  graffiti?: string;
76
76
  strictFeeRecipientCheck: boolean;
77
- feeRecipient: Eth1Address;
77
+ feeRecipient: ExecutionAddress;
78
78
  builder: {
79
79
  gasLimit: number;
80
80
  selection: routes.validator.BuilderSelection;
@@ -85,7 +85,7 @@ type DefaultProposerConfig = {
85
85
  export type ProposerConfig = {
86
86
  graffiti?: string;
87
87
  strictFeeRecipientCheck?: boolean;
88
- feeRecipient?: Eth1Address;
88
+ feeRecipient?: ExecutionAddress;
89
89
  builder?: {
90
90
  gasLimit?: number;
91
91
  selection?: routes.validator.BuilderSelection;
@@ -219,7 +219,7 @@ export class ValidatorStore {
219
219
  : this.indicesService.pollValidatorIndices(Array.from(this.validators.keys()));
220
220
  }
221
221
 
222
- getFeeRecipient(pubkeyHex: PubkeyHex): Eth1Address {
222
+ getFeeRecipient(pubkeyHex: PubkeyHex): ExecutionAddress {
223
223
  const validatorData = this.validators.get(pubkeyHex);
224
224
  if (validatorData === undefined) {
225
225
  throw Error(`Validator pubkey ${pubkeyHex} not known`);
@@ -227,12 +227,12 @@ export class ValidatorStore {
227
227
  return validatorData.feeRecipient ?? this.defaultProposerConfig.feeRecipient;
228
228
  }
229
229
 
230
- getFeeRecipientByIndex(index: ValidatorIndex): Eth1Address {
230
+ getFeeRecipientByIndex(index: ValidatorIndex): ExecutionAddress {
231
231
  const pubkey = this.indicesService.index2pubkey.get(index);
232
232
  return pubkey ? this.getFeeRecipient(pubkey) : this.defaultProposerConfig.feeRecipient;
233
233
  }
234
234
 
235
- setFeeRecipient(pubkeyHex: PubkeyHex, feeRecipient: Eth1Address): void {
235
+ setFeeRecipient(pubkeyHex: PubkeyHex, feeRecipient: ExecutionAddress): void {
236
236
  const validatorData = this.validators.get(pubkeyHex);
237
237
  if (validatorData === undefined) {
238
238
  throw Error(`Validator pubkey ${pubkeyHex} not known`);
@@ -696,7 +696,7 @@ export class ValidatorStore {
696
696
 
697
697
  async signValidatorRegistration(
698
698
  pubkeyMaybeHex: BLSPubkeyMaybeHex,
699
- regAttributes: {feeRecipient: Eth1Address; gasLimit: number},
699
+ regAttributes: {feeRecipient: ExecutionAddress; gasLimit: number},
700
700
  _slot: Slot
701
701
  ): Promise<bellatrix.SignedValidatorRegistrationV1> {
702
702
  const pubkey = typeof pubkeyMaybeHex === "string" ? fromHex(pubkeyMaybeHex) : pubkeyMaybeHex;
@@ -727,7 +727,7 @@ export class ValidatorStore {
727
727
 
728
728
  async getValidatorRegistration(
729
729
  pubkeyMaybeHex: BLSPubkeyMaybeHex,
730
- regAttributes: {feeRecipient: Eth1Address; gasLimit: number},
730
+ regAttributes: {feeRecipient: ExecutionAddress; gasLimit: number},
731
731
  slot: Slot
732
732
  ): Promise<bellatrix.SignedValidatorRegistrationV1> {
733
733
  const pubkeyHex = typeof pubkeyMaybeHex === "string" ? pubkeyMaybeHex : toPubkeyHex(pubkeyMaybeHex);
@@ -110,8 +110,8 @@ function getSpecCriticalParams(localConfig: ChainConfig): Record<keyof ConfigWit
110
110
 
111
111
  PRESET_BASE: false, // Not relevant, each preset value is checked below
112
112
  CONFIG_NAME: false, // Arbitrary string, not relevant
113
- // validator client behaviour does not change with this parameters, so it's not concerned about them.
114
- // However, with the override ttd flag, the validator and beacon could be out of sync and prevent it from running.
113
+
114
+ // Deprecated - All networks have completed the merge transition
115
115
  TERMINAL_TOTAL_DIFFICULTY: false,
116
116
  TERMINAL_BLOCK_HASH: false,
117
117
  TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: false,