-
-
Notifications
You must be signed in to change notification settings - Fork 479
Expand file tree
/
Copy pathminMaxSurround.ts
More file actions
110 lines (94 loc) 路 4.86 KB
/
Copy pathminMaxSurround.ts
File metadata and controls
110 lines (94 loc) 路 4.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import {BLSPubkey, Epoch} from "@lodestar/types";
import {SurroundAttestationError, SurroundAttestationErrorCode} from "./errors.js";
import {DistanceEntry, IDistanceStore, IMinMaxSurround, MinMaxSurroundAttestation} from "./interface.js";
// surround vote checking with min-max surround
// https://github.com/protolambda/eth2-surround#min-max-surround
/**
* Number of epochs in the past to check for surrounding attestations.
*
* This value can be limited to a reasonable high amount as Lodestar does not solely rely on this strategy but also
* rejects any source epoch below `minSpanCoverageStart` of the latest recorded attestation, which is the minimal
* strategy (formally proven safe, https://github.com/michaelsproul/slashing-proofs) relaxed by this lookback.
*
* Limiting this value is required due to practical reasons as otherwise there would be a min-span DB read and write
* for each validator from current epoch until genesis which massively increases DB size and causes I/O lag, resulting in
* instability on first startup with an empty DB. See https://github.com/ChainSafe/lodestar/issues/5356 for more details.
*
* The value 4096 has been chosen as it is the default used by slashers (https://lighthouse-book.sigmaprime.io/slasher.html#history-length)
* and is generally higher than the weak subjectivity period. However, it would still be risky if we just relied on min-max surround
* for slashing protection, as slashers can be configured to collect slashable attestations over a longer period.
*/
const DEFAULT_MAX_EPOCH_LOOKBACK = 4096;
export class MinMaxSurround implements IMinMaxSurround {
private store: IDistanceStore;
private maxEpochLookback: number;
constructor(store: IDistanceStore, options?: {maxEpochLookback?: number}) {
this.store = store;
this.maxEpochLookback = options?.maxEpochLookback ?? DEFAULT_MAX_EPOCH_LOOKBACK;
}
/** Lowest epoch with a min-span entry after inserting an attestation with `sourceEpoch` */
minSpanCoverageStart(sourceEpoch: Epoch): Epoch {
return Math.max(0, sourceEpoch - 1 - this.maxEpochLookback);
}
async assertNoSurround(pubKey: BLSPubkey, attestation: MinMaxSurroundAttestation): Promise<void> {
await this.assertNotSurrounding(pubKey, attestation);
await this.assertNotSurrounded(pubKey, attestation);
}
async insertAttestation(pubKey: BLSPubkey, attestation: MinMaxSurroundAttestation): Promise<void> {
await this.updateMinSpan(pubKey, attestation);
await this.updateMaxSpan(pubKey, attestation);
}
// min span
private async updateMinSpan(pubKey: BLSPubkey, attestation: MinMaxSurroundAttestation): Promise<void> {
await this.assertNotSurrounding(pubKey, attestation);
const untilEpoch = this.minSpanCoverageStart(attestation.sourceEpoch);
const values: DistanceEntry[] = [];
for (let epoch = attestation.sourceEpoch - 1; epoch >= untilEpoch; epoch--) {
const minSpan = await this.store.minSpan.get(pubKey, epoch);
const distance = attestation.targetEpoch - epoch;
if (minSpan === null || distance < minSpan) {
values.push({source: epoch, distance});
} else {
break;
}
}
await this.store.minSpan.setBatch(pubKey, values);
}
private async assertNotSurrounding(pubKey: BLSPubkey, attestation: MinMaxSurroundAttestation): Promise<void> {
const minSpan = await this.store.minSpan.get(pubKey, attestation.sourceEpoch);
const distance = attestation.targetEpoch - attestation.sourceEpoch;
if (minSpan != null && minSpan > 0 && minSpan < distance) {
throw new SurroundAttestationError({
code: SurroundAttestationErrorCode.IS_SURROUNDING,
attestation,
attestation2Target: attestation.sourceEpoch + minSpan,
});
}
}
// max span
private async updateMaxSpan(pubKey: BLSPubkey, attestation: MinMaxSurroundAttestation): Promise<void> {
await this.assertNotSurrounded(pubKey, attestation);
const values: DistanceEntry[] = [];
for (let epoch = attestation.sourceEpoch + 1; epoch < attestation.targetEpoch; epoch++) {
const maxSpan = await this.store.maxSpan.get(pubKey, epoch);
const distance = attestation.targetEpoch - epoch;
if (maxSpan === null || distance > maxSpan) {
values.push({source: epoch, distance});
} else {
break;
}
}
await this.store.maxSpan.setBatch(pubKey, values);
}
private async assertNotSurrounded(pubKey: BLSPubkey, attestation: MinMaxSurroundAttestation): Promise<void> {
const maxSpan = await this.store.maxSpan.get(pubKey, attestation.sourceEpoch);
const distance = attestation.targetEpoch - attestation.sourceEpoch;
if (maxSpan != null && maxSpan > 0 && maxSpan > distance) {
throw new SurroundAttestationError({
code: SurroundAttestationErrorCode.IS_SURROUNDED,
attestation: attestation,
attestation2Target: attestation.sourceEpoch + maxSpan,
});
}
}
}