@lodestar/validator 1.35.0-dev.a70bac5bd3 → 1.35.0-dev.ba92bd8a88
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/buckets.d.ts.map +1 -0
- package/lib/defaults.d.ts.map +1 -0
- package/lib/genesis.d.ts.map +1 -0
- package/lib/index.d.ts +7 -7
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +5 -5
- package/lib/index.js.map +1 -1
- package/lib/metrics.d.ts.map +1 -0
- package/lib/repositories/index.d.ts.map +1 -0
- package/lib/repositories/metaDataRepository.d.ts.map +1 -0
- package/lib/repositories/metaDataRepository.js +4 -3
- package/lib/repositories/metaDataRepository.js.map +1 -1
- package/lib/services/attestation.d.ts.map +1 -0
- package/lib/services/attestation.js +53 -44
- package/lib/services/attestation.js.map +1 -1
- package/lib/services/attestationDuties.d.ts.map +1 -0
- package/lib/services/attestationDuties.js +104 -98
- package/lib/services/attestationDuties.js.map +1 -1
- package/lib/services/block.d.ts.map +1 -0
- package/lib/services/block.js +64 -56
- package/lib/services/block.js.map +1 -1
- package/lib/services/blockDuties.d.ts.map +1 -0
- package/lib/services/blockDuties.js +31 -23
- package/lib/services/blockDuties.js.map +1 -1
- package/lib/services/chainHeaderTracker.d.ts.map +1 -0
- package/lib/services/chainHeaderTracker.js +30 -27
- package/lib/services/chainHeaderTracker.js.map +1 -1
- package/lib/services/doppelgangerService.d.ts.map +1 -0
- package/lib/services/doppelgangerService.js +52 -45
- package/lib/services/doppelgangerService.js.map +1 -1
- package/lib/services/emitter.d.ts +1 -1
- package/lib/services/emitter.d.ts.map +1 -0
- package/lib/services/externalSignerSync.d.ts.map +1 -0
- package/lib/services/externalSignerSync.js.map +1 -1
- package/lib/services/indices.d.ts.map +1 -0
- package/lib/services/indices.js +8 -5
- package/lib/services/indices.js.map +1 -1
- package/lib/services/prepareBeaconProposer.d.ts.map +1 -0
- package/lib/services/prepareBeaconProposer.js.map +1 -1
- package/lib/services/syncCommittee.d.ts.map +1 -0
- package/lib/services/syncCommittee.js +60 -49
- package/lib/services/syncCommittee.js.map +1 -1
- package/lib/services/syncCommitteeDuties.d.ts.map +1 -0
- package/lib/services/syncCommitteeDuties.js +28 -23
- package/lib/services/syncCommitteeDuties.js.map +1 -1
- package/lib/services/syncingStatusTracker.d.ts.map +1 -0
- package/lib/services/syncingStatusTracker.js +32 -27
- package/lib/services/syncingStatusTracker.js.map +1 -1
- package/lib/services/utils.d.ts.map +1 -0
- package/lib/services/validatorStore.d.ts.map +1 -0
- package/lib/services/validatorStore.js +9 -3
- package/lib/services/validatorStore.js.map +1 -1
- package/lib/slashingProtection/attestation/attestationByTargetRepository.d.ts.map +1 -0
- package/lib/slashingProtection/attestation/attestationByTargetRepository.js +7 -3
- package/lib/slashingProtection/attestation/attestationByTargetRepository.js.map +1 -1
- package/lib/slashingProtection/attestation/attestationLowerBoundRepository.d.ts.map +1 -0
- package/lib/slashingProtection/attestation/attestationLowerBoundRepository.js +5 -3
- package/lib/slashingProtection/attestation/attestationLowerBoundRepository.js.map +1 -1
- package/lib/slashingProtection/attestation/errors.d.ts.map +1 -0
- package/lib/slashingProtection/attestation/index.d.ts.map +1 -0
- package/lib/slashingProtection/attestation/index.js +3 -0
- package/lib/slashingProtection/attestation/index.js.map +1 -1
- package/lib/slashingProtection/block/blockBySlotRepository.d.ts.map +1 -0
- package/lib/slashingProtection/block/blockBySlotRepository.js +7 -3
- package/lib/slashingProtection/block/blockBySlotRepository.js.map +1 -1
- package/lib/slashingProtection/block/errors.d.ts.map +1 -0
- package/lib/slashingProtection/block/index.d.ts.map +1 -0
- package/lib/slashingProtection/block/index.js +1 -0
- package/lib/slashingProtection/block/index.js.map +1 -1
- package/lib/slashingProtection/index.d.ts +1 -1
- package/lib/slashingProtection/index.d.ts.map +1 -0
- package/lib/slashingProtection/index.js +3 -0
- package/lib/slashingProtection/index.js.map +1 -1
- package/lib/slashingProtection/interchange/errors.d.ts.map +1 -0
- package/lib/slashingProtection/interchange/formats/completeV4.d.ts.map +1 -0
- package/lib/slashingProtection/interchange/formats/index.d.ts.map +1 -0
- package/lib/slashingProtection/interchange/formats/v5.d.ts.map +1 -0
- package/lib/slashingProtection/interchange/index.d.ts.map +1 -0
- package/lib/slashingProtection/interchange/parseInterchange.d.ts.map +1 -0
- package/lib/slashingProtection/interchange/serializeInterchange.d.ts.map +1 -0
- package/lib/slashingProtection/interchange/types.d.ts.map +1 -0
- package/lib/slashingProtection/interface.d.ts.map +1 -0
- package/lib/slashingProtection/minMaxSurround/distanceStoreRepository.d.ts.map +1 -0
- package/lib/slashingProtection/minMaxSurround/distanceStoreRepository.js +8 -0
- package/lib/slashingProtection/minMaxSurround/distanceStoreRepository.js.map +1 -1
- package/lib/slashingProtection/minMaxSurround/errors.d.ts.map +1 -0
- package/lib/slashingProtection/minMaxSurround/index.d.ts.map +1 -0
- package/lib/slashingProtection/minMaxSurround/interface.d.ts.map +1 -0
- package/lib/slashingProtection/minMaxSurround/minMaxSurround.d.ts.map +1 -0
- package/lib/slashingProtection/minMaxSurround/minMaxSurround.js +2 -0
- package/lib/slashingProtection/minMaxSurround/minMaxSurround.js.map +1 -1
- package/lib/slashingProtection/types.d.ts.map +1 -0
- package/lib/slashingProtection/utils.d.ts +1 -1
- package/lib/slashingProtection/utils.d.ts.map +1 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/util/batch.d.ts.map +1 -0
- package/lib/util/clock.d.ts.map +1 -0
- package/lib/util/clock.js +5 -1
- package/lib/util/clock.js.map +1 -1
- package/lib/util/difference.d.ts.map +1 -0
- package/lib/util/externalSignerClient.d.ts.map +1 -0
- package/lib/util/format.d.ts.map +1 -0
- package/lib/util/index.d.ts.map +1 -0
- package/lib/util/logger.d.ts.map +1 -0
- package/lib/util/params.d.ts.map +1 -0
- package/lib/util/params.js +9 -0
- package/lib/util/params.js.map +1 -1
- package/lib/util/url.d.ts.map +1 -0
- package/lib/validator.d.ts.map +1 -0
- package/lib/validator.js +15 -0
- package/lib/validator.js.map +1 -1
- package/package.json +19 -16
- package/src/buckets.ts +30 -0
- package/src/defaults.ts +8 -0
- package/src/genesis.ts +19 -0
- package/src/index.ts +22 -0
- package/src/metrics.ts +417 -0
- package/src/repositories/index.ts +1 -0
- package/src/repositories/metaDataRepository.ts +42 -0
- package/src/services/attestation.ts +349 -0
- package/src/services/attestationDuties.ts +405 -0
- package/src/services/block.ts +261 -0
- package/src/services/blockDuties.ts +215 -0
- package/src/services/chainHeaderTracker.ts +89 -0
- package/src/services/doppelgangerService.ts +286 -0
- package/src/services/emitter.ts +43 -0
- package/src/services/externalSignerSync.ts +81 -0
- package/src/services/indices.ts +165 -0
- package/src/services/prepareBeaconProposer.ts +119 -0
- package/src/services/syncCommittee.ts +317 -0
- package/src/services/syncCommitteeDuties.ts +337 -0
- package/src/services/syncingStatusTracker.ts +74 -0
- package/src/services/utils.ts +58 -0
- package/src/services/validatorStore.ts +830 -0
- package/src/slashingProtection/attestation/attestationByTargetRepository.ts +77 -0
- package/src/slashingProtection/attestation/attestationLowerBoundRepository.ts +44 -0
- package/src/slashingProtection/attestation/errors.ts +66 -0
- package/src/slashingProtection/attestation/index.ts +171 -0
- package/src/slashingProtection/block/blockBySlotRepository.ts +78 -0
- package/src/slashingProtection/block/errors.ts +28 -0
- package/src/slashingProtection/block/index.ts +94 -0
- package/src/slashingProtection/index.ts +95 -0
- package/src/slashingProtection/interchange/errors.ts +15 -0
- package/src/slashingProtection/interchange/formats/completeV4.ts +125 -0
- package/src/slashingProtection/interchange/formats/index.ts +7 -0
- package/src/slashingProtection/interchange/formats/v5.ts +120 -0
- package/src/slashingProtection/interchange/index.ts +5 -0
- package/src/slashingProtection/interchange/parseInterchange.ts +55 -0
- package/src/slashingProtection/interchange/serializeInterchange.ts +35 -0
- package/src/slashingProtection/interchange/types.ts +18 -0
- package/src/slashingProtection/interface.ts +28 -0
- package/src/slashingProtection/minMaxSurround/distanceStoreRepository.ts +57 -0
- package/src/slashingProtection/minMaxSurround/errors.ts +27 -0
- package/src/slashingProtection/minMaxSurround/index.ts +4 -0
- package/src/slashingProtection/minMaxSurround/interface.ts +23 -0
- package/src/slashingProtection/minMaxSurround/minMaxSurround.ts +104 -0
- package/src/slashingProtection/types.ts +12 -0
- package/src/slashingProtection/utils.ts +42 -0
- package/src/types.ts +31 -0
- package/src/util/batch.ts +15 -0
- package/src/util/clock.ts +164 -0
- package/src/util/difference.ts +10 -0
- package/src/util/externalSignerClient.ts +277 -0
- package/src/util/format.ts +3 -0
- package/src/util/index.ts +6 -0
- package/src/util/logger.ts +51 -0
- package/src/util/params.ts +313 -0
- package/src/util/url.ts +16 -0
- package/src/validator.ts +418 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {ContainerType, Type} from "@chainsafe/ssz";
|
|
2
|
+
import {DB_PREFIX_LENGTH, DbReqOpts, encodeKey, uintLen} from "@lodestar/db";
|
|
3
|
+
import {BLSPubkey, Epoch, ssz} from "@lodestar/types";
|
|
4
|
+
import {bytesToInt, intToBytes} from "@lodestar/utils";
|
|
5
|
+
import {Bucket, getBucketNameByValue} from "../../buckets.js";
|
|
6
|
+
import {LodestarValidatorDatabaseController} from "../../types.js";
|
|
7
|
+
import {SlashingProtectionAttestation} from "../types.js";
|
|
8
|
+
import {blsPubkeyLen, uniqueVectorArr} from "../utils.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Manages validator db storage of attestations.
|
|
12
|
+
* Entries in the db are indexed by an encoded key which combines the validator's public key and the
|
|
13
|
+
* attestation's target epoch.
|
|
14
|
+
*/
|
|
15
|
+
export class AttestationByTargetRepository {
|
|
16
|
+
protected type: Type<SlashingProtectionAttestation>;
|
|
17
|
+
protected bucket = Bucket.slashingProtectionAttestationByTarget;
|
|
18
|
+
|
|
19
|
+
private readonly bucketId = getBucketNameByValue(this.bucket);
|
|
20
|
+
private readonly dbReqOpts: DbReqOpts = {bucketId: this.bucketId};
|
|
21
|
+
private readonly minKey: Uint8Array;
|
|
22
|
+
private readonly maxKey: Uint8Array;
|
|
23
|
+
|
|
24
|
+
constructor(protected db: LodestarValidatorDatabaseController) {
|
|
25
|
+
this.type = new ContainerType({
|
|
26
|
+
sourceEpoch: ssz.Epoch,
|
|
27
|
+
targetEpoch: ssz.Epoch,
|
|
28
|
+
signingRoot: ssz.Root,
|
|
29
|
+
}); // casing doesn't matter
|
|
30
|
+
this.minKey = encodeKey(this.bucket, Buffer.alloc(0));
|
|
31
|
+
this.maxKey = encodeKey(this.bucket + 1, Buffer.alloc(0));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async getAll(pubkey: BLSPubkey, limit?: number): Promise<SlashingProtectionAttestation[]> {
|
|
35
|
+
const attestations = await this.db.values({
|
|
36
|
+
limit,
|
|
37
|
+
gte: this.encodeKey(pubkey, 0),
|
|
38
|
+
lt: this.encodeKey(pubkey, Number.MAX_SAFE_INTEGER),
|
|
39
|
+
bucketId: this.bucketId,
|
|
40
|
+
});
|
|
41
|
+
return attestations.map((attestation) => this.type.deserialize(attestation));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async get(pubkey: BLSPubkey, targetEpoch: Epoch): Promise<SlashingProtectionAttestation | null> {
|
|
45
|
+
const att = await this.db.get(this.encodeKey(pubkey, targetEpoch), this.dbReqOpts);
|
|
46
|
+
return att && this.type.deserialize(att);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async set(pubkey: BLSPubkey, atts: SlashingProtectionAttestation[]): Promise<void> {
|
|
50
|
+
await this.db.batchPut(
|
|
51
|
+
atts.map((att) => ({
|
|
52
|
+
key: this.encodeKey(pubkey, att.targetEpoch),
|
|
53
|
+
value: this.type.serialize(att),
|
|
54
|
+
})),
|
|
55
|
+
this.dbReqOpts
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async listPubkeys(): Promise<BLSPubkey[]> {
|
|
60
|
+
const keys = await this.db.keys({gte: this.minKey, lt: this.maxKey, bucketId: this.bucketId});
|
|
61
|
+
return uniqueVectorArr(keys.map((key) => this.decodeKey(key).pubkey));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private encodeKey(pubkey: BLSPubkey, targetEpoch: Epoch): Uint8Array {
|
|
65
|
+
return encodeKey(this.bucket, Buffer.concat([pubkey, intToBytes(BigInt(targetEpoch), uintLen, "be")]));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private decodeKey(key: Uint8Array): {pubkey: BLSPubkey; targetEpoch: Epoch} {
|
|
69
|
+
return {
|
|
70
|
+
pubkey: key.slice(DB_PREFIX_LENGTH, DB_PREFIX_LENGTH + blsPubkeyLen),
|
|
71
|
+
targetEpoch: bytesToInt(
|
|
72
|
+
key.slice(DB_PREFIX_LENGTH + blsPubkeyLen, DB_PREFIX_LENGTH + blsPubkeyLen + uintLen),
|
|
73
|
+
"be"
|
|
74
|
+
),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {ContainerType, Type} from "@chainsafe/ssz";
|
|
2
|
+
import {DbReqOpts, encodeKey} from "@lodestar/db";
|
|
3
|
+
import {BLSPubkey, Epoch, ssz} from "@lodestar/types";
|
|
4
|
+
import {Bucket, getBucketNameByValue} from "../../buckets.js";
|
|
5
|
+
import {LodestarValidatorDatabaseController} from "../../types.js";
|
|
6
|
+
|
|
7
|
+
// Only used locally here
|
|
8
|
+
export interface SlashingProtectionLowerBound {
|
|
9
|
+
minSourceEpoch: Epoch;
|
|
10
|
+
minTargetEpoch: Epoch;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Manages validator db storage of the minimum source and target epochs required of a validator
|
|
15
|
+
* attestation.
|
|
16
|
+
*/
|
|
17
|
+
export class AttestationLowerBoundRepository {
|
|
18
|
+
protected type: Type<SlashingProtectionLowerBound>;
|
|
19
|
+
protected bucket = Bucket.slashingProtectionAttestationLowerBound;
|
|
20
|
+
|
|
21
|
+
private readonly bucketId = getBucketNameByValue(this.bucket);
|
|
22
|
+
private readonly dbReqOpts: DbReqOpts = {bucketId: this.bucketId};
|
|
23
|
+
|
|
24
|
+
constructor(protected db: LodestarValidatorDatabaseController) {
|
|
25
|
+
this.type = new ContainerType({
|
|
26
|
+
minSourceEpoch: ssz.Epoch,
|
|
27
|
+
minTargetEpoch: ssz.Epoch,
|
|
28
|
+
}); // casing doesn't matter
|
|
29
|
+
this.dbReqOpts = {bucketId: this.bucketId};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async get(pubkey: BLSPubkey): Promise<SlashingProtectionLowerBound | null> {
|
|
33
|
+
const att = await this.db.get(this.encodeKey(pubkey), this.dbReqOpts);
|
|
34
|
+
return att && this.type.deserialize(att);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async set(pubkey: BLSPubkey, value: SlashingProtectionLowerBound): Promise<void> {
|
|
38
|
+
await this.db.put(this.encodeKey(pubkey), this.type.serialize(value), this.dbReqOpts);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private encodeKey(pubkey: BLSPubkey): Uint8Array {
|
|
42
|
+
return encodeKey(this.bucket, pubkey);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {Epoch} from "@lodestar/types";
|
|
2
|
+
import {LodestarError} from "@lodestar/utils";
|
|
3
|
+
import {SlashingProtectionAttestation} from "../types.js";
|
|
4
|
+
|
|
5
|
+
export enum InvalidAttestationErrorCode {
|
|
6
|
+
/**
|
|
7
|
+
* The attestation has the same target epoch as an attestation from the DB
|
|
8
|
+
*/
|
|
9
|
+
DOUBLE_VOTE = "ERR_INVALID_ATTESTATION_DOUBLE_VOTE",
|
|
10
|
+
/**
|
|
11
|
+
* The attestation surrounds an existing attestation from the database `prev`
|
|
12
|
+
*/
|
|
13
|
+
NEW_SURROUNDS_PREV = "ERR_INVALID_ATTESTATION_NEW_SURROUNDS_PREV",
|
|
14
|
+
/**
|
|
15
|
+
* The attestation is surrounded by an existing attestation from the database `prev`
|
|
16
|
+
*/
|
|
17
|
+
PREV_SURROUNDS_NEW = "ERR_INVALID_ATTESTATION_PREV_SURROUNDS_NEW",
|
|
18
|
+
/**
|
|
19
|
+
* The attestation is invalid because its source epoch is greater than its target epoch
|
|
20
|
+
*/
|
|
21
|
+
SOURCE_EXCEEDS_TARGET = "ERR_INVALID_ATTESTATION_SOURCE_EXCEEDS_TARGET",
|
|
22
|
+
/**
|
|
23
|
+
* The attestation is invalid because its source epoch is less than the lower bound on source
|
|
24
|
+
* epochs for this validator.
|
|
25
|
+
*/
|
|
26
|
+
SOURCE_LESS_THAN_LOWER_BOUND = "ERR_INVALID_ATTESTATION_SOURCE_LESS_THAN_LOWER_BOUND",
|
|
27
|
+
/**
|
|
28
|
+
* The attestation is invalid because its target epoch is less than or equal to the lower
|
|
29
|
+
* bound on target epochs for this validator.
|
|
30
|
+
*/
|
|
31
|
+
TARGET_LESS_THAN_OR_EQ_LOWER_BOUND = "ERR_INVALID_ATTESTATION_TARGET_LESS_THAN_OR_EQ_LOWER_BOUND",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type InvalidAttestationErrorType =
|
|
35
|
+
| {
|
|
36
|
+
code: InvalidAttestationErrorCode.DOUBLE_VOTE;
|
|
37
|
+
attestation: SlashingProtectionAttestation;
|
|
38
|
+
prev: SlashingProtectionAttestation;
|
|
39
|
+
}
|
|
40
|
+
| {
|
|
41
|
+
code: InvalidAttestationErrorCode.NEW_SURROUNDS_PREV;
|
|
42
|
+
attestation: SlashingProtectionAttestation;
|
|
43
|
+
// Since using min-max surround, the actual attestation may not be available
|
|
44
|
+
prev: SlashingProtectionAttestation | null;
|
|
45
|
+
}
|
|
46
|
+
| {
|
|
47
|
+
code: InvalidAttestationErrorCode.PREV_SURROUNDS_NEW;
|
|
48
|
+
attestation: SlashingProtectionAttestation;
|
|
49
|
+
// Since using min-max surround, the actual attestation may not be available
|
|
50
|
+
prev: SlashingProtectionAttestation | null;
|
|
51
|
+
}
|
|
52
|
+
| {
|
|
53
|
+
code: InvalidAttestationErrorCode.SOURCE_EXCEEDS_TARGET;
|
|
54
|
+
}
|
|
55
|
+
| {
|
|
56
|
+
code: InvalidAttestationErrorCode.SOURCE_LESS_THAN_LOWER_BOUND;
|
|
57
|
+
sourceEpoch: Epoch;
|
|
58
|
+
minSourceEpoch: Epoch;
|
|
59
|
+
}
|
|
60
|
+
| {
|
|
61
|
+
code: InvalidAttestationErrorCode.TARGET_LESS_THAN_OR_EQ_LOWER_BOUND;
|
|
62
|
+
targetEpoch: Epoch;
|
|
63
|
+
minTargetEpoch: Epoch;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export class InvalidAttestationError extends LodestarError<InvalidAttestationErrorType> {}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import {BLSPubkey, Epoch} from "@lodestar/types";
|
|
2
|
+
import {MinMaxSurround, SurroundAttestationError, SurroundAttestationErrorCode} from "../minMaxSurround/index.js";
|
|
3
|
+
import {SlashingProtectionAttestation} from "../types.js";
|
|
4
|
+
import {isEqualNonZeroRoot, minEpoch} from "../utils.js";
|
|
5
|
+
import {AttestationByTargetRepository} from "./attestationByTargetRepository.js";
|
|
6
|
+
import {AttestationLowerBoundRepository} from "./attestationLowerBoundRepository.js";
|
|
7
|
+
import {InvalidAttestationError, InvalidAttestationErrorCode} from "./errors.js";
|
|
8
|
+
export {
|
|
9
|
+
AttestationByTargetRepository,
|
|
10
|
+
AttestationLowerBoundRepository,
|
|
11
|
+
InvalidAttestationError,
|
|
12
|
+
InvalidAttestationErrorCode,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
enum SafeStatus {
|
|
16
|
+
SAME_DATA = "SAFE_STATUS_SAME_DATA",
|
|
17
|
+
OK = "SAFE_STATUS_OK",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class SlashingProtectionAttestationService {
|
|
21
|
+
private attestationByTarget: AttestationByTargetRepository;
|
|
22
|
+
private attestationLowerBound: AttestationLowerBoundRepository;
|
|
23
|
+
private minMaxSurround: MinMaxSurround;
|
|
24
|
+
|
|
25
|
+
constructor(
|
|
26
|
+
signedAttestationDb: AttestationByTargetRepository,
|
|
27
|
+
attestationLowerBound: AttestationLowerBoundRepository,
|
|
28
|
+
minMaxSurround: MinMaxSurround
|
|
29
|
+
) {
|
|
30
|
+
this.attestationByTarget = signedAttestationDb;
|
|
31
|
+
this.attestationLowerBound = attestationLowerBound;
|
|
32
|
+
this.minMaxSurround = minMaxSurround;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Check an attestation for slash safety, and if it is safe, record it in the database
|
|
37
|
+
* This is the safe, externally-callable interface for checking attestations
|
|
38
|
+
*/
|
|
39
|
+
async checkAndInsertAttestation(pubKey: BLSPubkey, attestation: SlashingProtectionAttestation): Promise<void> {
|
|
40
|
+
const safeStatus = await this.checkAttestation(pubKey, attestation);
|
|
41
|
+
|
|
42
|
+
if (safeStatus !== SafeStatus.SAME_DATA) {
|
|
43
|
+
await this.insertAttestation(pubKey, attestation);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// TODO: Implement safe clean-up of stored attestations
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check an attestation from `pubKey` for slash safety.
|
|
51
|
+
*/
|
|
52
|
+
async checkAttestation(pubKey: BLSPubkey, attestation: SlashingProtectionAttestation): Promise<SafeStatus> {
|
|
53
|
+
// Although it's not required to avoid slashing, we disallow attestations
|
|
54
|
+
// which are obviously invalid by virtue of their source epoch exceeding their target.
|
|
55
|
+
if (attestation.sourceEpoch > attestation.targetEpoch) {
|
|
56
|
+
throw new InvalidAttestationError({code: InvalidAttestationErrorCode.SOURCE_EXCEEDS_TARGET});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check for a double vote. Namely, an existing attestation with the same target epoch,
|
|
60
|
+
// and a different signing root.
|
|
61
|
+
const sameTargetAtt = await this.attestationByTarget.get(pubKey, attestation.targetEpoch);
|
|
62
|
+
if (sameTargetAtt) {
|
|
63
|
+
// Interchange format allows for attestations without signing_root, then assume root is equal
|
|
64
|
+
if (isEqualNonZeroRoot(sameTargetAtt.signingRoot, attestation.signingRoot)) {
|
|
65
|
+
return SafeStatus.SAME_DATA;
|
|
66
|
+
}
|
|
67
|
+
throw new InvalidAttestationError({
|
|
68
|
+
code: InvalidAttestationErrorCode.DOUBLE_VOTE,
|
|
69
|
+
attestation: attestation,
|
|
70
|
+
prev: sameTargetAtt,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Check for a surround vote
|
|
75
|
+
try {
|
|
76
|
+
await this.minMaxSurround.assertNoSurround(pubKey, attestation);
|
|
77
|
+
} catch (e) {
|
|
78
|
+
if (e instanceof SurroundAttestationError) {
|
|
79
|
+
const prev = await this.attestationByTarget.get(pubKey, e.type.attestation2Target).catch(() => null);
|
|
80
|
+
switch (e.type.code) {
|
|
81
|
+
case SurroundAttestationErrorCode.IS_SURROUNDING:
|
|
82
|
+
throw new InvalidAttestationError({
|
|
83
|
+
code: InvalidAttestationErrorCode.NEW_SURROUNDS_PREV,
|
|
84
|
+
attestation,
|
|
85
|
+
prev,
|
|
86
|
+
});
|
|
87
|
+
case SurroundAttestationErrorCode.IS_SURROUNDED:
|
|
88
|
+
throw new InvalidAttestationError({
|
|
89
|
+
code: InvalidAttestationErrorCode.PREV_SURROUNDS_NEW,
|
|
90
|
+
attestation,
|
|
91
|
+
prev,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
throw e;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Refuse to sign any attestation with:
|
|
99
|
+
// - source.epoch < min(att.source_epoch for att in data.signed_attestations if att.pubkey == attester_pubkey), OR
|
|
100
|
+
// - target_epoch <= min(att.target_epoch for att in data.signed_attestations if att.pubkey == attester_pubkey)
|
|
101
|
+
// (spec v4, Slashing Protection Database Interchange Format)
|
|
102
|
+
const attestationLowerBound = await this.attestationLowerBound.get(pubKey);
|
|
103
|
+
if (attestationLowerBound) {
|
|
104
|
+
const {minSourceEpoch, minTargetEpoch} = attestationLowerBound;
|
|
105
|
+
if (attestation.sourceEpoch < minSourceEpoch) {
|
|
106
|
+
throw new InvalidAttestationError({
|
|
107
|
+
code: InvalidAttestationErrorCode.SOURCE_LESS_THAN_LOWER_BOUND,
|
|
108
|
+
sourceEpoch: attestation.sourceEpoch,
|
|
109
|
+
minSourceEpoch,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (attestation.targetEpoch <= minTargetEpoch) {
|
|
114
|
+
throw new InvalidAttestationError({
|
|
115
|
+
code: InvalidAttestationErrorCode.TARGET_LESS_THAN_OR_EQ_LOWER_BOUND,
|
|
116
|
+
targetEpoch: attestation.targetEpoch,
|
|
117
|
+
minTargetEpoch,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return SafeStatus.OK;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Insert an attestation into the slashing database
|
|
127
|
+
* This should *only* be called in the same (exclusive) transaction as `checkAttestation`
|
|
128
|
+
* so that the check isn't invalidated by a concurrent mutation
|
|
129
|
+
*/
|
|
130
|
+
async insertAttestation(pubKey: BLSPubkey, attestation: SlashingProtectionAttestation): Promise<void> {
|
|
131
|
+
await this.attestationByTarget.set(pubKey, [attestation]);
|
|
132
|
+
await this.minMaxSurround.insertAttestation(pubKey, attestation);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Retrieve an attestation from the slashing protection database for a given `pubkey` and `epoch`
|
|
137
|
+
*/
|
|
138
|
+
async getAttestationForEpoch(pubkey: BLSPubkey, epoch: Epoch): Promise<SlashingProtectionAttestation | null> {
|
|
139
|
+
return this.attestationByTarget.get(pubkey, epoch);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Interchange import / export functionality
|
|
144
|
+
*/
|
|
145
|
+
async importAttestations(pubkey: BLSPubkey, attestations: SlashingProtectionAttestation[]): Promise<void> {
|
|
146
|
+
await this.attestationByTarget.set(pubkey, attestations);
|
|
147
|
+
|
|
148
|
+
// Pre-compute spans for all attestations
|
|
149
|
+
for (const attestation of attestations) {
|
|
150
|
+
await this.minMaxSurround.insertAttestation(pubkey, attestation);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Pre-compute and store lower-bound
|
|
154
|
+
const minSourceEpoch = minEpoch(attestations.map((attestation) => attestation.sourceEpoch));
|
|
155
|
+
const minTargetEpoch = minEpoch(attestations.map((attestation) => attestation.targetEpoch));
|
|
156
|
+
if (minSourceEpoch != null && minTargetEpoch != null) {
|
|
157
|
+
await this.attestationLowerBound.set(pubkey, {minSourceEpoch, minTargetEpoch});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Interchange import / export functionality
|
|
163
|
+
*/
|
|
164
|
+
async exportAttestations(pubkey: BLSPubkey): Promise<SlashingProtectionAttestation[]> {
|
|
165
|
+
return this.attestationByTarget.getAll(pubkey);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async listPubkeys(): Promise<BLSPubkey[]> {
|
|
169
|
+
return this.attestationByTarget.listPubkeys();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {ContainerType, Type} from "@chainsafe/ssz";
|
|
2
|
+
import {DB_PREFIX_LENGTH, DbReqOpts, encodeKey, uintLen} from "@lodestar/db";
|
|
3
|
+
import {BLSPubkey, Slot, ssz} from "@lodestar/types";
|
|
4
|
+
import {bytesToInt, intToBytes} from "@lodestar/utils";
|
|
5
|
+
import {Bucket, getBucketNameByValue} from "../../buckets.js";
|
|
6
|
+
import {LodestarValidatorDatabaseController} from "../../types.js";
|
|
7
|
+
import {SlashingProtectionBlock} from "../types.js";
|
|
8
|
+
import {blsPubkeyLen, uniqueVectorArr} from "../utils.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Manages validator db storage of blocks.
|
|
12
|
+
* Entries in the db are indexed by an encoded key which combines the validator's public key and the
|
|
13
|
+
* block's slot.
|
|
14
|
+
*/
|
|
15
|
+
export class BlockBySlotRepository {
|
|
16
|
+
protected type: Type<SlashingProtectionBlock>;
|
|
17
|
+
protected bucket = Bucket.slashingProtectionBlockBySlot;
|
|
18
|
+
|
|
19
|
+
private readonly bucketId = getBucketNameByValue(this.bucket);
|
|
20
|
+
private readonly dbReqOpts: DbReqOpts = {bucketId: this.bucketId};
|
|
21
|
+
private readonly minKey: Uint8Array;
|
|
22
|
+
private readonly maxKey: Uint8Array;
|
|
23
|
+
|
|
24
|
+
constructor(protected db: LodestarValidatorDatabaseController) {
|
|
25
|
+
this.type = new ContainerType({
|
|
26
|
+
slot: ssz.Slot,
|
|
27
|
+
signingRoot: ssz.Root,
|
|
28
|
+
}); // casing doesn't matter
|
|
29
|
+
this.minKey = encodeKey(this.bucket, Buffer.alloc(0));
|
|
30
|
+
this.maxKey = encodeKey(this.bucket + 1, Buffer.alloc(0));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async getAll(pubkey: BLSPubkey, limit?: number): Promise<SlashingProtectionBlock[]> {
|
|
34
|
+
const blocks = await this.db.values({
|
|
35
|
+
limit,
|
|
36
|
+
gte: this.encodeKey(pubkey, 0),
|
|
37
|
+
lt: this.encodeKey(pubkey, Number.MAX_SAFE_INTEGER),
|
|
38
|
+
bucketId: this.bucketId,
|
|
39
|
+
});
|
|
40
|
+
return blocks.map((block) => this.type.deserialize(block));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async getFirst(pubkey: BLSPubkey): Promise<SlashingProtectionBlock | null> {
|
|
44
|
+
const blocks = await this.getAll(pubkey, 1);
|
|
45
|
+
return blocks[0] ?? null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async get(pubkey: BLSPubkey, slot: Slot): Promise<SlashingProtectionBlock | null> {
|
|
49
|
+
const block = await this.db.get(this.encodeKey(pubkey, slot), this.dbReqOpts);
|
|
50
|
+
return block && this.type.deserialize(block);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async set(pubkey: BLSPubkey, blocks: SlashingProtectionBlock[]): Promise<void> {
|
|
54
|
+
await this.db.batchPut(
|
|
55
|
+
blocks.map((block) => ({
|
|
56
|
+
key: this.encodeKey(pubkey, block.slot),
|
|
57
|
+
value: this.type.serialize(block),
|
|
58
|
+
})),
|
|
59
|
+
this.dbReqOpts
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async listPubkeys(): Promise<BLSPubkey[]> {
|
|
64
|
+
const keys = await this.db.keys({gte: this.minKey, lt: this.maxKey, bucketId: this.bucketId});
|
|
65
|
+
return uniqueVectorArr(keys.map((key) => this.decodeKey(key).pubkey));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private encodeKey(pubkey: BLSPubkey, slot: Slot): Uint8Array {
|
|
69
|
+
return encodeKey(this.bucket, Buffer.concat([pubkey, intToBytes(BigInt(slot), uintLen, "be")]));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private decodeKey(key: Uint8Array): {pubkey: BLSPubkey; slot: Slot} {
|
|
73
|
+
return {
|
|
74
|
+
pubkey: key.slice(DB_PREFIX_LENGTH, DB_PREFIX_LENGTH + blsPubkeyLen),
|
|
75
|
+
slot: bytesToInt(key.slice(DB_PREFIX_LENGTH, DB_PREFIX_LENGTH + uintLen), "be"),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {Slot} from "@lodestar/types";
|
|
2
|
+
import {LodestarError} from "@lodestar/utils";
|
|
3
|
+
import {SlashingProtectionBlock} from "../types.js";
|
|
4
|
+
|
|
5
|
+
export enum InvalidBlockErrorCode {
|
|
6
|
+
/**
|
|
7
|
+
* The block has the same slot as a block from the DB
|
|
8
|
+
*/
|
|
9
|
+
DOUBLE_BLOCK_PROPOSAL = "ERR_INVALID_BLOCK_DOUBLE_BLOCK_PROPOSAL",
|
|
10
|
+
/**
|
|
11
|
+
* The block is invalid because its slot is less than the lower bound slot for this validator.
|
|
12
|
+
*/
|
|
13
|
+
SLOT_LESS_THAN_LOWER_BOUND = "ERR_INVALID_BLOCK_SLOT_LESS_THAN_LOWER_BOUND",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type InvalidBlockErrorType =
|
|
17
|
+
| {
|
|
18
|
+
code: InvalidBlockErrorCode.DOUBLE_BLOCK_PROPOSAL;
|
|
19
|
+
block: SlashingProtectionBlock;
|
|
20
|
+
block2: SlashingProtectionBlock;
|
|
21
|
+
}
|
|
22
|
+
| {
|
|
23
|
+
code: InvalidBlockErrorCode.SLOT_LESS_THAN_LOWER_BOUND;
|
|
24
|
+
slot: Slot;
|
|
25
|
+
minSlot: Slot;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export class InvalidBlockError extends LodestarError<InvalidBlockErrorType> {}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {BLSPubkey} from "@lodestar/types";
|
|
2
|
+
import {SlashingProtectionBlock} from "../types.js";
|
|
3
|
+
import {isEqualNonZeroRoot} from "../utils.js";
|
|
4
|
+
import {BlockBySlotRepository} from "./blockBySlotRepository.js";
|
|
5
|
+
import {InvalidBlockError, InvalidBlockErrorCode} from "./errors.js";
|
|
6
|
+
export {BlockBySlotRepository, InvalidBlockError, InvalidBlockErrorCode};
|
|
7
|
+
|
|
8
|
+
enum SafeStatus {
|
|
9
|
+
SAME_DATA = "SAFE_STATUS_SAME_DATA",
|
|
10
|
+
OK = "SAFE_STATUS_OK",
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class SlashingProtectionBlockService {
|
|
14
|
+
private blockBySlot: BlockBySlotRepository;
|
|
15
|
+
|
|
16
|
+
constructor(blockBySlot: BlockBySlotRepository) {
|
|
17
|
+
this.blockBySlot = blockBySlot;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Check a block proposal for slash safety, and if it is safe, record it in the database.
|
|
22
|
+
* This is the safe, externally-callable interface for checking block proposals.
|
|
23
|
+
*/
|
|
24
|
+
async checkAndInsertBlockProposal(pubkey: BLSPubkey, block: SlashingProtectionBlock): Promise<void> {
|
|
25
|
+
const safeStatus = await this.checkBlockProposal(pubkey, block);
|
|
26
|
+
|
|
27
|
+
if (safeStatus !== SafeStatus.SAME_DATA) {
|
|
28
|
+
await this.insertBlockProposal(pubkey, block);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// TODO: Implement safe clean-up of stored blocks
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check a block proposal from `pubKey` for slash safety.
|
|
36
|
+
*/
|
|
37
|
+
async checkBlockProposal(pubkey: BLSPubkey, block: SlashingProtectionBlock): Promise<SafeStatus> {
|
|
38
|
+
// Double proposal
|
|
39
|
+
const sameSlotBlock = await this.blockBySlot.get(pubkey, block.slot);
|
|
40
|
+
if (sameSlotBlock && block.slot === sameSlotBlock.slot) {
|
|
41
|
+
// Interchange format allows for blocks without signing_root, then assume root is equal
|
|
42
|
+
if (isEqualNonZeroRoot(sameSlotBlock.signingRoot, block.signingRoot)) {
|
|
43
|
+
return SafeStatus.SAME_DATA;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
throw new InvalidBlockError({
|
|
47
|
+
code: InvalidBlockErrorCode.DOUBLE_BLOCK_PROPOSAL,
|
|
48
|
+
block,
|
|
49
|
+
block2: sameSlotBlock,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Refuse to sign any block with slot <= min(b.slot for b in data.signed_blocks if b.pubkey == proposer_pubkey),
|
|
54
|
+
// except if it is a repeat signing as determined by the signing_root.
|
|
55
|
+
// (spec v4, Slashing Protection Database Interchange Format)
|
|
56
|
+
const minBlock = await this.blockBySlot.getFirst(pubkey);
|
|
57
|
+
if (minBlock && block.slot <= minBlock.slot) {
|
|
58
|
+
throw new InvalidBlockError({
|
|
59
|
+
code: InvalidBlockErrorCode.SLOT_LESS_THAN_LOWER_BOUND,
|
|
60
|
+
slot: block.slot,
|
|
61
|
+
minSlot: minBlock.slot,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return SafeStatus.OK;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Insert a block proposal into the slashing database
|
|
70
|
+
* This should *only* be called in the same (exclusive) transaction as `checkBlockProposal`
|
|
71
|
+
* so that the check isn't invalidated by a concurrent mutation
|
|
72
|
+
*/
|
|
73
|
+
async insertBlockProposal(pubkey: BLSPubkey, block: SlashingProtectionBlock): Promise<void> {
|
|
74
|
+
await this.blockBySlot.set(pubkey, [block]);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Interchange import / export functionality
|
|
79
|
+
*/
|
|
80
|
+
async importBlocks(pubkey: BLSPubkey, blocks: SlashingProtectionBlock[]): Promise<void> {
|
|
81
|
+
await this.blockBySlot.set(pubkey, blocks);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Interchange import / export functionality
|
|
86
|
+
*/
|
|
87
|
+
async exportBlocks(pubkey: BLSPubkey): Promise<SlashingProtectionBlock[]> {
|
|
88
|
+
return this.blockBySlot.getAll(pubkey);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async listPubkeys(): Promise<BLSPubkey[]> {
|
|
92
|
+
return this.blockBySlot.listPubkeys();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {BLSPubkey, Epoch, Root} from "@lodestar/types";
|
|
2
|
+
import {Logger, toPubkeyHex} from "@lodestar/utils";
|
|
3
|
+
import {uniqueVectorArr} from "../slashingProtection/utils.js";
|
|
4
|
+
import {LodestarValidatorDatabaseController} from "../types.js";
|
|
5
|
+
import {
|
|
6
|
+
AttestationByTargetRepository,
|
|
7
|
+
AttestationLowerBoundRepository,
|
|
8
|
+
SlashingProtectionAttestationService,
|
|
9
|
+
} from "./attestation/index.js";
|
|
10
|
+
import {BlockBySlotRepository, SlashingProtectionBlockService} from "./block/index.js";
|
|
11
|
+
import {
|
|
12
|
+
Interchange,
|
|
13
|
+
InterchangeFormatVersion,
|
|
14
|
+
InterchangeLodestar,
|
|
15
|
+
parseInterchange,
|
|
16
|
+
serializeInterchange,
|
|
17
|
+
} from "./interchange/index.js";
|
|
18
|
+
import {ISlashingProtection} from "./interface.js";
|
|
19
|
+
import {DistanceStoreRepository, MinMaxSurround} from "./minMaxSurround/index.js";
|
|
20
|
+
import {SlashingProtectionAttestation, SlashingProtectionBlock} from "./types.js";
|
|
21
|
+
|
|
22
|
+
export {InvalidAttestationError, InvalidAttestationErrorCode} from "./attestation/index.js";
|
|
23
|
+
export {InvalidBlockError, InvalidBlockErrorCode} from "./block/index.js";
|
|
24
|
+
export type {Interchange, InterchangeFormat} from "./interchange/index.js";
|
|
25
|
+
export {InterchangeError, InterchangeErrorErrorCode} from "./interchange/index.js";
|
|
26
|
+
export type {ISlashingProtection, InterchangeFormatVersion, SlashingProtectionBlock, SlashingProtectionAttestation};
|
|
27
|
+
/**
|
|
28
|
+
* Handles slashing protection for validator proposer and attester duties as well as slashing protection
|
|
29
|
+
* during a validator interchange import/export process.
|
|
30
|
+
*/
|
|
31
|
+
export class SlashingProtection implements ISlashingProtection {
|
|
32
|
+
private blockService: SlashingProtectionBlockService;
|
|
33
|
+
private attestationService: SlashingProtectionAttestationService;
|
|
34
|
+
|
|
35
|
+
constructor(protected db: LodestarValidatorDatabaseController) {
|
|
36
|
+
const blockBySlotRepository = new BlockBySlotRepository(db);
|
|
37
|
+
const attestationByTargetRepository = new AttestationByTargetRepository(db);
|
|
38
|
+
const attestationLowerBoundRepository = new AttestationLowerBoundRepository(db);
|
|
39
|
+
const distanceStoreRepository = new DistanceStoreRepository(db);
|
|
40
|
+
const minMaxSurround = new MinMaxSurround(distanceStoreRepository);
|
|
41
|
+
|
|
42
|
+
this.blockService = new SlashingProtectionBlockService(blockBySlotRepository);
|
|
43
|
+
this.attestationService = new SlashingProtectionAttestationService(
|
|
44
|
+
attestationByTargetRepository,
|
|
45
|
+
attestationLowerBoundRepository,
|
|
46
|
+
minMaxSurround
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async checkAndInsertBlockProposal(pubKey: BLSPubkey, block: SlashingProtectionBlock): Promise<void> {
|
|
51
|
+
await this.blockService.checkAndInsertBlockProposal(pubKey, block);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async checkAndInsertAttestation(pubKey: BLSPubkey, attestation: SlashingProtectionAttestation): Promise<void> {
|
|
55
|
+
await this.attestationService.checkAndInsertAttestation(pubKey, attestation);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async hasAttestedInEpoch(pubKey: BLSPubkey, epoch: Epoch): Promise<boolean> {
|
|
59
|
+
return (await this.attestationService.getAttestationForEpoch(pubKey, epoch)) !== null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async importInterchange(interchange: Interchange, genesisValidatorsRoot: Root, logger?: Logger): Promise<void> {
|
|
63
|
+
const {data} = parseInterchange(interchange, genesisValidatorsRoot);
|
|
64
|
+
for (const validator of data) {
|
|
65
|
+
logger?.info("Importing slashing protection", {pubkey: toPubkeyHex(validator.pubkey)});
|
|
66
|
+
await this.blockService.importBlocks(validator.pubkey, validator.signedBlocks);
|
|
67
|
+
await this.attestationService.importAttestations(validator.pubkey, validator.signedAttestations);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async exportInterchange(
|
|
72
|
+
genesisValidatorsRoot: Root,
|
|
73
|
+
pubkeys: BLSPubkey[],
|
|
74
|
+
formatVersion: InterchangeFormatVersion,
|
|
75
|
+
logger?: Logger
|
|
76
|
+
): Promise<Interchange> {
|
|
77
|
+
const validatorData: InterchangeLodestar["data"] = [];
|
|
78
|
+
for (const pubkey of pubkeys) {
|
|
79
|
+
logger?.info("Exporting slashing protection", {pubkey: toPubkeyHex(pubkey)});
|
|
80
|
+
validatorData.push({
|
|
81
|
+
pubkey,
|
|
82
|
+
signedBlocks: await this.blockService.exportBlocks(pubkey),
|
|
83
|
+
signedAttestations: await this.attestationService.exportAttestations(pubkey),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
logger?.verbose("Serializing Interchange");
|
|
87
|
+
return serializeInterchange({data: validatorData, genesisValidatorsRoot}, formatVersion);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async listPubkeys(): Promise<BLSPubkey[]> {
|
|
91
|
+
const pubkeysAtt = await this.attestationService.listPubkeys();
|
|
92
|
+
const pubkeysBlk = await this.blockService.listPubkeys();
|
|
93
|
+
return uniqueVectorArr([...pubkeysAtt, ...pubkeysBlk]);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {Root} from "@lodestar/types";
|
|
2
|
+
import {LodestarError} from "@lodestar/utils";
|
|
3
|
+
|
|
4
|
+
export enum InterchangeErrorErrorCode {
|
|
5
|
+
UNSUPPORTED_FORMAT = "ERR_INTERCHANGE_UNSUPPORTED_FORMAT",
|
|
6
|
+
UNSUPPORTED_VERSION = "ERR_INTERCHANGE_UNSUPPORTED_VERSION",
|
|
7
|
+
GENESIS_VALIDATOR_MISMATCH = "ERR_INTERCHANGE_GENESIS_VALIDATOR_MISMATCH",
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type InterchangeErrorErrorType =
|
|
11
|
+
| {code: InterchangeErrorErrorCode.UNSUPPORTED_FORMAT; format: string}
|
|
12
|
+
| {code: InterchangeErrorErrorCode.UNSUPPORTED_VERSION; version: string}
|
|
13
|
+
| {code: InterchangeErrorErrorCode.GENESIS_VALIDATOR_MISMATCH; root: Root; expectedRoot: Root};
|
|
14
|
+
|
|
15
|
+
export class InterchangeError extends LodestarError<InterchangeErrorErrorType> {}
|