@lodestar/validator 1.43.0-dev.9c8becae00 → 1.43.0-dev.9f5db5b9c7

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 (48) hide show
  1. package/lib/metrics.d.ts +10 -0
  2. package/lib/metrics.d.ts.map +1 -1
  3. package/lib/metrics.js +37 -0
  4. package/lib/metrics.js.map +1 -1
  5. package/lib/services/block.d.ts.map +1 -1
  6. package/lib/services/block.js +1 -2
  7. package/lib/services/block.js.map +1 -1
  8. package/lib/services/chainHeaderTracker.d.ts +8 -2
  9. package/lib/services/chainHeaderTracker.d.ts.map +1 -1
  10. package/lib/services/chainHeaderTracker.js +23 -8
  11. package/lib/services/chainHeaderTracker.js.map +1 -1
  12. package/lib/services/emitter.d.ts +14 -1
  13. package/lib/services/emitter.d.ts.map +1 -1
  14. package/lib/services/emitter.js +22 -0
  15. package/lib/services/emitter.js.map +1 -1
  16. package/lib/services/ptc.d.ts +28 -0
  17. package/lib/services/ptc.d.ts.map +1 -0
  18. package/lib/services/ptc.js +89 -0
  19. package/lib/services/ptc.js.map +1 -0
  20. package/lib/services/ptcDuties.d.ts +31 -0
  21. package/lib/services/ptcDuties.d.ts.map +1 -0
  22. package/lib/services/ptcDuties.js +201 -0
  23. package/lib/services/ptcDuties.js.map +1 -0
  24. package/lib/services/validatorStore.d.ts +2 -0
  25. package/lib/services/validatorStore.d.ts.map +1 -1
  26. package/lib/services/validatorStore.js +33 -4
  27. package/lib/services/validatorStore.js.map +1 -1
  28. package/lib/util/externalSignerClient.d.ts +5 -1
  29. package/lib/util/externalSignerClient.d.ts.map +1 -1
  30. package/lib/util/externalSignerClient.js +4 -0
  31. package/lib/util/externalSignerClient.js.map +1 -1
  32. package/lib/util/params.js +3 -0
  33. package/lib/util/params.js.map +1 -1
  34. package/lib/validator.d.ts +4 -1
  35. package/lib/validator.d.ts.map +1 -1
  36. package/lib/validator.js +8 -2
  37. package/lib/validator.js.map +1 -1
  38. package/package.json +12 -12
  39. package/src/metrics.ts +46 -0
  40. package/src/services/block.ts +1 -2
  41. package/src/services/chainHeaderTracker.ts +31 -7
  42. package/src/services/emitter.ts +31 -0
  43. package/src/services/ptc.ts +131 -0
  44. package/src/services/ptcDuties.ts +246 -0
  45. package/src/services/validatorStore.ts +47 -3
  46. package/src/util/externalSignerClient.ts +7 -1
  47. package/src/util/params.ts +3 -0
  48. package/src/validator.ts +20 -1
