1
+ import { fromHexString } from "@chainsafe/ssz" ;
1
2
import { Epoch , ValidatorIndex } from "@lodestar/types" ;
2
3
import { Api , ApiError , routes } from "@lodestar/api" ;
3
- import { Logger , sleep } from "@lodestar/utils" ;
4
+ import { Logger , sleep , truncBytes } from "@lodestar/utils" ;
4
5
import { computeStartSlotAtEpoch } from "@lodestar/state-transition" ;
6
+ import { ISlashingProtection } from "../slashingProtection/index.js" ;
5
7
import { ProcessShutdownCallback , PubkeyHex } from "../types.js" ;
6
8
import { IClock } from "../util/index.js" ;
7
9
import { Metrics } from "../metrics.js" ;
@@ -10,7 +12,8 @@ import {IndicesService} from "./indices.js";
10
12
// The number of epochs that must be checked before we assume that there are
11
13
// no other duplicate validators on the network
12
14
const DEFAULT_REMAINING_DETECTION_EPOCHS = 1 ;
13
- const REMAINING_EPOCHS_IF_DOPPLEGANGER = Infinity ;
15
+ const REMAINING_EPOCHS_IF_DOPPELGANGER = Infinity ;
16
+ const REMAINING_EPOCHS_IF_SKIPPED = 0 ;
14
17
15
18
/** Liveness responses for a given epoch */
16
19
type EpochLivenessData = {
@@ -24,13 +27,13 @@ export type DoppelgangerState = {
24
27
} ;
25
28
26
29
export enum DoppelgangerStatus {
27
- // This pubkey is known to the doppelganger service and has been verified safe
30
+ /** This pubkey is known to the doppelganger service and has been verified safe */
28
31
VerifiedSafe = "VerifiedSafe" ,
29
- // This pubkey is known to the doppelganger service but has not been verified safe
32
+ /** This pubkey is known to the doppelganger service but has not been verified safe */
30
33
Unverified = "Unverified" ,
31
- // This pubkey is unknown to the doppelganger service
34
+ /** This pubkey is unknown to the doppelganger service */
32
35
Unknown = "Unknown" ,
33
- // This pubkey has been detected to be active on the network
36
+ /** This pubkey has been detected to be active on the network */
34
37
DoppelgangerDetected = "DoppelgangerDetected" ,
35
38
}
36
39
@@ -42,6 +45,7 @@ export class DoppelgangerService {
42
45
private readonly clock : IClock ,
43
46
private readonly api : Api ,
44
47
private readonly indicesService : IndicesService ,
48
+ private readonly slashingProtection : ISlashingProtection ,
45
49
private readonly processShutdownCallback : ProcessShutdownCallback ,
46
50
private readonly metrics : Metrics | null
47
51
) {
@@ -54,16 +58,41 @@ export class DoppelgangerService {
54
58
this . logger . info ( "Doppelganger protection enabled" , { detectionEpochs : DEFAULT_REMAINING_DETECTION_EPOCHS } ) ;
55
59
}
56
60
57
- registerValidator ( pubkeyHex : PubkeyHex ) : void {
61
+ async registerValidator ( pubkeyHex : PubkeyHex ) : Promise < void > {
58
62
const { currentEpoch} = this . clock ;
59
63
// Disable doppelganger protection when the validator was initialized before genesis.
60
64
// There's no activity before genesis, so doppelganger is pointless.
61
- const remainingEpochs = currentEpoch <= 0 ? 0 : DEFAULT_REMAINING_DETECTION_EPOCHS ;
65
+ let remainingEpochs = currentEpoch <= 0 ? REMAINING_EPOCHS_IF_SKIPPED : DEFAULT_REMAINING_DETECTION_EPOCHS ;
62
66
const nextEpochToCheck = currentEpoch + 1 ;
63
67
64
68
// Log here to alert that validation won't be active until remainingEpochs == 0
65
69
if ( remainingEpochs > 0 ) {
66
- this . logger . info ( "Registered validator for doppelganger" , { remainingEpochs, nextEpochToCheck, pubkeyHex} ) ;
70
+ const previousEpoch = currentEpoch - 1 ;
71
+ const attestedInPreviousEpoch = await this . slashingProtection . hasAttestedInEpoch (
72
+ fromHexString ( pubkeyHex ) ,
73
+ previousEpoch
74
+ ) ;
75
+
76
+ if ( attestedInPreviousEpoch ) {
77
+ // It is safe to skip doppelganger detection
78
+ // https://github.com/ChainSafe/lodestar/issues/5856
79
+ remainingEpochs = REMAINING_EPOCHS_IF_SKIPPED ;
80
+ this . logger . info ( "Doppelganger detection skipped for validator because restart was detected" , {
81
+ pubkey : truncBytes ( pubkeyHex ) ,
82
+ previousEpoch,
83
+ } ) ;
84
+ } else {
85
+ this . logger . info ( "Registered validator for doppelganger detection" , {
86
+ pubkey : truncBytes ( pubkeyHex ) ,
87
+ remainingEpochs,
88
+ nextEpochToCheck,
89
+ } ) ;
90
+ }
91
+ } else {
92
+ this . logger . info ( "Doppelganger detection skipped for validator initialized before genesis" , {
93
+ pubkey : truncBytes ( pubkeyHex ) ,
94
+ currentEpoch,
95
+ } ) ;
67
96
}
68
97
69
98
this . doppelgangerStateByPubkey . set ( pubkeyHex , {
@@ -180,7 +209,7 @@ export class DoppelgangerService {
180
209
}
181
210
182
211
if ( state . nextEpochToCheck <= epoch ) {
183
- // Doppleganger detected
212
+ // Doppelganger detected
184
213
violators . push ( response . index ) ;
185
214
}
186
215
}
@@ -189,7 +218,7 @@ export class DoppelgangerService {
189
218
if ( violators . length > 0 ) {
190
219
// If a single doppelganger is detected, enable doppelganger checks on all validators forever
191
220
for ( const state of this . doppelgangerStateByPubkey . values ( ) ) {
192
- state . remainingEpochs = Infinity ;
221
+ state . remainingEpochs = REMAINING_EPOCHS_IF_DOPPELGANGER ;
193
222
}
194
223
195
224
this . logger . error (
@@ -225,9 +254,9 @@ export class DoppelgangerService {
225
254
226
255
const { remainingEpochs, nextEpochToCheck} = state ;
227
256
if ( remainingEpochs <= 0 ) {
228
- this . logger . info ( "Doppelganger detection complete" , { index : response . index } ) ;
257
+ this . logger . info ( "Doppelganger detection complete" , { index : response . index , epoch : currentEpoch } ) ;
229
258
} else {
230
- this . logger . info ( "Found no doppelganger" , { remainingEpochs , nextEpochToCheck , index : response . index } ) ;
259
+ this . logger . info ( "Found no doppelganger" , { index : response . index , remainingEpochs , nextEpochToCheck } ) ;
231
260
}
232
261
}
233
262
}
@@ -253,7 +282,7 @@ function getStatus(state: DoppelgangerState | undefined): DoppelgangerStatus {
253
282
return DoppelgangerStatus . Unknown ;
254
283
} else if ( state . remainingEpochs <= 0 ) {
255
284
return DoppelgangerStatus . VerifiedSafe ;
256
- } else if ( state . remainingEpochs === REMAINING_EPOCHS_IF_DOPPLEGANGER ) {
285
+ } else if ( state . remainingEpochs === REMAINING_EPOCHS_IF_DOPPELGANGER ) {
257
286
return DoppelgangerStatus . DoppelgangerDetected ;
258
287
} else {
259
288
return DoppelgangerStatus . Unverified ;
0 commit comments