@lodestar/validator 1.39.0-dev.aceb5b7416 → 1.39.0-dev.ad129ced66
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 +0 -11
- package/lib/services/attestation.d.ts.map +1 -1
- package/lib/services/attestation.js +1 -75
- package/lib/services/attestation.js.map +1 -1
- package/lib/services/attestationDuties.d.ts +8 -0
- package/lib/services/attestationDuties.d.ts.map +1 -1
- package/lib/services/attestationDuties.js +60 -2
- package/lib/services/attestationDuties.js.map +1 -1
- package/lib/services/syncCommittee.d.ts +0 -10
- package/lib/services/syncCommittee.d.ts.map +1 -1
- package/lib/services/syncCommittee.js +0 -67
- package/lib/services/syncCommittee.js.map +1 -1
- package/lib/services/syncCommitteeDuties.d.ts +9 -0
- package/lib/services/syncCommitteeDuties.d.ts.map +1 -1
- package/lib/services/syncCommitteeDuties.js +64 -2
- package/lib/services/syncCommitteeDuties.js.map +1 -1
- package/lib/util/externalSignerClient.d.ts +2 -6
- package/lib/util/externalSignerClient.d.ts.map +1 -1
- package/lib/util/externalSignerClient.js +21 -7
- package/lib/util/externalSignerClient.js.map +1 -1
- package/package.json +17 -15
- package/src/services/attestation.ts +3 -100
- package/src/services/attestationDuties.ts +79 -2
- package/src/services/syncCommittee.ts +2 -93
- package/src/services/syncCommitteeDuties.ts +85 -3
- package/src/util/externalSignerClient.ts +28 -10
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {ApiClient
|
|
1
|
+
import {ApiClient} from "@lodestar/api";
|
|
2
2
|
import {ChainForkConfig} from "@lodestar/config";
|
|
3
3
|
import {ForkName, isForkPostAltair} from "@lodestar/params";
|
|
4
|
-
import {
|
|
5
|
-
import {BLSSignature, CommitteeIndex, Root, Slot, altair} from "@lodestar/types";
|
|
4
|
+
import {CommitteeIndex, Root, Slot, altair} from "@lodestar/types";
|
|
6
5
|
import {sleep} from "@lodestar/utils";
|
|
7
6
|
import {Metrics} from "../metrics.js";
|
|
8
7
|
import {PubkeyHex} from "../types.js";
|
|
@@ -73,18 +72,6 @@ export class SyncCommitteeService {
|
|
|
73
72
|
return;
|
|
74
73
|
}
|
|
75
74
|
|
|
76
|
-
if (this.opts?.distributedAggregationSelection) {
|
|
77
|
-
// Validator in distributed cluster only has a key share, not the full private key.
|
|
78
|
-
// The partial selection proofs must be exchanged for combined selection proofs by
|
|
79
|
-
// calling submitSyncCommitteeSelections on the distributed validator middleware client.
|
|
80
|
-
// This will run in parallel to other sync committee tasks but must be finished before starting
|
|
81
|
-
// sync committee contributions as it is required to correctly determine if validator is aggregator
|
|
82
|
-
// and to produce a ContributionAndProof that can be threshold aggregated by the middleware client.
|
|
83
|
-
this.runDistributedAggregationSelectionTasks(fork, dutiesAtSlot, slot, signal).catch((e) =>
|
|
84
|
-
this.logger.error("Error on sync committee aggregation selection", {slot}, e)
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
75
|
// unlike Attestation, SyncCommitteeSignature could be published asap
|
|
89
76
|
// especially with lodestar, it's very busy at ATTESTATION_DUE_BPS of the slot
|
|
90
77
|
// see https://github.com/ChainSafe/lodestar/issues/4608
|
|
@@ -257,82 +244,4 @@ export class SyncCommitteeService {
|
|
|
257
244
|
}
|
|
258
245
|
}
|
|
259
246
|
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Performs additional sync committee contribution tasks required if validator is part of distributed cluster
|
|
263
|
-
*
|
|
264
|
-
* 1. Exchange partial for combined selection proofs
|
|
265
|
-
* 2. Determine validators that should produce sync committee contribution
|
|
266
|
-
* 3. Mutate duty objects to set selection proofs for aggregators
|
|
267
|
-
*
|
|
268
|
-
* See https://docs.google.com/document/d/1q9jOTPcYQa-3L8luRvQJ-M0eegtba4Nmon3dpO79TMk/mobilebasic
|
|
269
|
-
*/
|
|
270
|
-
private async runDistributedAggregationSelectionTasks(
|
|
271
|
-
fork: ForkName,
|
|
272
|
-
duties: SyncDutyAndProofs[],
|
|
273
|
-
slot: number,
|
|
274
|
-
signal: AbortSignal
|
|
275
|
-
): Promise<void> {
|
|
276
|
-
const partialSelections: routes.validator.SyncCommitteeSelection[] = [];
|
|
277
|
-
|
|
278
|
-
for (const {duty, selectionProofs} of duties) {
|
|
279
|
-
const validatorSelections: routes.validator.SyncCommitteeSelection[] = selectionProofs.map(
|
|
280
|
-
({subcommitteeIndex, partialSelectionProof}) => ({
|
|
281
|
-
validatorIndex: duty.validatorIndex,
|
|
282
|
-
slot,
|
|
283
|
-
subcommitteeIndex,
|
|
284
|
-
selectionProof: partialSelectionProof as BLSSignature,
|
|
285
|
-
})
|
|
286
|
-
);
|
|
287
|
-
partialSelections.push(...validatorSelections);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
this.logger.debug("Submitting partial sync committee selection proofs", {slot, count: partialSelections.length});
|
|
291
|
-
|
|
292
|
-
const res = await Promise.race([
|
|
293
|
-
this.api.validator.submitSyncCommitteeSelections({selections: partialSelections}),
|
|
294
|
-
// Exit sync committee contributions flow if there is no response after CONTRIBUTION_DUE_BPS of the slot.
|
|
295
|
-
// This is in contrast to attestations aggregations flow which is already exited at ATTESTATION_DUE_BPS of the slot
|
|
296
|
-
// because for sync committee is not required to resubscribe to subnets as beacon node will assume
|
|
297
|
-
// validator always aggregates. This allows us to wait until we have to produce sync committee contributions.
|
|
298
|
-
// Note that the sync committee contributions flow is not explicitly exited but rather will be skipped
|
|
299
|
-
// due to the fact that calculation of `is_sync_committee_aggregator` in SyncCommitteeDutiesService is not done
|
|
300
|
-
// and selectionProof is set to null, meaning no validator will be considered an aggregator.
|
|
301
|
-
sleep(this.config.getSyncContributionDueMs(fork) - this.clock.msFromSlot(slot), signal),
|
|
302
|
-
]);
|
|
303
|
-
|
|
304
|
-
if (!res) {
|
|
305
|
-
throw new Error("Failed to receive combined selection proofs before CONTRIBUTION_DUE_BPS of the slot");
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
const combinedSelections = res.value();
|
|
309
|
-
this.logger.debug("Received combined sync committee selection proofs", {slot, count: combinedSelections.length});
|
|
310
|
-
|
|
311
|
-
for (const dutyAndProofs of duties) {
|
|
312
|
-
const {validatorIndex, subnets} = dutyAndProofs.duty;
|
|
313
|
-
|
|
314
|
-
for (const subnet of subnets) {
|
|
315
|
-
const logCtxValidator = {slot, index: subnet, validatorIndex};
|
|
316
|
-
|
|
317
|
-
const combinedSelection = combinedSelections.find(
|
|
318
|
-
(s) => s.validatorIndex === validatorIndex && s.slot === slot && s.subcommitteeIndex === subnet
|
|
319
|
-
);
|
|
320
|
-
|
|
321
|
-
if (!combinedSelection) {
|
|
322
|
-
this.logger.warn("Did not receive combined sync committee selection proof", logCtxValidator);
|
|
323
|
-
continue;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const isAggregator = isSyncCommitteeAggregator(combinedSelection.selectionProof);
|
|
327
|
-
|
|
328
|
-
if (isAggregator) {
|
|
329
|
-
const selectionProofObject = dutyAndProofs.selectionProofs.find((p) => p.subcommitteeIndex === subnet);
|
|
330
|
-
if (selectionProofObject) {
|
|
331
|
-
// Update selection proof by mutating proof objects in duty object
|
|
332
|
-
selectionProofObject.selectionProof = combinedSelection.selectionProof;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
247
|
}
|
|
@@ -85,7 +85,7 @@ export class SyncCommitteeDutiesService {
|
|
|
85
85
|
private readonly config: ChainForkConfig,
|
|
86
86
|
private readonly logger: LoggerVc,
|
|
87
87
|
private readonly api: ApiClient,
|
|
88
|
-
clock: IClock,
|
|
88
|
+
private readonly clock: IClock,
|
|
89
89
|
private readonly validatorStore: ValidatorStore,
|
|
90
90
|
syncingStatusTracker: SyncingStatusTracker,
|
|
91
91
|
metrics: Metrics | null,
|
|
@@ -134,6 +134,18 @@ export class SyncCommitteeDutiesService {
|
|
|
134
134
|
selectionProofs: await this.getSelectionProofs(slot, dutyAtPeriod.duty),
|
|
135
135
|
});
|
|
136
136
|
}
|
|
137
|
+
|
|
138
|
+
if (this.opts?.distributedAggregationSelection) {
|
|
139
|
+
// Validator in distributed cluster only has a key share, not the full private key.
|
|
140
|
+
// The partial selection proofs must be exchanged for combined selection proofs by
|
|
141
|
+
// calling submitSyncCommitteeSelections on the distributed validator middleware client.
|
|
142
|
+
// This will run in parallel to other sync committee tasks but must be finished before starting
|
|
143
|
+
// sync committee contributions as it is required to correctly determine if validator is aggregator
|
|
144
|
+
// and to produce a ContributionAndProof that can be threshold aggregated by the middleware client.
|
|
145
|
+
this.runDistributedAggregationSelectionTasks(duties, slot).catch((e) =>
|
|
146
|
+
this.logger.debug("Error on sync committee aggregation selection", {slot}, e)
|
|
147
|
+
);
|
|
148
|
+
}
|
|
137
149
|
}
|
|
138
150
|
|
|
139
151
|
return duties;
|
|
@@ -307,8 +319,8 @@ export class SyncCommitteeDutiesService {
|
|
|
307
319
|
if (this.opts?.distributedAggregationSelection) {
|
|
308
320
|
// Validator in distributed cluster only has a key share, not the full private key.
|
|
309
321
|
// Passing a partial selection proof to `is_sync_committee_aggregator` would produce incorrect result.
|
|
310
|
-
//
|
|
311
|
-
// distributed validator middleware client
|
|
322
|
+
// For all duties in the slot, aggregators are determined by exchanging partial for combined selection
|
|
323
|
+
// proofs retrieved from distributed validator middleware client at beginning of every slot.
|
|
312
324
|
dutiesAndProofs.push({
|
|
313
325
|
selectionProof: null,
|
|
314
326
|
partialSelectionProof: selectionProof,
|
|
@@ -334,4 +346,74 @@ export class SyncCommitteeDutiesService {
|
|
|
334
346
|
}
|
|
335
347
|
}
|
|
336
348
|
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Performs additional sync committee contribution tasks required if validator is part of distributed cluster
|
|
352
|
+
*
|
|
353
|
+
* 1. Exchange partial for combined selection proofs
|
|
354
|
+
* 2. Determine validators that should produce sync committee contribution
|
|
355
|
+
* 3. Mutate duty objects to set selection proofs for aggregators
|
|
356
|
+
*/
|
|
357
|
+
private async runDistributedAggregationSelectionTasks(duties: SyncDutyAndProofs[], slot: number): Promise<void> {
|
|
358
|
+
if (duties.length === 0) {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const partialSelections: routes.validator.SyncCommitteeSelection[] = [];
|
|
363
|
+
|
|
364
|
+
for (const {duty, selectionProofs} of duties) {
|
|
365
|
+
const validatorSelections: routes.validator.SyncCommitteeSelection[] = selectionProofs.map(
|
|
366
|
+
({subcommitteeIndex, partialSelectionProof}) => ({
|
|
367
|
+
validatorIndex: duty.validatorIndex,
|
|
368
|
+
slot,
|
|
369
|
+
subcommitteeIndex,
|
|
370
|
+
selectionProof: partialSelectionProof as BLSSignature,
|
|
371
|
+
})
|
|
372
|
+
);
|
|
373
|
+
partialSelections.push(...validatorSelections);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
this.logger.debug("Submitting partial sync committee selection proofs", {slot, count: partialSelections.length});
|
|
377
|
+
|
|
378
|
+
const res = await this.api.validator.submitSyncCommitteeSelections(
|
|
379
|
+
{selections: partialSelections},
|
|
380
|
+
{
|
|
381
|
+
// Exit sync committee contributions flow if there is no response until CONTRIBUTION_DUE_BPS of the slot.
|
|
382
|
+
// Note that the sync committee contributions flow is not explicitly exited but rather will be skipped
|
|
383
|
+
// due to the fact that calculation of `is_sync_committee_aggregator` in SyncCommitteeDutiesService is not done
|
|
384
|
+
// and selectionProof is set to null, meaning no validator will be considered an aggregator.
|
|
385
|
+
timeoutMs: this.config.getSyncContributionDueMs(this.config.getForkName(slot)) - this.clock.msFromSlot(slot),
|
|
386
|
+
}
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
const combinedSelections = res.value();
|
|
390
|
+
this.logger.debug("Received combined sync committee selection proofs", {slot, count: combinedSelections.length});
|
|
391
|
+
|
|
392
|
+
for (const dutyAndProofs of duties) {
|
|
393
|
+
const {validatorIndex, subnets} = dutyAndProofs.duty;
|
|
394
|
+
|
|
395
|
+
for (const subnet of subnets) {
|
|
396
|
+
const logCtxValidator = {slot, index: subnet, validatorIndex};
|
|
397
|
+
|
|
398
|
+
const combinedSelection = combinedSelections.find(
|
|
399
|
+
(s) => s.validatorIndex === validatorIndex && s.slot === slot && s.subcommitteeIndex === subnet
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
if (!combinedSelection) {
|
|
403
|
+
this.logger.debug("Did not receive combined sync committee selection proof", logCtxValidator);
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const isAggregator = isSyncCommitteeAggregator(combinedSelection.selectionProof);
|
|
408
|
+
|
|
409
|
+
if (isAggregator) {
|
|
410
|
+
const selectionProofObject = dutyAndProofs.selectionProofs.find((p) => p.subcommitteeIndex === subnet);
|
|
411
|
+
if (selectionProofObject) {
|
|
412
|
+
// Update selection proof by mutating proof objects in duty object
|
|
413
|
+
selectionProofObject.selectionProof = combinedSelection.selectionProof;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
337
419
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {ContainerType, ValueOf} from "@chainsafe/ssz";
|
|
2
2
|
import {BeaconConfig} from "@lodestar/config";
|
|
3
|
-
import {ForkPreBellatrix, ForkSeq} from "@lodestar/params";
|
|
3
|
+
import {ForkName, ForkPreBellatrix, ForkSeq, isForkPostDeneb} from "@lodestar/params";
|
|
4
4
|
import {blindedOrFullBlockToHeader, computeEpochAtSlot} from "@lodestar/state-transition";
|
|
5
5
|
import {
|
|
6
6
|
AggregateAndProof,
|
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
RootHex,
|
|
12
12
|
Slot,
|
|
13
13
|
altair,
|
|
14
|
-
capella,
|
|
15
14
|
phase0,
|
|
16
15
|
ssz,
|
|
17
16
|
sszTypesFor,
|
|
@@ -33,7 +32,6 @@ export enum SignableMessageType {
|
|
|
33
32
|
SYNC_COMMITTEE_SELECTION_PROOF = "SYNC_COMMITTEE_SELECTION_PROOF",
|
|
34
33
|
SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF = "SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF",
|
|
35
34
|
VALIDATOR_REGISTRATION = "VALIDATOR_REGISTRATION",
|
|
36
|
-
BLS_TO_EXECUTION_CHANGE = "BLS_TO_EXECUTION_CHANGE",
|
|
37
35
|
}
|
|
38
36
|
|
|
39
37
|
const AggregationSlotType = new ContainerType({
|
|
@@ -82,8 +80,7 @@ export type SignableMessage =
|
|
|
82
80
|
| {type: SignableMessageType.SYNC_COMMITTEE_MESSAGE; data: ValueOf<typeof SyncCommitteeMessageType>}
|
|
83
81
|
| {type: SignableMessageType.SYNC_COMMITTEE_SELECTION_PROOF; data: ValueOf<typeof SyncAggregatorSelectionDataType>}
|
|
84
82
|
| {type: SignableMessageType.SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF; data: altair.ContributionAndProof}
|
|
85
|
-
| {type: SignableMessageType.VALIDATOR_REGISTRATION; data: ValidatorRegistrationV1}
|
|
86
|
-
| {type: SignableMessageType.BLS_TO_EXECUTION_CHANGE; data: capella.BLSToExecutionChange};
|
|
83
|
+
| {type: SignableMessageType.VALIDATOR_REGISTRATION; data: ValidatorRegistrationV1};
|
|
87
84
|
|
|
88
85
|
const requiresForkInfo: Record<SignableMessageType, boolean> = {
|
|
89
86
|
[SignableMessageType.AGGREGATION_SLOT]: true,
|
|
@@ -98,7 +95,6 @@ const requiresForkInfo: Record<SignableMessageType, boolean> = {
|
|
|
98
95
|
[SignableMessageType.SYNC_COMMITTEE_SELECTION_PROOF]: true,
|
|
99
96
|
[SignableMessageType.SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF]: true,
|
|
100
97
|
[SignableMessageType.VALIDATOR_REGISTRATION]: false,
|
|
101
|
-
[SignableMessageType.BLS_TO_EXECUTION_CHANGE]: true,
|
|
102
98
|
};
|
|
103
99
|
|
|
104
100
|
type Web3SignerSerializedRequest = {
|
|
@@ -147,12 +143,12 @@ export async function externalSignerPostSignature(
|
|
|
147
143
|
requestObj.signingRoot = toRootHex(signingRoot);
|
|
148
144
|
|
|
149
145
|
if (requiresForkInfo[signableMessage.type]) {
|
|
150
|
-
const forkInfo = config.
|
|
146
|
+
const forkInfo = getForkInfoForSigning(config, signingSlot, signableMessage.type);
|
|
151
147
|
requestObj.fork_info = {
|
|
152
148
|
fork: {
|
|
153
149
|
previous_version: toHex(forkInfo.prevVersion),
|
|
154
150
|
current_version: toHex(forkInfo.version),
|
|
155
|
-
epoch: String(
|
|
151
|
+
epoch: String(forkInfo.epoch),
|
|
156
152
|
},
|
|
157
153
|
genesis_validators_root: toRootHex(config.genesisValidatorsRoot),
|
|
158
154
|
};
|
|
@@ -270,8 +266,30 @@ function serializerSignableMessagePayload(config: BeaconConfig, payload: Signabl
|
|
|
270
266
|
|
|
271
267
|
case SignableMessageType.VALIDATOR_REGISTRATION:
|
|
272
268
|
return {validator_registration: ssz.bellatrix.ValidatorRegistrationV1.toJson(payload.data)};
|
|
269
|
+
}
|
|
270
|
+
}
|
|
273
271
|
|
|
274
|
-
|
|
275
|
-
|
|
272
|
+
function getForkInfoForSigning(
|
|
273
|
+
config: BeaconConfig,
|
|
274
|
+
signingSlot: Slot,
|
|
275
|
+
messageType: SignableMessageType
|
|
276
|
+
): {version: Uint8Array; prevVersion: Uint8Array; epoch: number} {
|
|
277
|
+
const forkInfo = config.getForkInfo(signingSlot);
|
|
278
|
+
|
|
279
|
+
if (messageType === SignableMessageType.VOLUNTARY_EXIT && isForkPostDeneb(forkInfo.name)) {
|
|
280
|
+
// Always uses Capella fork post-Deneb (EIP-7044)
|
|
281
|
+
const capellaFork = config.forks[ForkName.capella];
|
|
282
|
+
return {
|
|
283
|
+
version: capellaFork.version,
|
|
284
|
+
prevVersion: capellaFork.prevVersion,
|
|
285
|
+
epoch: capellaFork.epoch,
|
|
286
|
+
};
|
|
276
287
|
}
|
|
288
|
+
|
|
289
|
+
// Use the fork at the signing slot by default
|
|
290
|
+
return {
|
|
291
|
+
version: forkInfo.version,
|
|
292
|
+
prevVersion: forkInfo.prevVersion,
|
|
293
|
+
epoch: computeEpochAtSlot(signingSlot),
|
|
294
|
+
};
|
|
277
295
|
}
|