@lodestar/validator 1.50.1 → 1.51.0-dev.09763aa8d2

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 (92) hide show
  1. package/lib/services/attestation.d.ts.map +1 -1
  2. package/lib/services/attestation.js +1 -2
  3. package/lib/services/attestation.js.map +1 -1
  4. package/lib/services/attestationDuties.d.ts.map +1 -1
  5. package/lib/services/attestationDuties.js +12 -4
  6. package/lib/services/attestationDuties.js.map +1 -1
  7. package/lib/services/block.d.ts.map +1 -1
  8. package/lib/services/block.js +1 -1
  9. package/lib/services/block.js.map +1 -1
  10. package/lib/services/blockDuties.d.ts.map +1 -1
  11. package/lib/services/blockDuties.js +3 -1
  12. package/lib/services/blockDuties.js.map +1 -1
  13. package/lib/services/chainHeaderTracker.d.ts.map +1 -1
  14. package/lib/services/chainHeaderTracker.js +6 -1
  15. package/lib/services/chainHeaderTracker.js.map +1 -1
  16. package/lib/services/emitter.d.ts +10 -1
  17. package/lib/services/emitter.d.ts.map +1 -1
  18. package/lib/services/emitter.js +4 -0
  19. package/lib/services/emitter.js.map +1 -1
  20. package/lib/services/externalSignerSync.d.ts.map +1 -1
  21. package/lib/services/externalSignerSync.js +1 -1
  22. package/lib/services/externalSignerSync.js.map +1 -1
  23. package/lib/services/inclusionList.d.ts +25 -0
  24. package/lib/services/inclusionList.d.ts.map +1 -0
  25. package/lib/services/inclusionList.js +101 -0
  26. package/lib/services/inclusionList.js.map +1 -0
  27. package/lib/services/inclusionListDuties.d.ts +59 -0
  28. package/lib/services/inclusionListDuties.d.ts.map +1 -0
  29. package/lib/services/inclusionListDuties.js +252 -0
  30. package/lib/services/inclusionListDuties.js.map +1 -0
  31. package/lib/services/indices.js +1 -1
  32. package/lib/services/indices.js.map +1 -1
  33. package/lib/services/ptc.d.ts.map +1 -1
  34. package/lib/services/ptc.js +3 -2
  35. package/lib/services/ptc.js.map +1 -1
  36. package/lib/services/ptcDuties.d.ts.map +1 -1
  37. package/lib/services/ptcDuties.js +5 -3
  38. package/lib/services/ptcDuties.js.map +1 -1
  39. package/lib/services/syncCommittee.js +1 -1
  40. package/lib/services/syncCommittee.js.map +1 -1
  41. package/lib/services/syncCommitteeDuties.d.ts.map +1 -1
  42. package/lib/services/syncCommitteeDuties.js +10 -5
  43. package/lib/services/syncCommitteeDuties.js.map +1 -1
  44. package/lib/services/validatorStore.d.ts +8 -2
  45. package/lib/services/validatorStore.d.ts.map +1 -1
  46. package/lib/services/validatorStore.js +29 -3
  47. package/lib/services/validatorStore.js.map +1 -1
  48. package/lib/slashingProtection/attestation/index.d.ts +1 -0
  49. package/lib/slashingProtection/attestation/index.d.ts.map +1 -1
  50. package/lib/slashingProtection/attestation/index.js +77 -44
  51. package/lib/slashingProtection/attestation/index.js.map +1 -1
  52. package/lib/slashingProtection/block/index.d.ts.map +1 -1
  53. package/lib/slashingProtection/block/index.js.map +1 -1
  54. package/lib/slashingProtection/index.d.ts +3 -1
  55. package/lib/slashingProtection/index.d.ts.map +1 -1
  56. package/lib/slashingProtection/index.js +48 -5
  57. package/lib/slashingProtection/index.js.map +1 -1
  58. package/lib/slashingProtection/utils.js +1 -1
  59. package/lib/slashingProtection/utils.js.map +1 -1
  60. package/lib/types.d.ts +1 -1
  61. package/lib/types.d.ts.map +1 -1
  62. package/lib/util/externalSignerClient.d.ts +7 -4
  63. package/lib/util/externalSignerClient.d.ts.map +1 -1
  64. package/lib/util/externalSignerClient.js +4 -0
  65. package/lib/util/externalSignerClient.js.map +1 -1
  66. package/lib/validator.d.ts +4 -1
  67. package/lib/validator.d.ts.map +1 -1
  68. package/lib/validator.js +11 -4
  69. package/lib/validator.js.map +1 -1
  70. package/package.json +14 -14
  71. package/src/services/attestation.ts +1 -2
  72. package/src/services/attestationDuties.ts +18 -4
  73. package/src/services/block.ts +6 -3
  74. package/src/services/blockDuties.ts +3 -1
  75. package/src/services/chainHeaderTracker.ts +8 -1
  76. package/src/services/emitter.ts +10 -0
  77. package/src/services/externalSignerSync.ts +1 -1
  78. package/src/services/inclusionList.ts +140 -0
  79. package/src/services/inclusionListDuties.ts +306 -0
  80. package/src/services/indices.ts +1 -1
  81. package/src/services/ptc.ts +6 -2
  82. package/src/services/ptcDuties.ts +6 -3
  83. package/src/services/syncCommittee.ts +1 -1
  84. package/src/services/syncCommitteeDuties.ts +13 -5
  85. package/src/services/validatorStore.ts +44 -4
  86. package/src/slashingProtection/attestation/index.ts +82 -46
  87. package/src/slashingProtection/block/index.ts +1 -0
  88. package/src/slashingProtection/index.ts +51 -7
  89. package/src/slashingProtection/utils.ts +1 -1
  90. package/src/types.ts +1 -1
  91. package/src/util/externalSignerClient.ts +10 -3
  92. package/src/validator.ts +31 -3
