@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.
- package/lib/services/attestation.d.ts.map +1 -1
- package/lib/services/attestation.js +1 -2
- package/lib/services/attestation.js.map +1 -1
- package/lib/services/attestationDuties.d.ts.map +1 -1
- package/lib/services/attestationDuties.js +12 -4
- package/lib/services/attestationDuties.js.map +1 -1
- package/lib/services/block.d.ts.map +1 -1
- package/lib/services/block.js +1 -1
- package/lib/services/block.js.map +1 -1
- package/lib/services/blockDuties.d.ts.map +1 -1
- package/lib/services/blockDuties.js +3 -1
- package/lib/services/blockDuties.js.map +1 -1
- package/lib/services/chainHeaderTracker.d.ts.map +1 -1
- package/lib/services/chainHeaderTracker.js +6 -1
- package/lib/services/chainHeaderTracker.js.map +1 -1
- package/lib/services/emitter.d.ts +10 -1
- package/lib/services/emitter.d.ts.map +1 -1
- package/lib/services/emitter.js +4 -0
- package/lib/services/emitter.js.map +1 -1
- package/lib/services/externalSignerSync.d.ts.map +1 -1
- package/lib/services/externalSignerSync.js +1 -1
- package/lib/services/externalSignerSync.js.map +1 -1
- package/lib/services/inclusionList.d.ts +25 -0
- package/lib/services/inclusionList.d.ts.map +1 -0
- package/lib/services/inclusionList.js +101 -0
- package/lib/services/inclusionList.js.map +1 -0
- package/lib/services/inclusionListDuties.d.ts +59 -0
- package/lib/services/inclusionListDuties.d.ts.map +1 -0
- package/lib/services/inclusionListDuties.js +252 -0
- package/lib/services/inclusionListDuties.js.map +1 -0
- package/lib/services/indices.js +1 -1
- package/lib/services/indices.js.map +1 -1
- package/lib/services/ptc.d.ts.map +1 -1
- package/lib/services/ptc.js +3 -2
- package/lib/services/ptc.js.map +1 -1
- package/lib/services/ptcDuties.d.ts.map +1 -1
- package/lib/services/ptcDuties.js +5 -3
- package/lib/services/ptcDuties.js.map +1 -1
- package/lib/services/syncCommittee.js +1 -1
- package/lib/services/syncCommittee.js.map +1 -1
- package/lib/services/syncCommitteeDuties.d.ts.map +1 -1
- package/lib/services/syncCommitteeDuties.js +10 -5
- package/lib/services/syncCommitteeDuties.js.map +1 -1
- package/lib/services/validatorStore.d.ts +8 -2
- package/lib/services/validatorStore.d.ts.map +1 -1
- package/lib/services/validatorStore.js +29 -3
- package/lib/services/validatorStore.js.map +1 -1
- package/lib/slashingProtection/attestation/index.d.ts +1 -0
- package/lib/slashingProtection/attestation/index.d.ts.map +1 -1
- package/lib/slashingProtection/attestation/index.js +77 -44
- package/lib/slashingProtection/attestation/index.js.map +1 -1
- package/lib/slashingProtection/block/index.d.ts.map +1 -1
- package/lib/slashingProtection/block/index.js.map +1 -1
- package/lib/slashingProtection/index.d.ts +3 -1
- package/lib/slashingProtection/index.d.ts.map +1 -1
- package/lib/slashingProtection/index.js +48 -5
- package/lib/slashingProtection/index.js.map +1 -1
- package/lib/slashingProtection/utils.js +1 -1
- package/lib/slashingProtection/utils.js.map +1 -1
- package/lib/types.d.ts +1 -1
- package/lib/types.d.ts.map +1 -1
- package/lib/util/externalSignerClient.d.ts +7 -4
- package/lib/util/externalSignerClient.d.ts.map +1 -1
- package/lib/util/externalSignerClient.js +4 -0
- package/lib/util/externalSignerClient.js.map +1 -1
- package/lib/validator.d.ts +4 -1
- package/lib/validator.d.ts.map +1 -1
- package/lib/validator.js +11 -4
- package/lib/validator.js.map +1 -1
- package/package.json +14 -14
- package/src/services/attestation.ts +1 -2
- package/src/services/attestationDuties.ts +18 -4
- package/src/services/block.ts +6 -3
- package/src/services/blockDuties.ts +3 -1
- package/src/services/chainHeaderTracker.ts +8 -1
- package/src/services/emitter.ts +10 -0
- package/src/services/externalSignerSync.ts +1 -1
- package/src/services/inclusionList.ts +140 -0
- package/src/services/inclusionListDuties.ts +306 -0
- package/src/services/indices.ts +1 -1
- package/src/services/ptc.ts +6 -2
- package/src/services/ptcDuties.ts +6 -3
- package/src/services/syncCommittee.ts +1 -1
- package/src/services/syncCommitteeDuties.ts +13 -5
- package/src/services/validatorStore.ts +44 -4
- package/src/slashingProtection/attestation/index.ts +82 -46
- package/src/slashingProtection/block/index.ts +1 -0
- package/src/slashingProtection/index.ts +51 -7
- package/src/slashingProtection/utils.ts +1 -1
- package/src/types.ts +1 -1
- package/src/util/externalSignerClient.ts +10 -3
- 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
|
-
|
|
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
|
|
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
|
-
|
|
43
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
176
|
-
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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,
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
|
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,6 +1,6 @@
|
|
|
1
1
|
import {ContainerType, ValueOf} from "@chainsafe/ssz";
|
|
2
2
|
import {BeaconConfig} from "@lodestar/config";
|
|
3
|
-
import {ForkName, ForkPostGloas,
|
|
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
|
|
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 {
|
|
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:
|
|
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:
|
|
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
|
|