@@ -0,0 +1,246 @@
1
+ import {ApiClient, routes} from "@lodestar/api";
2
+ import {ChainForkConfig} from "@lodestar/config";
3
+ import {SLOTS_PER_EPOCH, isForkPostGloas} from "@lodestar/params";
4
+ import {computeEpochAtSlot, isStartSlotOfEpoch} from "@lodestar/state-transition";
5
+ import {Epoch, RootHex, Slot, ValidatorIndex} from "@lodestar/types";
6
+ import {toPubkeyHex} from "@lodestar/utils";
7
+ import {Metrics} from "../metrics.js";
8
+ import {PubkeyHex} from "../types.js";
9
+ import {IClock, LoggerVc} from "../util/index.js";
10
+ import {ChainHeaderTracker, HeadEventData} from "./chainHeaderTracker.js";
11
+ import {SyncingStatusTracker} from "./syncingStatusTracker.js";
12
+ import {ValidatorStore} from "./validatorStore.js";
13
+
14
+ /** Only retain `HISTORICAL_DUTIES_EPOCHS` duties prior to the current epoch. */
15
+ const HISTORICAL_DUTIES_EPOCHS = 2;
16
+
17
+ type PtcDutiesAtEpoch = {dependentRoot: RootHex; dutiesByIndex: Map<ValidatorIndex, routes.validator.PtcDuty>};
18
+
19
+ export class PtcDutiesService {
20
+ /** Maps a validator index to its PTC duty for each epoch. */
21
+ private readonly dutiesByIndexByEpoch = new Map<Epoch, PtcDutiesAtEpoch>();
22
+
23
+ constructor(
24
+ private readonly config: ChainForkConfig,
25
+ private readonly logger: LoggerVc,
26
+ private readonly api: ApiClient,
27
+ private readonly clock: IClock,
28
+ private readonly validatorStore: ValidatorStore,
29
+ chainHeadTracker: ChainHeaderTracker,
30
+ syncingStatusTracker: SyncingStatusTracker,
31
+ private readonly metrics: Metrics | null
32
+ ) {
33
+ clock.runEveryEpoch(this.runDutiesTasks);
34
+ chainHeadTracker.runOnNewHead(this.onNewHead);
35
+ syncingStatusTracker.runOnResynced(async (slot) => {
36
+ // Skip on first slot of epoch since tasks are already scheduled.
37
+ if (!isStartSlotOfEpoch(slot)) {
38
+ return this.runDutiesTasks(computeEpochAtSlot(slot));
39
+ }
40
+ });
41
+
42
+ if (metrics) {
43
+ metrics.ptcDutiesCount.addCollect(() => {
44
+ const currentSlot = this.clock.getCurrentSlot();
45
+ let duties = 0;
46
+ let nextDutySlot = null;
47
+ for (const [epoch, ptcDutiesAtEpoch] of this.dutiesByIndexByEpoch) {
48
+ duties += ptcDutiesAtEpoch.dutiesByIndex.size;
49
+
50
+ // Epochs are sorted, stop searching once a next duty slot is found.
51
+ if (epoch < this.clock.currentEpoch || nextDutySlot !== null) continue;
52
+
53
+ for (const duty of ptcDutiesAtEpoch.dutiesByIndex.values()) {
54
+ if (duty.slot > currentSlot && (nextDutySlot === null || duty.slot < nextDutySlot)) {
55
+ nextDutySlot = duty.slot;
56
+ }
57
+ }
58
+ }
59
+ metrics.ptcDutiesCount.set(duties);
60
+ metrics.ptcDutiesEpochCount.set(this.dutiesByIndexByEpoch.size);
61
+ if (nextDutySlot !== null) metrics.ptcDutiesNextSlot.set(nextDutySlot);
62
+ });
63
+ }
64
+ }
65
+
66
+ removeDutiesForKey(pubkey: PubkeyHex): void {
67
+ for (const [epoch, ptcDutiesAtEpoch] of this.dutiesByIndexByEpoch) {
68
+ for (const [validatorIndex, duty] of ptcDutiesAtEpoch.dutiesByIndex) {
69
+ if (toPubkeyHex(duty.pubkey) === pubkey) {
70
+ ptcDutiesAtEpoch.dutiesByIndex.delete(validatorIndex);
71
+ if (ptcDutiesAtEpoch.dutiesByIndex.size === 0) {
72
+ this.dutiesByIndexByEpoch.delete(epoch);
73
+ }
74
+ }
75
+ }
76
+ }
77
+ }
78
+
79
+ /** Returns all PTC duties for the given slot. */
80
+ getDutiesAtSlot(slot: Slot): routes.validator.PtcDuty[] {
81
+ const epoch = computeEpochAtSlot(slot);
82
+ const duties: routes.validator.PtcDuty[] = [];
83
+ const epochDuties = this.dutiesByIndexByEpoch.get(epoch);
84
+ if (epochDuties === undefined) {
85
+ return duties;
86
+ }
87
+
88
+ for (const duty of epochDuties.dutiesByIndex.values()) {
89
+ if (duty.slot === slot) {
90
+ duties.push(duty);
91
+ }
92
+ }
93
+
94
+ return duties;
95
+ }
96
+
97
+ private runDutiesTasks = async (epoch: Epoch): Promise<void> => {
98
+ const nextEpoch = epoch + 1;
99
+ if (!isForkPostGloas(this.config.getForkName(nextEpoch * SLOTS_PER_EPOCH))) {
100
+ return;
101
+ }
102
+
103
+ await Promise.all([
104
+ this.pollPtcDuties(epoch, this.validatorStore.getAllLocalIndices()).catch((e: Error) => {
105
+ this.logger.error("Error on poll PTC duties", {epoch}, e);
106
+ }),
107
+
108
+ this.validatorStore
109
+ .pollValidatorIndices()
110
+ .then((newIndices) => this.pollPtcDuties(epoch, newIndices))
111
+ .catch((e: Error) => {
112
+ this.logger.error("Error on poll indices and PTC duties", {epoch}, e);
113
+ }),
114
+ ]);
115
+
116
+ this.pruneOldDuties(epoch);
117
+ };
118
+
119
+ private async pollPtcDuties(currentEpoch: Epoch, indexArr: ValidatorIndex[]): Promise<void> {
120
+ const nextEpoch = currentEpoch + 1;
121
+
122
+ if (indexArr.length === 0) {
123
+ return;
124
+ }
125
+
126
+ for (const epoch of [currentEpoch, nextEpoch]) {
127
+ await this.pollPtcDutiesForEpoch(epoch, indexArr).catch((e: Error) => {
128
+ this.logger.error("Failed to download PTC duties", {epoch}, e);
129
+ });
130
+ }
131
+ }
132
+
133
+ private async pollPtcDutiesForEpoch(epoch: Epoch, indexArr: ValidatorIndex[]): Promise<void> {
134
+ if (epoch < 0) {
135
+ return;
136
+ }
137
+
138
+ if (!isForkPostGloas(this.config.getForkName(epoch * SLOTS_PER_EPOCH))) {
139
+ return;
140
+ }
141
+
142
+ const res = await this.api.validator.getPtcDuties({epoch, indices: indexArr});
143
+ const ptcDuties = res.value();
144
+ const {dependentRoot} = res.meta();
145
+ const relevantDuties = ptcDuties.filter((duty) => {
146
+ const pubkeyHex = toPubkeyHex(duty.pubkey);
147
+ return this.validatorStore.hasVotingPubkey(pubkeyHex) && this.validatorStore.isDoppelgangerSafe(pubkeyHex);
148
+ });
149
+
150
+ this.logger.debug("Downloaded PTC duties", {epoch, dependentRoot, count: relevantDuties.length});
151
+
152
+ const dutiesAtEpoch = this.dutiesByIndexByEpoch.get(epoch);
153
+ const priorDependentRoot = dutiesAtEpoch?.dependentRoot;
154
+ const dependentRootChanged = priorDependentRoot !== undefined && priorDependentRoot !== dependentRoot;
155
+
156
+ if (!priorDependentRoot || dependentRootChanged) {
157
+ const dutiesByIndex = new Map<ValidatorIndex, routes.validator.PtcDuty>();
158
+ for (const duty of relevantDuties) {
159
+ dutiesByIndex.set(duty.validatorIndex, duty);
160
+ }
161
+ this.dutiesByIndexByEpoch.set(epoch, {dependentRoot, dutiesByIndex});
162
+
163
+ if (priorDependentRoot && dependentRootChanged) {
164
+ this.metrics?.ptcDutiesReorg.inc();
165
+ this.logger.warn("PTC duties re-org. This may happen from time to time", {
166
+ priorDependentRoot,
167
+ dependentRoot,
168
+ epoch,
169
+ });
170
+ }
171
+ } else {
172
+ const existingDuties = dutiesAtEpoch.dutiesByIndex;
173
+ const existingDutiesCount = existingDuties.size;
174
+ const discoveredNewDuties = relevantDuties.length > existingDutiesCount;
175
+
176
+ if (discoveredNewDuties) {
177
+ for (const duty of relevantDuties) {
178
+ if (!existingDuties.has(duty.validatorIndex)) {
179
+ existingDuties.set(duty.validatorIndex, duty);
180
+ }
181
+ }
182
+
183
+ this.logger.debug("Discovered new PTC duties", {
184
+ epoch,
185
+ dependentRoot,
186
+ count: relevantDuties.length - existingDutiesCount,
187
+ });
188
+ }
189
+ }
190
+ }
191
+
192
+ private onNewHead = async ({
193
+ slot,
194
+ previousDutyDependentRoot,
195
+ currentDutyDependentRoot,
196
+ }: HeadEventData): Promise<void> => {
197
+ const currentEpoch = computeEpochAtSlot(slot);
198
+ const nextEpoch = currentEpoch + 1;
199
+
200
+ const nextEpochDependentRoot = this.dutiesByIndexByEpoch.get(nextEpoch)?.dependentRoot;
201
+ if (nextEpochDependentRoot && currentDutyDependentRoot !== nextEpochDependentRoot) {
202
+ this.logger.warn("Potential next epoch PTC duties reorg", {
203
+ slot,
204
+ dutyEpoch: nextEpoch,
205
+ priorDependentRoot: nextEpochDependentRoot,
206
+ newDependentRoot: currentDutyDependentRoot,
207
+ });
208
+ await this.handlePtcDutiesReorg(nextEpoch, slot, nextEpochDependentRoot, currentDutyDependentRoot);
209
+ }
210
+
211
+ const currentEpochDependentRoot = this.dutiesByIndexByEpoch.get(currentEpoch)?.dependentRoot;
212
+ if (currentEpochDependentRoot && currentEpochDependentRoot !== previousDutyDependentRoot) {
213
+ this.logger.warn("Potential current epoch PTC duties reorg", {
214
+ slot,
215
+ dutyEpoch: currentEpoch,
216
+ priorDependentRoot: currentEpochDependentRoot,
217
+ newDependentRoot: previousDutyDependentRoot,
218
+ });
219
+ await this.handlePtcDutiesReorg(currentEpoch, slot, currentEpochDependentRoot, previousDutyDependentRoot);
220
+ }
221
+ };
222
+
223
+ private async handlePtcDutiesReorg(
224
+ dutyEpoch: Epoch,
225
+ slot: Slot,
226
+ oldDependentRoot: RootHex,
227
+ newDependentRoot: RootHex
228
+ ): Promise<void> {
229
+ this.metrics?.ptcDutiesReorg.inc();
230
+ const logContext = {dutyEpoch, slot, oldDependentRoot, newDependentRoot};
231
+ this.logger.debug("Redownload PTC duties", logContext);
232
+
233
+ await this.pollPtcDutiesForEpoch(dutyEpoch, this.validatorStore.getAllLocalIndices()).catch((e: Error) => {
234
+ this.logger.error("Failed to redownload PTC duties when reorg happens", logContext, e);
235
+ });
236
+ }
237
+
238
+ /** Run once per epoch to prune duties map. */
239
+ private pruneOldDuties(currentEpoch: Epoch): void {
240
+ for (const epoch of this.dutiesByIndexByEpoch.keys()) {
241
+ if (epoch + HISTORICAL_DUTIES_EPOCHS < currentEpoch) {
242
+ this.dutiesByIndexByEpoch.delete(epoch);
243
+ }
244
+ }
245
+ }
246
+ }
@@ -9,6 +9,7 @@ import {
9
9
  DOMAIN_BEACON_BUILDER,
10
10
  DOMAIN_BEACON_PROPOSER,
11
11
  DOMAIN_CONTRIBUTION_AND_PROOF,
12
+ DOMAIN_PTC_ATTESTER,
12
13
  DOMAIN_RANDAO,
13
14
  DOMAIN_SELECTION_PROOF,
14
15
  DOMAIN_SYNC_COMMITTEE,
@@ -496,11 +497,13 @@ export class ValidatorStore {
496
497
  logger?: LoggerVc
497
498
  ): Promise<gloas.SignedExecutionPayloadEnvelope> {
498
499
  // Make sure the envelope slot is not higher than the current slot to avoid potential attacks.
499
- if (envelope.slot > currentSlot) {
500
- throw Error(`Not signing envelope with slot ${envelope.slot} greater than current slot ${currentSlot}`);
500
+ if (envelope.payload.slotNumber > currentSlot) {
501
+ throw Error(
502
+ `Not signing envelope with slot ${envelope.payload.slotNumber} greater than current slot ${currentSlot}`
503
+ );
501
504
  }
502
505
 
503
- const signingSlot = envelope.slot;
506
+ const signingSlot = envelope.payload.slotNumber;
504
507
  const domain = this.config.getDomain(signingSlot, DOMAIN_BEACON_BUILDER);
505
508
  const signingRoot = computeSigningRoot(ssz.gloas.ExecutionPayloadEnvelope, envelope, domain);
506
509
 
@@ -666,6 +669,41 @@ export class ValidatorStore {
666
669
  };
667
670
  }
668
671
 
672
+ async signPayloadAttestation(
673
+ duty: routes.validator.PtcDuty,
674
+ data: gloas.PayloadAttestationData,
675
+ currentSlot: Slot,
676
+ logger?: LoggerVc
677
+ ): Promise<gloas.PayloadAttestationMessage> {
678
+ if (data.slot > currentSlot) {
679
+ throw Error(`Not signing payload attestation with slot ${data.slot} greater than current slot ${currentSlot}`);
680
+ }
681
+
682
+ this.assertDoppelgangerSafe(duty.pubkey);
683
+ this.validatePtcDuty(duty, data);
684
+
685
+ const signingSlot = data.slot;
686
+ const domain = this.config.getDomain(signingSlot, DOMAIN_PTC_ATTESTER);
687
+ const signingRoot = computeSigningRoot(ssz.gloas.PayloadAttestationData, data, domain);
688
+
689
+ logger?.debug("Signing payload attestation message", {
690
+ slot: signingSlot,
691
+ beaconBlockRoot: toRootHex(data.beaconBlockRoot),
692
+ signingRoot: toRootHex(signingRoot),
693
+ });
694
+
695
+ const signableMessage: SignableMessage = {
696
+ type: SignableMessageType.PAYLOAD_ATTESTATION,
697
+ data,
698
+ };
699
+
700
+ return {
701
+ validatorIndex: duty.validatorIndex,
702
+ data,
703
+ signature: await this.getSignature(duty.pubkey, signingRoot, signingSlot, signableMessage),
704
+ };
705
+ }
706
+
669
707
  async signAttestationSelectionProof(pubkey: BLSPubkeyMaybeHex, slot: Slot): Promise<BLSSignature> {
670
708
  const signingSlot = slot;
671
709
  const domain = this.config.getDomain(slot, DOMAIN_SELECTION_PROOF);
@@ -850,6 +888,12 @@ export class ValidatorStore {
850
888
  }
851
889
  }
852
890
 
891
+ private validatePtcDuty(duty: routes.validator.PtcDuty, data: gloas.PayloadAttestationData): void {
892
+ if (duty.slot !== data.slot) {
893
+ throw Error(`Inconsistent PTC duties during signing: duty.slot ${duty.slot} != data.slot ${data.slot}`);
894
+ }
895
+ }
896
+
853
897
  private assertDoppelgangerSafe(pubKey: PubkeyHex | BLSPubkey): void {
854
898
  const pubkeyHex = typeof pubKey === "string" ? pubKey : toPubkeyHex(pubKey);
855
899
  if (!this.isDoppelgangerSafe(pubkeyHex)) {
@@ -34,6 +34,7 @@ export enum SignableMessageType {
34
34
  SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF = "SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF",
35
35
  VALIDATOR_REGISTRATION = "VALIDATOR_REGISTRATION",
36
36
  EXECUTION_PAYLOAD_ENVELOPE = "EXECUTION_PAYLOAD_ENVELOPE",
37
+ PAYLOAD_ATTESTATION = "PAYLOAD_ATTESTATION",
37
38
  }
38
39
 
39
40
  const AggregationSlotType = new ContainerType({
@@ -83,7 +84,8 @@ export type SignableMessage =
83
84
  | {type: SignableMessageType.SYNC_COMMITTEE_SELECTION_PROOF; data: ValueOf<typeof SyncAggregatorSelectionDataType>}
84
85
  | {type: SignableMessageType.SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF; data: altair.ContributionAndProof}
85
86
  | {type: SignableMessageType.VALIDATOR_REGISTRATION; data: ValidatorRegistrationV1}
86
- | {type: SignableMessageType.EXECUTION_PAYLOAD_ENVELOPE; data: gloas.ExecutionPayloadEnvelope};
87
+ | {type: SignableMessageType.EXECUTION_PAYLOAD_ENVELOPE; data: gloas.ExecutionPayloadEnvelope}
88
+ | {type: SignableMessageType.PAYLOAD_ATTESTATION; data: gloas.PayloadAttestationData};
87
89
 
88
90
  const requiresForkInfo: Record<SignableMessageType, boolean> = {
89
91
  [SignableMessageType.AGGREGATION_SLOT]: true,
@@ -99,6 +101,7 @@ const requiresForkInfo: Record<SignableMessageType, boolean> = {
99
101
  [SignableMessageType.SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF]: true,
100
102
  [SignableMessageType.VALIDATOR_REGISTRATION]: false,
101
103
  [SignableMessageType.EXECUTION_PAYLOAD_ENVELOPE]: true,
104
+ [SignableMessageType.PAYLOAD_ATTESTATION]: true,
102
105
  };
103
106
 
104
107
  type Web3SignerSerializedRequest = {
@@ -273,6 +276,9 @@ function serializerSignableMessagePayload(config: BeaconConfig, payload: Signabl
273
276
 
274
277
  case SignableMessageType.EXECUTION_PAYLOAD_ENVELOPE:
275
278
  return {execution_payload_envelope: ssz.gloas.ExecutionPayloadEnvelope.toJson(payload.data)};
279
+
280
+ case SignableMessageType.PAYLOAD_ATTESTATION:
281
+ return {payload_attestation: ssz.gloas.PayloadAttestationData.toJson(payload.data)};
276
282
  }
277
283
  }
278
284
 
@@ -327,5 +327,8 @@ function getSpecCriticalParams(localConfig: ChainConfig): Record<keyof ConfigWit
327
327
  BUILDER_PENDING_WITHDRAWALS_LIMIT: gloasForkRelevant,
328
328
  MAX_BUILDERS_PER_WITHDRAWALS_SWEEP: gloasForkRelevant,
329
329
  MIN_BUILDER_WITHDRAWABILITY_DELAY: gloasForkRelevant,
330
+ CHURN_LIMIT_QUOTIENT_GLOAS: gloasForkRelevant,
331
+ CONSOLIDATION_CHURN_LIMIT_QUOTIENT: gloasForkRelevant,
332
+ MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT_GLOAS: gloasForkRelevant,
330
333
  };
331
334
  }
package/src/validator.ts CHANGED
@@ -15,6 +15,7 @@ import {ValidatorEventEmitter} from "./services/emitter.js";
15
15
  import {ExternalSignerOptions, pollExternalSignerPubkeys} from "./services/externalSignerSync.js";
16
16
  import {IndicesService} from "./services/indices.js";
17
17
  import {pollBuilderValidatorRegistration, pollPrepareBeaconProposer} from "./services/prepareBeaconProposer.js";
18
+ import {PtcService} from "./services/ptc.js";
18
19
  import {SyncCommitteeService} from "./services/syncCommittee.js";
19
20
  import {SyncingStatusTracker} from "./services/syncingStatusTracker.js";
20
21
  import {Signer, ValidatorProposerConfig, ValidatorStore, defaultOptions} from "./services/validatorStore.js";
@@ -30,6 +31,7 @@ export type ValidatorModules = {
30
31
  slashingProtection: ISlashingProtection;
31
32
  blockProposingService: BlockProposingService;
32
33
  attestationService: AttestationService;
34
+ ptcService: PtcService;
33
35
  syncCommitteeService: SyncCommitteeService;
34
36
  config: BeaconConfig;
35
37
  api: ApiClient;
@@ -84,6 +86,7 @@ export class Validator {
84
86
  private readonly slashingProtection: ISlashingProtection;
85
87
  private readonly blockProposingService: BlockProposingService;
86
88
  private readonly attestationService: AttestationService;
89
+ private readonly ptcService: PtcService;
87
90
  private readonly syncCommitteeService: SyncCommitteeService;
88
91
  private readonly config: BeaconConfig;
89
92
  private readonly api: ApiClient;
@@ -102,6 +105,7 @@ export class Validator {
102
105
  slashingProtection,
103
106
  blockProposingService,
104
107
  attestationService,
108
+ ptcService,
105
109
  syncCommitteeService,
106
110
  config,
107
111
  api,
@@ -118,6 +122,7 @@ export class Validator {
118
122
  this.slashingProtection = slashingProtection;
119
123
  this.blockProposingService = blockProposingService;
120
124
  this.attestationService = attestationService;
125
+ this.ptcService = ptcService;
121
126
  this.syncCommitteeService = syncCommitteeService;
122
127
  this.config = config;
123
128
  this.api = api;
@@ -225,7 +230,7 @@ export class Validator {
225
230
  // We set infinity to prevent MaxListenersExceededWarning which get logged when listeners > 10
226
231
  emitter.setMaxListeners(Infinity);
227
232
 
228
- const chainHeaderTracker = new ChainHeaderTracker(logger, api, emitter);
233
+ const chainHeaderTracker = new ChainHeaderTracker(config, logger, api, emitter);
229
234
  const syncingStatusTracker = new SyncingStatusTracker(logger, api, clock, metrics);
230
235
 
231
236
  const blockProposingService = new BlockProposingService(config, loggerVc, api, clock, validatorStore, metrics, {
@@ -249,6 +254,18 @@ export class Validator {
249
254
  }
250
255
  );
251
256
 
257
+ const ptcService = new PtcService(
258
+ config,
259
+ loggerVc,
260
+ api,
261
+ clock,
262
+ validatorStore,
263
+ emitter,
264
+ chainHeaderTracker,
265
+ syncingStatusTracker,
266
+ metrics
267
+ );
268
+
252
269
  const syncCommitteeService = new SyncCommitteeService(
253
270
  config,
254
271
  loggerVc,
@@ -272,6 +289,7 @@ export class Validator {
272
289
  slashingProtection,
273
290
  blockProposingService,
274
291
  attestationService,
292
+ ptcService,
275
293
  syncCommitteeService,
276
294
  config,
277
295
  api,
@@ -338,6 +356,7 @@ export class Validator {
338
356
  removeDutiesForKey(pubkey: PubkeyHex): void {
339
357
  this.blockProposingService.removeDutiesForKey(pubkey);
340
358
  this.attestationService.removeDutiesForKey(pubkey);
359
+ this.ptcService.removeDutiesForKey(pubkey);
341
360
  this.syncCommitteeService.removeDutiesForKey(pubkey);
342
361
  }
343
362