@lodestar/validator 1.35.0-dev.83de5b8dea → 1.35.0-dev.8689cc3545
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.map +1 -0
- 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/services/attestation.d.ts.map +1 -0
- package/lib/services/attestationDuties.d.ts.map +1 -0
- package/lib/services/block.d.ts.map +1 -0
- package/lib/services/blockDuties.d.ts.map +1 -0
- package/lib/services/chainHeaderTracker.d.ts.map +1 -0
- package/lib/services/doppelgangerService.d.ts.map +1 -0
- package/lib/services/emitter.d.ts.map +1 -0
- package/lib/services/externalSignerSync.d.ts.map +1 -0
- package/lib/services/indices.d.ts.map +1 -0
- package/lib/services/prepareBeaconProposer.d.ts.map +1 -0
- package/lib/services/syncCommittee.d.ts.map +1 -0
- package/lib/services/syncCommitteeDuties.d.ts.map +1 -0
- package/lib/services/syncingStatusTracker.d.ts.map +1 -0
- package/lib/services/utils.d.ts.map +1 -0
- package/lib/services/validatorStore.d.ts.map +1 -0
- package/lib/slashingProtection/attestation/attestationByTargetRepository.d.ts.map +1 -0
- package/lib/slashingProtection/attestation/attestationLowerBoundRepository.d.ts.map +1 -0
- package/lib/slashingProtection/attestation/errors.d.ts.map +1 -0
- package/lib/slashingProtection/attestation/index.d.ts.map +1 -0
- package/lib/slashingProtection/block/blockBySlotRepository.d.ts.map +1 -0
- package/lib/slashingProtection/block/errors.d.ts.map +1 -0
- package/lib/slashingProtection/block/index.d.ts.map +1 -0
- package/lib/slashingProtection/index.d.ts.map +1 -0
- 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/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/types.d.ts.map +1 -0
- 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/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/url.d.ts.map +1 -0
- package/lib/validator.d.ts.map +1 -0
- package/package.json +13 -15
- 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
package/src/validator.ts
ADDED
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import {ApiClient, ApiRequestInit, defaultInit, getClient, routes} from "@lodestar/api";
|
|
2
|
+
import {BeaconConfig, ChainForkConfig, createBeaconConfig} from "@lodestar/config";
|
|
3
|
+
import {computeEpochAtSlot, getCurrentSlot} from "@lodestar/state-transition";
|
|
4
|
+
import {BLSPubkey, phase0, ssz} from "@lodestar/types";
|
|
5
|
+
import {Genesis} from "@lodestar/types/phase0";
|
|
6
|
+
import {Logger, toPrintableUrl, toRootHex} from "@lodestar/utils";
|
|
7
|
+
import {waitForGenesis} from "./genesis.js";
|
|
8
|
+
import {Metrics} from "./metrics.js";
|
|
9
|
+
import {MetaDataRepository} from "./repositories/metaDataRepository.js";
|
|
10
|
+
import {AttestationService} from "./services/attestation.js";
|
|
11
|
+
import {BlockProposingService} from "./services/block.js";
|
|
12
|
+
import {ChainHeaderTracker} from "./services/chainHeaderTracker.js";
|
|
13
|
+
import {DoppelgangerService} from "./services/doppelgangerService.js";
|
|
14
|
+
import {ValidatorEventEmitter} from "./services/emitter.js";
|
|
15
|
+
import {ExternalSignerOptions, pollExternalSignerPubkeys} from "./services/externalSignerSync.js";
|
|
16
|
+
import {IndicesService} from "./services/indices.js";
|
|
17
|
+
import {pollBuilderValidatorRegistration, pollPrepareBeaconProposer} from "./services/prepareBeaconProposer.js";
|
|
18
|
+
import {SyncCommitteeService} from "./services/syncCommittee.js";
|
|
19
|
+
import {SyncingStatusTracker} from "./services/syncingStatusTracker.js";
|
|
20
|
+
import {Signer, ValidatorProposerConfig, ValidatorStore, defaultOptions} from "./services/validatorStore.js";
|
|
21
|
+
import {ISlashingProtection, Interchange, InterchangeFormatVersion} from "./slashingProtection/index.js";
|
|
22
|
+
import {LodestarValidatorDatabaseController, ProcessShutdownCallback, PubkeyHex} from "./types.js";
|
|
23
|
+
import {Clock, IClock} from "./util/clock.js";
|
|
24
|
+
import {NotEqualParamsError, assertEqualParams, getLoggerVc} from "./util/index.js";
|
|
25
|
+
|
|
26
|
+
export type ValidatorModules = {
|
|
27
|
+
opts: ValidatorOptions;
|
|
28
|
+
genesis: Genesis;
|
|
29
|
+
validatorStore: ValidatorStore;
|
|
30
|
+
slashingProtection: ISlashingProtection;
|
|
31
|
+
blockProposingService: BlockProposingService;
|
|
32
|
+
attestationService: AttestationService;
|
|
33
|
+
syncCommitteeService: SyncCommitteeService;
|
|
34
|
+
config: BeaconConfig;
|
|
35
|
+
api: ApiClient;
|
|
36
|
+
clock: IClock;
|
|
37
|
+
chainHeaderTracker: ChainHeaderTracker;
|
|
38
|
+
syncingStatusTracker: SyncingStatusTracker;
|
|
39
|
+
logger: Logger;
|
|
40
|
+
db: LodestarValidatorDatabaseController;
|
|
41
|
+
metrics: Metrics | null;
|
|
42
|
+
controller: AbortController;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type ValidatorOptions = {
|
|
46
|
+
slashingProtection: ISlashingProtection;
|
|
47
|
+
db: LodestarValidatorDatabaseController;
|
|
48
|
+
config: ChainForkConfig;
|
|
49
|
+
api: {
|
|
50
|
+
clientOrUrls: ApiClient | string | string[];
|
|
51
|
+
globalInit?: ApiRequestInit;
|
|
52
|
+
};
|
|
53
|
+
signers: Signer[];
|
|
54
|
+
logger: Logger;
|
|
55
|
+
processShutdownCallback: ProcessShutdownCallback;
|
|
56
|
+
abortController: AbortController;
|
|
57
|
+
afterBlockDelaySlotFraction?: number;
|
|
58
|
+
scAfterBlockDelaySlotFraction?: number;
|
|
59
|
+
doppelgangerProtection?: boolean;
|
|
60
|
+
closed?: boolean;
|
|
61
|
+
valProposerConfig?: ValidatorProposerConfig;
|
|
62
|
+
distributed?: boolean;
|
|
63
|
+
broadcastValidation?: routes.beacon.BroadcastValidation;
|
|
64
|
+
blindedLocal?: boolean;
|
|
65
|
+
externalSigner?: ExternalSignerOptions;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// TODO: Extend the timeout, and let it be customizable
|
|
69
|
+
/// The global timeout for HTTP requests to the beacon node.
|
|
70
|
+
// const HTTP_TIMEOUT_MS = 12 * 1000;
|
|
71
|
+
|
|
72
|
+
enum Status {
|
|
73
|
+
running,
|
|
74
|
+
closed,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Main class for the Validator client.
|
|
79
|
+
*/
|
|
80
|
+
export class Validator {
|
|
81
|
+
private readonly genesis: Genesis;
|
|
82
|
+
readonly validatorStore: ValidatorStore;
|
|
83
|
+
private readonly slashingProtection: ISlashingProtection;
|
|
84
|
+
private readonly blockProposingService: BlockProposingService;
|
|
85
|
+
private readonly attestationService: AttestationService;
|
|
86
|
+
private readonly syncCommitteeService: SyncCommitteeService;
|
|
87
|
+
private readonly config: BeaconConfig;
|
|
88
|
+
private readonly api: ApiClient;
|
|
89
|
+
private readonly clock: IClock;
|
|
90
|
+
private readonly chainHeaderTracker: ChainHeaderTracker;
|
|
91
|
+
readonly syncingStatusTracker: SyncingStatusTracker;
|
|
92
|
+
private readonly logger: Logger;
|
|
93
|
+
private readonly db: LodestarValidatorDatabaseController;
|
|
94
|
+
private state: Status;
|
|
95
|
+
private readonly controller: AbortController;
|
|
96
|
+
|
|
97
|
+
constructor({
|
|
98
|
+
opts,
|
|
99
|
+
genesis,
|
|
100
|
+
validatorStore,
|
|
101
|
+
slashingProtection,
|
|
102
|
+
blockProposingService,
|
|
103
|
+
attestationService,
|
|
104
|
+
syncCommitteeService,
|
|
105
|
+
config,
|
|
106
|
+
api,
|
|
107
|
+
clock,
|
|
108
|
+
chainHeaderTracker,
|
|
109
|
+
syncingStatusTracker,
|
|
110
|
+
logger,
|
|
111
|
+
db,
|
|
112
|
+
metrics,
|
|
113
|
+
controller,
|
|
114
|
+
}: ValidatorModules) {
|
|
115
|
+
this.genesis = genesis;
|
|
116
|
+
this.validatorStore = validatorStore;
|
|
117
|
+
this.slashingProtection = slashingProtection;
|
|
118
|
+
this.blockProposingService = blockProposingService;
|
|
119
|
+
this.attestationService = attestationService;
|
|
120
|
+
this.syncCommitteeService = syncCommitteeService;
|
|
121
|
+
this.config = config;
|
|
122
|
+
this.api = api;
|
|
123
|
+
this.clock = clock;
|
|
124
|
+
this.chainHeaderTracker = chainHeaderTracker;
|
|
125
|
+
this.syncingStatusTracker = syncingStatusTracker;
|
|
126
|
+
this.logger = logger;
|
|
127
|
+
this.controller = controller;
|
|
128
|
+
this.db = db;
|
|
129
|
+
|
|
130
|
+
if (opts.closed) {
|
|
131
|
+
this.state = Status.closed;
|
|
132
|
+
} else {
|
|
133
|
+
// Add notifier to warn user if primary node is unhealthy as there might
|
|
134
|
+
// not be any errors in the logs due to fallback nodes handling the requests
|
|
135
|
+
const {httpClient} = this.api;
|
|
136
|
+
if (httpClient.urlsInits.length > 1) {
|
|
137
|
+
const primaryNodeUrl = toPrintableUrl(httpClient.urlsInits[0].baseUrl);
|
|
138
|
+
|
|
139
|
+
this.clock.runEveryEpoch(async () => {
|
|
140
|
+
// Only emit warning if URL score is 0 to prevent false positives
|
|
141
|
+
// if just a single request fails which might happen due to other reasons
|
|
142
|
+
if (httpClient.urlsScore[0] === 0) {
|
|
143
|
+
this.logger.warn("Primary beacon node is unhealthy", {url: primaryNodeUrl});
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (metrics) {
|
|
149
|
+
this.db.setMetrics(metrics.db);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// "start" the validator
|
|
153
|
+
this.state = Status.running;
|
|
154
|
+
this.clock.start(this.controller.signal);
|
|
155
|
+
this.chainHeaderTracker.start(this.controller.signal);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
get isRunning(): boolean {
|
|
160
|
+
return this.state === Status.running;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Initialize and start a validator client
|
|
165
|
+
*/
|
|
166
|
+
static async init(opts: ValidatorOptions, genesis: Genesis, metrics: Metrics | null = null): Promise<Validator> {
|
|
167
|
+
const {db, config: chainConfig, logger, slashingProtection, signers, valProposerConfig} = opts;
|
|
168
|
+
const config = createBeaconConfig(chainConfig, genesis.genesisValidatorsRoot);
|
|
169
|
+
const controller = opts.abortController;
|
|
170
|
+
const clock = new Clock(config, logger, {genesisTime: Number(genesis.genesisTime)});
|
|
171
|
+
const loggerVc = getLoggerVc(logger, clock);
|
|
172
|
+
|
|
173
|
+
let api: ApiClient;
|
|
174
|
+
const {clientOrUrls, globalInit} = opts.api;
|
|
175
|
+
if (typeof clientOrUrls === "string" || Array.isArray(clientOrUrls)) {
|
|
176
|
+
api = getClient(
|
|
177
|
+
{
|
|
178
|
+
urls: typeof clientOrUrls === "string" ? [clientOrUrls] : clientOrUrls,
|
|
179
|
+
// Validator would need the beacon to respond within the slot
|
|
180
|
+
// See https://github.com/ChainSafe/lodestar/issues/5315 for rationale
|
|
181
|
+
globalInit: {timeoutMs: config.SECONDS_PER_SLOT * 1000, signal: controller.signal, ...globalInit},
|
|
182
|
+
},
|
|
183
|
+
{config, logger, metrics: metrics?.restApiClient}
|
|
184
|
+
);
|
|
185
|
+
} else {
|
|
186
|
+
api = clientOrUrls;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const indicesService = new IndicesService(logger, api, metrics);
|
|
190
|
+
|
|
191
|
+
const doppelgangerService = opts.doppelgangerProtection
|
|
192
|
+
? new DoppelgangerService(
|
|
193
|
+
logger,
|
|
194
|
+
clock,
|
|
195
|
+
api,
|
|
196
|
+
indicesService,
|
|
197
|
+
slashingProtection,
|
|
198
|
+
opts.processShutdownCallback,
|
|
199
|
+
metrics
|
|
200
|
+
)
|
|
201
|
+
: null;
|
|
202
|
+
|
|
203
|
+
const validatorStore = await ValidatorStore.init(
|
|
204
|
+
{
|
|
205
|
+
config,
|
|
206
|
+
slashingProtection,
|
|
207
|
+
indicesService,
|
|
208
|
+
doppelgangerService,
|
|
209
|
+
metrics,
|
|
210
|
+
},
|
|
211
|
+
signers,
|
|
212
|
+
valProposerConfig
|
|
213
|
+
);
|
|
214
|
+
pollPrepareBeaconProposer(config, loggerVc, api, clock, validatorStore, metrics);
|
|
215
|
+
pollBuilderValidatorRegistration(config, loggerVc, api, clock, validatorStore, metrics);
|
|
216
|
+
pollExternalSignerPubkeys(config, loggerVc, controller.signal, validatorStore, opts.externalSigner);
|
|
217
|
+
|
|
218
|
+
const emitter = new ValidatorEventEmitter();
|
|
219
|
+
// Validator event emitter can have more than 10 listeners in a normal course of operation
|
|
220
|
+
// We set infinity to prevent MaxListenersExceededWarning which get logged when listeners > 10
|
|
221
|
+
emitter.setMaxListeners(Infinity);
|
|
222
|
+
|
|
223
|
+
const chainHeaderTracker = new ChainHeaderTracker(logger, api, emitter);
|
|
224
|
+
const syncingStatusTracker = new SyncingStatusTracker(logger, api, clock, metrics);
|
|
225
|
+
|
|
226
|
+
const blockProposingService = new BlockProposingService(config, loggerVc, api, clock, validatorStore, metrics, {
|
|
227
|
+
broadcastValidation: opts.broadcastValidation ?? defaultOptions.broadcastValidation,
|
|
228
|
+
blindedLocal: opts.blindedLocal ?? defaultOptions.blindedLocal,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const attestationService = new AttestationService(
|
|
232
|
+
loggerVc,
|
|
233
|
+
api,
|
|
234
|
+
clock,
|
|
235
|
+
validatorStore,
|
|
236
|
+
emitter,
|
|
237
|
+
chainHeaderTracker,
|
|
238
|
+
syncingStatusTracker,
|
|
239
|
+
metrics,
|
|
240
|
+
config,
|
|
241
|
+
{
|
|
242
|
+
afterBlockDelaySlotFraction: opts.afterBlockDelaySlotFraction,
|
|
243
|
+
distributedAggregationSelection: opts.distributed,
|
|
244
|
+
}
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const syncCommitteeService = new SyncCommitteeService(
|
|
248
|
+
config,
|
|
249
|
+
loggerVc,
|
|
250
|
+
api,
|
|
251
|
+
clock,
|
|
252
|
+
validatorStore,
|
|
253
|
+
emitter,
|
|
254
|
+
chainHeaderTracker,
|
|
255
|
+
syncingStatusTracker,
|
|
256
|
+
metrics,
|
|
257
|
+
{
|
|
258
|
+
scAfterBlockDelaySlotFraction: opts.scAfterBlockDelaySlotFraction,
|
|
259
|
+
distributedAggregationSelection: opts.distributed,
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
return new Validator({
|
|
264
|
+
opts,
|
|
265
|
+
genesis,
|
|
266
|
+
validatorStore,
|
|
267
|
+
slashingProtection,
|
|
268
|
+
blockProposingService,
|
|
269
|
+
attestationService,
|
|
270
|
+
syncCommitteeService,
|
|
271
|
+
config,
|
|
272
|
+
api,
|
|
273
|
+
clock,
|
|
274
|
+
chainHeaderTracker,
|
|
275
|
+
syncingStatusTracker,
|
|
276
|
+
logger,
|
|
277
|
+
db,
|
|
278
|
+
metrics,
|
|
279
|
+
controller,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** Waits for genesis and genesis time */
|
|
284
|
+
static async initializeFromBeaconNode(opts: ValidatorOptions, metrics?: Metrics | null): Promise<Validator> {
|
|
285
|
+
const {logger, config} = opts;
|
|
286
|
+
|
|
287
|
+
let api: ApiClient;
|
|
288
|
+
const {clientOrUrls, globalInit} = opts.api;
|
|
289
|
+
if (typeof clientOrUrls === "string" || Array.isArray(clientOrUrls)) {
|
|
290
|
+
const urls = typeof clientOrUrls === "string" ? [clientOrUrls] : clientOrUrls;
|
|
291
|
+
// This new api instance can make do with default timeout as a faster timeout is
|
|
292
|
+
// not necessary since this instance won't be used for validator duties
|
|
293
|
+
api = getClient({urls, globalInit: {signal: opts.abortController.signal, ...globalInit}}, {config, logger});
|
|
294
|
+
logger.info("Beacon node", {
|
|
295
|
+
urls: urls.map(toPrintableUrl).toString(),
|
|
296
|
+
requestWireFormat: globalInit?.requestWireFormat ?? defaultInit.requestWireFormat,
|
|
297
|
+
responseWireFormat: globalInit?.responseWireFormat ?? defaultInit.responseWireFormat,
|
|
298
|
+
});
|
|
299
|
+
} else {
|
|
300
|
+
api = clientOrUrls;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const genesis = await waitForGenesis(api, opts.logger, opts.abortController.signal);
|
|
304
|
+
logger.info("Genesis fetched from the beacon node");
|
|
305
|
+
|
|
306
|
+
const res = await api.config.getSpec();
|
|
307
|
+
assertEqualParams(config, res.value());
|
|
308
|
+
logger.info("Verified connected beacon node and validator have same the config");
|
|
309
|
+
|
|
310
|
+
await assertEqualGenesis(opts, genesis);
|
|
311
|
+
logger.info("Verified connected beacon node and validator have the same genesisValidatorRoot");
|
|
312
|
+
|
|
313
|
+
const {broadcastValidation = defaultOptions.broadcastValidation, valProposerConfig} = opts;
|
|
314
|
+
const defaultBuilderSelection =
|
|
315
|
+
valProposerConfig?.defaultConfig.builder?.selection ?? defaultOptions.builderSelection;
|
|
316
|
+
const strictFeeRecipientCheck = valProposerConfig?.defaultConfig.strictFeeRecipientCheck ?? false;
|
|
317
|
+
const suggestedFeeRecipient = valProposerConfig?.defaultConfig.feeRecipient ?? defaultOptions.suggestedFeeRecipient;
|
|
318
|
+
|
|
319
|
+
logger.info("Initializing validator", {
|
|
320
|
+
broadcastValidation,
|
|
321
|
+
defaultBuilderSelection,
|
|
322
|
+
suggestedFeeRecipient,
|
|
323
|
+
strictFeeRecipientCheck,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
metrics?.defaultConfiguration.set({builderSelection: defaultBuilderSelection, broadcastValidation}, 1);
|
|
327
|
+
|
|
328
|
+
// Instantiates block and attestation services and runs them once the chain has been started.
|
|
329
|
+
return Validator.init(opts, genesis, metrics);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
removeDutiesForKey(pubkey: PubkeyHex): void {
|
|
333
|
+
this.blockProposingService.removeDutiesForKey(pubkey);
|
|
334
|
+
this.attestationService.removeDutiesForKey(pubkey);
|
|
335
|
+
this.syncCommitteeService.removeDutiesForKey(pubkey);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Stops all validator functions.
|
|
340
|
+
*/
|
|
341
|
+
async close(): Promise<void> {
|
|
342
|
+
if (this.state === Status.closed) return;
|
|
343
|
+
this.controller.abort();
|
|
344
|
+
await this.db.close();
|
|
345
|
+
this.state = Status.closed;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async importInterchange(interchange: Interchange): Promise<void> {
|
|
349
|
+
return this.slashingProtection.importInterchange(interchange, this.genesis.genesisValidatorsRoot);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async exportInterchange(pubkeys: BLSPubkey[], formatVersion: InterchangeFormatVersion): Promise<Interchange> {
|
|
353
|
+
return this.slashingProtection.exportInterchange(this.genesis.genesisValidatorsRoot, pubkeys, formatVersion);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Perform a voluntary exit for the given validator by its key.
|
|
358
|
+
*/
|
|
359
|
+
async voluntaryExit(publicKey: string, exitEpoch?: number): Promise<void> {
|
|
360
|
+
const signedVoluntaryExit = await this.signVoluntaryExit(publicKey, exitEpoch);
|
|
361
|
+
|
|
362
|
+
(await this.api.beacon.submitPoolVoluntaryExit({signedVoluntaryExit})).assertOk();
|
|
363
|
+
|
|
364
|
+
this.logger.info(`Submitted voluntary exit for ${publicKey} to the network`);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Create a signed voluntary exit message for the given validator by its key.
|
|
369
|
+
*/
|
|
370
|
+
async signVoluntaryExit(publicKey: string, exitEpoch?: number): Promise<phase0.SignedVoluntaryExit> {
|
|
371
|
+
const validators = (
|
|
372
|
+
await this.api.beacon.postStateValidators({stateId: "head", validatorIds: [publicKey]})
|
|
373
|
+
).value();
|
|
374
|
+
|
|
375
|
+
const validator = validators[0];
|
|
376
|
+
if (validator === undefined) {
|
|
377
|
+
throw new Error(`Validator pubkey ${publicKey} not found in state`);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (exitEpoch === undefined) {
|
|
381
|
+
exitEpoch = computeEpochAtSlot(getCurrentSlot(this.config, this.clock.genesisTime));
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return this.validatorStore.signVoluntaryExit(publicKey, validator.index, exitEpoch);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** Assert the same genesisValidatorRoot and genesisTime */
|
|
389
|
+
async function assertEqualGenesis(opts: ValidatorOptions, genesis: Genesis): Promise<void> {
|
|
390
|
+
const nodeGenesisValidatorRoot = genesis.genesisValidatorsRoot;
|
|
391
|
+
const metaDataRepository = new MetaDataRepository(opts.db);
|
|
392
|
+
const genesisValidatorsRoot = await metaDataRepository.getGenesisValidatorsRoot();
|
|
393
|
+
if (genesisValidatorsRoot) {
|
|
394
|
+
if (!ssz.Root.equals(genesisValidatorsRoot, nodeGenesisValidatorRoot)) {
|
|
395
|
+
// this happens when the existing validator db served another network before
|
|
396
|
+
opts.logger.error("Not the same genesisValidatorRoot", {
|
|
397
|
+
expected: toRootHex(nodeGenesisValidatorRoot),
|
|
398
|
+
actual: toRootHex(genesisValidatorsRoot),
|
|
399
|
+
});
|
|
400
|
+
throw new NotEqualParamsError("Not the same genesisValidatorRoot");
|
|
401
|
+
}
|
|
402
|
+
} else {
|
|
403
|
+
await metaDataRepository.setGenesisValidatorsRoot(nodeGenesisValidatorRoot);
|
|
404
|
+
opts.logger.info("Persisted genesisValidatorRoot", toRootHex(nodeGenesisValidatorRoot));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const nodeGenesisTime = genesis.genesisTime;
|
|
408
|
+
const genesisTime = await metaDataRepository.getGenesisTime();
|
|
409
|
+
if (genesisTime !== null) {
|
|
410
|
+
if (genesisTime !== nodeGenesisTime) {
|
|
411
|
+
opts.logger.error("Not the same genesisTime", {expected: nodeGenesisTime, actual: genesisTime});
|
|
412
|
+
throw new NotEqualParamsError("Not the same genesisTime");
|
|
413
|
+
}
|
|
414
|
+
} else {
|
|
415
|
+
await metaDataRepository.setGenesisTime(nodeGenesisTime);
|
|
416
|
+
opts.logger.info("Persisted genesisTime", nodeGenesisTime);
|
|
417
|
+
}
|
|
418
|
+
}
|