@@ -1,8 +1,8 @@
1
- import {SecretKey} from "@chainsafe/lodestar-z/blst";
2
1
  import {BitArray} from "@chainsafe/ssz";
3
2
  import {routes} from "@lodestar/api";
4
3
  import {BuilderConfigData, BuilderEntryConfig} from "@lodestar/api/keymanager";
5
4
  import {BeaconConfig} from "@lodestar/config";
5
+ import {SecretKey} from "@lodestar/native/blst";
6
6
  import {
7
7
  DOMAIN_AGGREGATE_AND_PROOF,
8
8
  DOMAIN_APPLICATION_BUILDER,
@@ -11,6 +11,7 @@ import {
11
11
  DOMAIN_BEACON_PROPOSER,
12
12
  DOMAIN_BUILDER_REQUEST_AUTH,
13
13
  DOMAIN_CONTRIBUTION_AND_PROOF,
14
+ DOMAIN_INCLUSION_LIST_COMMITTEE,
14
15
  DOMAIN_PROPOSER_PREFERENCES,
15
16
  DOMAIN_PTC_ATTESTER,
16
17
  DOMAIN_RANDAO,
@@ -46,6 +47,7 @@ import {
46
47
  altair,
47
48
  bellatrix,
48
49
  gloas,
50
+ heze,
49
51
  phase0,
50
52
  ssz,
51
53
  } from "@lodestar/types";
@@ -633,7 +635,8 @@ export class ValidatorStore {
633
635
  removeSigner(pubkeyHex: PubkeyHex): boolean {
634
636
  this.doppelgangerService?.unregisterValidator(pubkeyHex);
635
637
 
636
- return this.indicesService.removeForKey(pubkeyHex) || this.validators.delete(pubkeyHex);
638
+ this.indicesService.removeForKey(pubkeyHex);
639
+ return this.validators.delete(pubkeyHex);
637
640
  }
638
641
 
639
642
  /** Return true if there is at least 1 pubkey registered */
@@ -809,6 +812,8 @@ export class ValidatorStore {
809
812
  ): Promise<SignedAggregateAndProof> {
810
813
  this.validateAttestationDuty(duty, aggregate.data);
811
814
 
815
+ this.assertDoppelgangerSafe(duty.pubkey);
816
+
812
817
  const aggregateAndProof: AggregateAndProof = {
813
818
  aggregate,
814
819
  aggregatorIndex: duty.validatorIndex,
@@ -929,8 +934,6 @@ export class ValidatorStore {
929
934
  throw Error(`Not signing proposer preferences for past slot ${duty.slot} (current ${currentSlot})`);
930
935
  }
931
936
 
932
- this.assertDoppelgangerSafe(duty.pubkey);
933
-
934
937
  const message: gloas.ProposerPreferences = {
935
938
  dependentRoot,
936
939
  proposalSlot: duty.slot,
@@ -954,6 +957,38 @@ export class ValidatorStore {
954
957
  };
955
958
  }
956
959
 
960
+ async signInclusionList(
961
+ duty: routes.validator.InclusionListDuty,
962
+ inclusionList: heze.InclusionList
963
+ ): Promise<heze.SignedInclusionList> {
964
+ if (duty.slot !== inclusionList.slot) {
965
+ throw Error(
966
+ `Inconsistent duties during signing: duty.slot ${duty.slot} != inclusionList.slot ${inclusionList.slot}`
967
+ );
968
+ }
969
+ if (duty.validatorIndex !== inclusionList.validatorIndex) {
970
+ throw Error(
971
+ `Inconsistent duties during signing: duty.validatorIndex ${duty.validatorIndex} != inclusionList.validatorIndex ${inclusionList.validatorIndex}`
972
+ );
973
+ }
974
+
975
+ this.assertDoppelgangerSafe(duty.pubkey);
976
+
977
+ const signingSlot = inclusionList.slot;
978
+ const domain = this.config.getDomain(signingSlot, DOMAIN_INCLUSION_LIST_COMMITTEE);
979
+ const signingRoot = computeSigningRoot(ssz.heze.InclusionList, inclusionList, domain);
980
+
981
+ const signableMessage: SignableMessage = {
982
+ type: SignableMessageType.INCLUSION_LIST,
983
+ data: inclusionList,
984
+ };
985
+
986
+ return {
987
+ message: inclusionList,
988
+ signature: await this.getSignature(duty.pubkey, signingRoot, signingSlot, signableMessage),
989
+ };
990
+ }
991
+
957
992
  async signAttestationSelectionProof(pubkey: BLSPubkeyMaybeHex, slot: Slot): Promise<BLSSignature> {
958
993
  const signingSlot = slot;
959
994
  const domain = this.config.getDomain(slot, DOMAIN_SELECTION_PROOF);
@@ -1214,6 +1249,11 @@ export class ValidatorStore {
1214
1249
  }
1215
1250
  }
1216
1251
 
1252
+ /**
1253
+ * Required before signing a message that is slashable or that the beacon node counts as validator
1254
+ * liveness (see `validatorSeenAtEpoch`), otherwise doppelganger detection would flag our own messages.
1255
+ * Skip it for anything else.
1256
+ */
1217
1257
  private assertDoppelgangerSafe(pubKey: PubkeyHex | BLSPubkey): void {
1218
1258
  const pubkeyHex = typeof pubKey === "string" ? pubKey : toPubkeyHex(pubKey);
1219
1259
  if (!this.isDoppelgangerSafe(pubkeyHex)) {
@@ -1,10 +1,12 @@
1
1
  import {BLSPubkey, Epoch} from "@lodestar/types";
2
+ import {defer, toPubkeyHex} from "@lodestar/utils";
2
3
  import {MinMaxSurround, SurroundAttestationError, SurroundAttestationErrorCode} from "../minMaxSurround/index.js";
3
4
  import {SlashingProtectionAttestation} from "../types.js";
4
5
  import {ZERO_ROOT, isEqualNonZeroRoot, isEqualRoot, minEpoch} from "../utils.js";
5
6
  import {AttestationByTargetRepository} from "./attestationByTargetRepository.js";
6
7
  import {AttestationLowerBoundRepository} from "./attestationLowerBoundRepository.js";
7
8
  import {InvalidAttestationError, InvalidAttestationErrorCode} from "./errors.js";
9
+
8
10
  export {
9
11
  AttestationByTargetRepository,
10
12
  AttestationLowerBoundRepository,
@@ -21,6 +23,7 @@ export class SlashingProtectionAttestationService {
21
23
  private attestationByTarget: AttestationByTargetRepository;
22
24
  private attestationLowerBound: AttestationLowerBoundRepository;
23
25
  private minMaxSurround: MinMaxSurround;
26
+ private readonly pendingAttestations = new Map<string, Promise<void>>();
24
27
 
25
28
  constructor(
26
29
  signedAttestationDb: AttestationByTargetRepository,
@@ -37,10 +40,25 @@ export class SlashingProtectionAttestationService {
37
40
  * This is the safe, externally-callable interface for checking attestations
38
41
  */
39
42
  async checkAndInsertAttestation(pubKey: BLSPubkey, attestation: SlashingProtectionAttestation): Promise<void> {
40
- const safeStatus = await this.checkAttestation(pubKey, attestation);
43
+ const pubkeyHex = toPubkeyHex(pubKey);
44
+ const previous = this.pendingAttestations.get(pubkeyHex);
45
+ const {promise, resolve} = defer<void>();
46
+ this.pendingAttestations.set(pubkeyHex, promise);
47
+
48
+ try {
49
+ if (previous !== undefined) {
50
+ await previous;
51
+ }
52
+ const safeStatus = await this.checkAttestation(pubKey, attestation);
41
53
 
42
- if (safeStatus !== SafeStatus.SAME_DATA) {
43
- await this.insertAttestation(pubKey, attestation);
54
+ if (safeStatus !== SafeStatus.SAME_DATA) {
55
+ await this.insertAttestation(pubKey, attestation);
56
+ }
57
+ } finally {
58
+ resolve();
59
+ if (this.pendingAttestations.get(pubkeyHex) === promise) {
60
+ this.pendingAttestations.delete(pubkeyHex);
61
+ }
44
62
  }
45
63
 
46
64
  // TODO: Implement safe clean-up of stored attestations
@@ -164,55 +182,73 @@ export class SlashingProtectionAttestationService {
164
182
  * Interchange import / export functionality
165
183
  */
166
184
  async importAttestations(pubkey: BLSPubkey, attestations: SlashingProtectionAttestation[]): Promise<void> {
167
- // Min-max surround misses a surround vote beyond its lookback, require the highest target attestation to
168
- // have the highest source epoch as `checkAttestation` relies on it
169
- let latestAtt = await this.attestationByTarget.getLatest(pubkey);
170
- let maxSourceAtt = latestAtt;
171
- for (const attestation of attestations) {
172
- if (latestAtt === null || attestation.targetEpoch >= latestAtt.targetEpoch) {
173
- latestAtt = attestation;
185
+ const pubkeyHex = toPubkeyHex(pubkey);
186
+ const previous = this.pendingAttestations.get(pubkeyHex);
187
+ const {promise, resolve} = defer<void>();
188
+ this.pendingAttestations.set(pubkeyHex, promise);
189
+
190
+ try {
191
+ if (previous !== undefined) {
192
+ await previous;
174
193
  }
175
- if (maxSourceAtt === null || attestation.sourceEpoch > maxSourceAtt.sourceEpoch) {
176
- maxSourceAtt = attestation;
194
+ // Min-max surround misses a surround vote beyond its lookback, require the highest target attestation to
195
+ // have the highest source epoch as `checkAttestation` relies on it
196
+ let latestAtt = await this.attestationByTarget.getLatest(pubkey);
197
+ let maxSourceAtt = latestAtt;
198
+ for (const attestation of attestations) {
199
+ if (latestAtt === null || attestation.targetEpoch >= latestAtt.targetEpoch) {
200
+ latestAtt = attestation;
201
+ }
202
+ if (maxSourceAtt === null || attestation.sourceEpoch > maxSourceAtt.sourceEpoch) {
203
+ maxSourceAtt = attestation;
204
+ }
177
205
  }
178
- }
179
- if (latestAtt && maxSourceAtt && latestAtt.sourceEpoch < maxSourceAtt.sourceEpoch) {
180
- throw new InvalidAttestationError({
181
- code: InvalidAttestationErrorCode.NEW_SURROUNDS_PREV,
182
- attestation: latestAtt,
183
- prev: maxSourceAtt,
184
- });
185
- }
186
-
187
- // Never replace a recorded attestation with a different source or signing root, a zero root refuses any attestation
188
- // with that target. The highest source epoch is kept as the latest attestation must have it, see above.
189
- const attestationsByTarget = new Map<Epoch, SlashingProtectionAttestation>();
190
- for (const attestation of attestations) {
191
- const {sourceEpoch, targetEpoch, signingRoot} = attestation;
192
- const prevAtt =
193
- attestationsByTarget.get(targetEpoch) ?? (await this.attestationByTarget.get(pubkey, targetEpoch));
194
- if (prevAtt === null || (prevAtt.sourceEpoch === sourceEpoch && isEqualRoot(prevAtt.signingRoot, signingRoot))) {
195
- attestationsByTarget.set(targetEpoch, attestation);
196
- } else {
197
- attestationsByTarget.set(targetEpoch, {
198
- sourceEpoch: Math.max(prevAtt.sourceEpoch, sourceEpoch),
199
- targetEpoch,
200
- signingRoot: ZERO_ROOT,
206
+ if (latestAtt && maxSourceAtt && latestAtt.sourceEpoch < maxSourceAtt.sourceEpoch) {
207
+ throw new InvalidAttestationError({
208
+ code: InvalidAttestationErrorCode.NEW_SURROUNDS_PREV,
209
+ attestation: latestAtt,
210
+ prev: maxSourceAtt,
201
211
  });
202
212
  }
203
- }
204
- await this.attestationByTarget.set(pubkey, Array.from(attestationsByTarget.values()));
205
213
 
206
- // Pre-compute spans for all attestations
207
- for (const attestation of attestations) {
208
- await this.minMaxSurround.insertAttestation(pubkey, attestation);
209
- }
214
+ // Never replace a recorded attestation with a different source or signing root, a zero root refuses any attestation
215
+ // with that target. The highest source epoch is kept as the latest attestation must have it, see above.
216
+ const attestationsByTarget = new Map<Epoch, SlashingProtectionAttestation>();
217
+ for (const attestation of attestations) {
218
+ const {sourceEpoch, targetEpoch, signingRoot} = attestation;
219
+ const prevAtt =
220
+ attestationsByTarget.get(targetEpoch) ?? (await this.attestationByTarget.get(pubkey, targetEpoch));
221
+ if (
222
+ prevAtt === null ||
223
+ (prevAtt.sourceEpoch === sourceEpoch && isEqualRoot(prevAtt.signingRoot, signingRoot))
224
+ ) {
225
+ attestationsByTarget.set(targetEpoch, attestation);
226
+ } else {
227
+ attestationsByTarget.set(targetEpoch, {
228
+ sourceEpoch: Math.max(prevAtt.sourceEpoch, sourceEpoch),
229
+ targetEpoch,
230
+ signingRoot: ZERO_ROOT,
231
+ });
232
+ }
233
+ }
234
+ await this.attestationByTarget.set(pubkey, Array.from(attestationsByTarget.values()));
235
+
236
+ // Pre-compute spans for all attestations
237
+ for (const attestation of attestations) {
238
+ await this.minMaxSurround.insertAttestation(pubkey, attestation);
239
+ }
210
240
 
211
- // Pre-compute and store lower-bound
212
- const minSourceEpoch = minEpoch(attestations.map((attestation) => attestation.sourceEpoch));
213
- const minTargetEpoch = minEpoch(attestations.map((attestation) => attestation.targetEpoch));
214
- if (minSourceEpoch != null && minTargetEpoch != null) {
215
- await this.attestationLowerBound.set(pubkey, {minSourceEpoch, minTargetEpoch});
241
+ // Pre-compute and store lower-bound
242
+ const minSourceEpoch = minEpoch(attestations.map((attestation) => attestation.sourceEpoch));
243
+ const minTargetEpoch = minEpoch(attestations.map((attestation) => attestation.targetEpoch));
244
+ if (minSourceEpoch != null && minTargetEpoch != null) {
245
+ await this.attestationLowerBound.set(pubkey, {minSourceEpoch, minTargetEpoch});
246
+ }
247
+ } finally {
248
+ resolve();
249
+ if (this.pendingAttestations.get(pubkeyHex) === promise) {
250
+ this.pendingAttestations.delete(pubkeyHex);
251
+ }
216
252
  }
217
253
  }
218
254
 
@@ -4,6 +4,7 @@ import {SlashingProtectionBlock} from "../types.js";
4
4
  import {ZERO_ROOT, isEqualNonZeroRoot, isEqualRoot} from "../utils.js";
5
5
  import {BlockBySlotRepository} from "./blockBySlotRepository.js";
6
6
  import {InvalidBlockError, InvalidBlockErrorCode} from "./errors.js";
7
+
7
8
  export {BlockBySlotRepository, InvalidBlockError, InvalidBlockErrorCode};
8
9
 
9
10
  enum SafeStatus {
@@ -1,8 +1,8 @@
1
1
  import {GENESIS_EPOCH} from "@lodestar/params";
2
2
  import {BLSPubkey, Epoch, Root} from "@lodestar/types";
3
- import {Logger, toPubkeyHex} from "@lodestar/utils";
3
+ import {Logger, defer, toPubkeyHex} from "@lodestar/utils";
4
4
  import {uniqueVectorArr} from "../slashingProtection/utils.js";
5
- import {LodestarValidatorDatabaseController} from "../types.js";
5
+ import {LodestarValidatorDatabaseController, PubkeyHex} from "../types.js";
6
6
  import {
7
7
  AttestationByTargetRepository,
8
8
  AttestationLowerBoundRepository,
@@ -26,7 +26,7 @@ export {InvalidAttestationError, InvalidAttestationErrorCode} from "./attestatio
26
26
  export {InvalidBlockError, InvalidBlockErrorCode} from "./block/index.js";
27
27
  export type {Interchange, InterchangeFormat} from "./interchange/index.js";
28
28
  export {InterchangeError, InterchangeErrorErrorCode} from "./interchange/index.js";
29
- export type {ISlashingProtection, InterchangeFormatVersion, SlashingProtectionBlock, SlashingProtectionAttestation};
29
+ export type {ISlashingProtection, InterchangeFormatVersion, SlashingProtectionAttestation, SlashingProtectionBlock};
30
30
  /**
31
31
  * Handles slashing protection for validator proposer and attester duties as well as slashing protection
32
32
  * during a validator interchange import/export process.
@@ -34,6 +34,8 @@ export type {ISlashingProtection, InterchangeFormatVersion, SlashingProtectionBl
34
34
  export class SlashingProtection implements ISlashingProtection {
35
35
  private blockService: SlashingProtectionBlockService;
36
36
  private attestationService: SlashingProtectionAttestationService;
37
+ private readonly importing = new Map<PubkeyHex, Promise<void>>();
38
+ private lastImport: Promise<void> | null = null;
37
39
 
38
40
  constructor(protected db: LodestarValidatorDatabaseController) {
39
41
  const blockBySlotRepository = new BlockBySlotRepository(db);
@@ -51,10 +53,26 @@ export class SlashingProtection implements ISlashingProtection {
51
53
  }
52
54
 
53
55
  async checkAndInsertBlockProposal(pubKey: BLSPubkey, block: SlashingProtectionBlock): Promise<void> {
56
+ if (this.importing.size !== 0) {
57
+ const pubkeyHex = toPubkeyHex(pubKey);
58
+ let importing = this.importing.get(pubkeyHex);
59
+ while (importing !== undefined) {
60
+ await importing;
61
+ importing = this.importing.get(pubkeyHex);
62
+ }
63
+ }
54
64
  await this.blockService.checkAndInsertBlockProposal(pubKey, block);
55
65
  }
56
66
 
57
67
  async checkAndInsertAttestation(pubKey: BLSPubkey, attestation: SlashingProtectionAttestation): Promise<void> {
68
+ if (this.importing.size !== 0) {
69
+ const pubkeyHex = toPubkeyHex(pubKey);
70
+ let importing = this.importing.get(pubkeyHex);
71
+ while (importing !== undefined) {
72
+ await importing;
73
+ importing = this.importing.get(pubkeyHex);
74
+ }
75
+ }
58
76
  await this.attestationService.checkAndInsertAttestation(pubKey, attestation);
59
77
  }
60
78
 
@@ -84,10 +102,36 @@ export class SlashingProtection implements ISlashingProtection {
84
102
  }
85
103
  }
86
104
  }
87
- for (const validator of data) {
88
- logger?.info("Importing slashing protection", {pubkey: toPubkeyHex(validator.pubkey)});
89
- await this.blockService.importBlocks(validator.pubkey, validator.signedBlocks);
90
- await this.attestationService.importAttestations(validator.pubkey, validator.signedAttestations);
105
+
106
+ // Checks for keys in the file wait for the whole import, a key that is already signing must not sign against
107
+ // a history the import is still extending. Imports run one after another so a later one cannot release a key early.
108
+ const pubkeyHexes = data.map((validator) => toPubkeyHex(validator.pubkey));
109
+ const previous = this.lastImport;
110
+ const {promise, resolve} = defer<void>();
111
+ this.lastImport = promise;
112
+ for (const pubkeyHex of pubkeyHexes) {
113
+ this.importing.set(pubkeyHex, promise);
114
+ }
115
+
116
+ try {
117
+ if (previous !== null) {
118
+ await previous;
119
+ }
120
+ for (const validator of data) {
121
+ logger?.info("Importing slashing protection", {pubkey: toPubkeyHex(validator.pubkey)});
122
+ await this.blockService.importBlocks(validator.pubkey, validator.signedBlocks);
123
+ await this.attestationService.importAttestations(validator.pubkey, validator.signedAttestations);
124
+ }
125
+ } finally {
126
+ resolve();
127
+ if (this.lastImport === promise) {
128
+ this.lastImport = null;
129
+ }
130
+ for (const pubkeyHex of pubkeyHexes) {
131
+ if (this.importing.get(pubkeyHex) === promise) {
132
+ this.importing.delete(pubkeyHex);
133
+ }
134
+ }
91
135
  }
92
136
  }
93
137
 
@@ -40,7 +40,7 @@ export function stringToNum(str: string): number {
40
40
  }
41
41
 
42
42
  export function minEpoch(epochs: Epoch[]): Epoch | null {
43
- return epochs.length > 0 ? epochs.reduce((minEpoch, epoch) => (minEpoch < epoch ? minEpoch : epoch)) : null;
43
+ return epochs.length > 0 ? epochs.reduce((minEpoch, epoch) => Math.min(minEpoch, epoch)) : null;
44
44
  }
45
45
 
46
46
  export function uniqueVectorArr(buffers: Uint8Array[]): Uint8Array[] {
package/src/types.ts CHANGED
@@ -1,5 +1,5 @@
1
- import {SecretKey} from "@chainsafe/lodestar-z/blst";
2
1
  import {DatabaseController} from "@lodestar/db";
2
+ import {SecretKey} from "@lodestar/native/blst";
3
3
  import {BLSPubkey} from "@lodestar/types";
4
4
 
5
5
  export type GenesisInfo = {
@@ -1,6 +1,6 @@
1
1
  import {ContainerType, ValueOf} from "@chainsafe/ssz";
2
2
  import {BeaconConfig} from "@lodestar/config";
3
- import {ForkName, ForkPostGloas, ForkPreBellatrix, ForkSeq, isForkPostDeneb} from "@lodestar/params";
3
+ import {ForkName, ForkPostGloas, ForkSeq, isForkPostDeneb} from "@lodestar/params";
4
4
  import {blindedOrFullBlockToHeader, computeEpochAtSlot} from "@lodestar/state-transition";
5
5
  import {
6
6
  AggregateAndProof,
@@ -12,6 +12,7 @@ import {
12
12
  Slot,
13
13
  altair,
14
14
  gloas,
15
+ heze,
15
16
  phase0,
16
17
  ssz,
17
18
  sszTypesFor,
@@ -37,6 +38,7 @@ export enum SignableMessageType {
37
38
  PAYLOAD_ATTESTATION_MESSAGE = "PAYLOAD_ATTESTATION_MESSAGE",
38
39
  PROPOSER_PREFERENCES = "PROPOSER_PREFERENCES",
39
40
  BUILDER_REQUEST_AUTH = "BUILDER_REQUEST_AUTH",
41
+ INCLUSION_LIST = "INCLUSION_LIST",
40
42
  }
41
43
 
42
44
  const AggregationSlotType = new ContainerType({
@@ -78,7 +80,7 @@ export type SignableMessage =
78
80
  | {type: SignableMessageType.AGGREGATE_AND_PROOF; data: phase0.AggregateAndProof}
79
81
  | {type: SignableMessageType.AGGREGATE_AND_PROOF_V2; data: AggregateAndProof}
80
82
  | {type: SignableMessageType.ATTESTATION; data: phase0.AttestationData}
81
- | {type: SignableMessageType.BLOCK_V2; data: BeaconBlock<ForkPreBellatrix> | BlindedBeaconBlock}
83
+ | {type: SignableMessageType.BLOCK_V2; data: BeaconBlock | BlindedBeaconBlock}
82
84
  | {type: SignableMessageType.DEPOSIT; data: ValueOf<typeof DepositType>}
83
85
  | {type: SignableMessageType.RANDAO_REVEAL; data: {epoch: Epoch}}
84
86
  | {type: SignableMessageType.VOLUNTARY_EXIT; data: phase0.VoluntaryExit}
@@ -89,7 +91,8 @@ export type SignableMessage =
89
91
  | {type: SignableMessageType.EXECUTION_PAYLOAD_ENVELOPE; data: gloas.ExecutionPayloadEnvelope}
90
92
  | {type: SignableMessageType.PAYLOAD_ATTESTATION_MESSAGE; data: gloas.PayloadAttestationData}
91
93
  | {type: SignableMessageType.PROPOSER_PREFERENCES; data: gloas.ProposerPreferences}
92
- | {type: SignableMessageType.BUILDER_REQUEST_AUTH; data: gloas.BuilderRequestAuth};
94
+ | {type: SignableMessageType.BUILDER_REQUEST_AUTH; data: gloas.BuilderRequestAuth}
95
+ | {type: SignableMessageType.INCLUSION_LIST; data: heze.InclusionList};
93
96
 
94
97
  const requiresForkInfo: Record<SignableMessageType, boolean> = {
95
98
  [SignableMessageType.AGGREGATION_SLOT]: true,
@@ -108,6 +111,7 @@ const requiresForkInfo: Record<SignableMessageType, boolean> = {
108
111
  [SignableMessageType.PAYLOAD_ATTESTATION_MESSAGE]: true,
109
112
  [SignableMessageType.PROPOSER_PREFERENCES]: true,
110
113
  [SignableMessageType.BUILDER_REQUEST_AUTH]: false,
114
+ [SignableMessageType.INCLUSION_LIST]: true,
111
115
  };
112
116
 
113
117
  type Web3SignerSerializedRequest = {
@@ -319,6 +323,9 @@ function serializerSignableMessagePayload(config: BeaconConfig, payload: Signabl
319
323
  },
320
324
  };
321
325
  }
326
+
327
+ case SignableMessageType.INCLUSION_LIST:
328
+ return {inclusion_list: ssz.heze.InclusionList.toJson(payload.data)};
322
329
  }
323
330
  }
324
331
 
package/src/validator.ts CHANGED
@@ -6,7 +6,14 @@ import {
6
6
  assertEqualParams,
7
7
  createBeaconConfig,
8
8
  } from "@lodestar/config";
9
- import {Clock, ClockOptions, IClock, computeEpochAtSlot, getCurrentSlot} from "@lodestar/state-transition";
9
+ import {
10
+ Clock,
11
+ ClockOptions,
12
+ IClock,
13
+ computeEpochAtSlot,
14
+ getCurrentSlot,
15
+ getSlotDurations,
16
+ } from "@lodestar/state-transition";
10
17
  import {BLSPubkey, phase0, ssz} from "@lodestar/types";
11
18
  import {Genesis} from "@lodestar/types/phase0";
12
19
  import {Logger, prettyGweiToEth, toPrintableUrl, toRootHex} from "@lodestar/utils";
@@ -20,6 +27,7 @@ import {ChainHeaderTracker} from "./services/chainHeaderTracker.js";
20
27
  import {DoppelgangerService} from "./services/doppelgangerService.js";
21
28
  import {ValidatorEventEmitter} from "./services/emitter.js";
22
29
  import {ExternalSignerOptions, pollExternalSignerPubkeys} from "./services/externalSignerSync.js";
30
+ import {InclusionListService} from "./services/inclusionList.js";
23
31
  import {IndicesService} from "./services/indices.js";
24
32
  import {pollBuilderValidatorRegistration, pollPrepareBeaconProposer} from "./services/prepareBeaconProposer.js";
25
33
  import {ProposalPreferencesService} from "./services/proposalPreferences.js";
@@ -46,6 +54,7 @@ export type ValidatorModules = {
46
54
  blockProposingService: BlockProposingService;
47
55
  attestationService: AttestationService;
48
56
  ptcService: PtcService;
57
+ inclusionListService: InclusionListService;
49
58
  syncCommitteeService: SyncCommitteeService;
50
59
  config: BeaconConfig;
51
60
  api: ApiClient;
@@ -102,6 +111,7 @@ export class Validator {
102
111
  private readonly blockProposingService: BlockProposingService;
103
112
  private readonly attestationService: AttestationService;
104
113
  private readonly ptcService: PtcService;
114
+ private readonly inclusionListService: InclusionListService;
105
115
  private readonly syncCommitteeService: SyncCommitteeService;
106
116
  private readonly config: BeaconConfig;
107
117
  private readonly api: ApiClient;
@@ -121,6 +131,7 @@ export class Validator {
121
131
  blockProposingService,
122
132
  attestationService,
123
133
  ptcService,
134
+ inclusionListService,
124
135
  syncCommitteeService,
125
136
  config,
126
137
  api,
@@ -138,6 +149,7 @@ export class Validator {
138
149
  this.blockProposingService = blockProposingService;
139
150
  this.attestationService = attestationService;
140
151
  this.ptcService = ptcService;
152
+ this.inclusionListService = inclusionListService;
141
153
  this.syncCommitteeService = syncCommitteeService;
142
154
  this.config = config;
143
155
  this.api = api;
@@ -202,7 +214,9 @@ export class Validator {
202
214
  globalInit: {
203
215
  signal: controller.signal,
204
216
  ...globalInit,
205
- timeoutMs: globalInit?.timeoutMs ?? config.SLOT_DURATION_MS,
217
+ timeoutMs:
218
+ globalInit?.timeoutMs ??
219
+ Math.min(...getSlotDurations(config).map(([, slotDurationMs]) => slotDurationMs)),
206
220
  },
207
221
  },
208
222
  {config, logger, metrics: metrics?.restApiClient}
@@ -303,6 +317,17 @@ export class Validator {
303
317
  metrics
304
318
  );
305
319
 
320
+ const inclusionListService = new InclusionListService(
321
+ config,
322
+ loggerVc,
323
+ api,
324
+ clock,
325
+ validatorStore,
326
+ chainHeaderTracker,
327
+ syncingStatusTracker,
328
+ emitter
329
+ );
330
+
306
331
  const syncCommitteeService = new SyncCommitteeService(
307
332
  config,
308
333
  loggerVc,
@@ -338,6 +363,7 @@ export class Validator {
338
363
  blockProposingService,
339
364
  attestationService,
340
365
  ptcService,
366
+ inclusionListService,
341
367
  syncCommitteeService,
342
368
  config,
343
369
  api,
@@ -366,7 +392,8 @@ export class Validator {
366
392
  urls: urls.map(toPrintableUrl).toString(),
367
393
  requestWireFormat: globalInit?.requestWireFormat ?? defaultInit.requestWireFormat,
368
394
  responseWireFormat: globalInit?.responseWireFormat ?? defaultInit.responseWireFormat,
369
- requestTimeoutMs: globalInit?.timeoutMs ?? config.SLOT_DURATION_MS,
395
+ requestTimeoutMs:
396
+ globalInit?.timeoutMs ?? Math.min(...getSlotDurations(config).map(([, slotDurationMs]) => slotDurationMs)),
370
397
  });
371
398
  } else {
372
399
  api = clientOrUrls;
@@ -423,6 +450,7 @@ export class Validator {
423
450
  this.blockProposingService.removeDutiesForKey(pubkey);
424
451
  this.attestationService.removeDutiesForKey(pubkey);
425
452
  this.ptcService.removeDutiesForKey(pubkey);
453
+ this.inclusionListService.removeDutiesForKey(pubkey);
426
454
  this.syncCommitteeService.removeDutiesForKey(pubkey);
427
455
  }
428
456