@lodestar/validator 1.35.0-dev.8689cc3545 → 1.35.0-dev.8b45b1e978
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/package.json +15 -13
- package/lib/buckets.d.ts.map +0 -1
- package/lib/defaults.d.ts.map +0 -1
- package/lib/genesis.d.ts.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/metrics.d.ts.map +0 -1
- package/lib/repositories/index.d.ts.map +0 -1
- package/lib/repositories/metaDataRepository.d.ts.map +0 -1
- package/lib/services/attestation.d.ts.map +0 -1
- package/lib/services/attestationDuties.d.ts.map +0 -1
- package/lib/services/block.d.ts.map +0 -1
- package/lib/services/blockDuties.d.ts.map +0 -1
- package/lib/services/chainHeaderTracker.d.ts.map +0 -1
- package/lib/services/doppelgangerService.d.ts.map +0 -1
- package/lib/services/emitter.d.ts.map +0 -1
- package/lib/services/externalSignerSync.d.ts.map +0 -1
- package/lib/services/indices.d.ts.map +0 -1
- package/lib/services/prepareBeaconProposer.d.ts.map +0 -1
- package/lib/services/syncCommittee.d.ts.map +0 -1
- package/lib/services/syncCommitteeDuties.d.ts.map +0 -1
- package/lib/services/syncingStatusTracker.d.ts.map +0 -1
- package/lib/services/utils.d.ts.map +0 -1
- package/lib/services/validatorStore.d.ts.map +0 -1
- package/lib/slashingProtection/attestation/attestationByTargetRepository.d.ts.map +0 -1
- package/lib/slashingProtection/attestation/attestationLowerBoundRepository.d.ts.map +0 -1
- package/lib/slashingProtection/attestation/errors.d.ts.map +0 -1
- package/lib/slashingProtection/attestation/index.d.ts.map +0 -1
- package/lib/slashingProtection/block/blockBySlotRepository.d.ts.map +0 -1
- package/lib/slashingProtection/block/errors.d.ts.map +0 -1
- package/lib/slashingProtection/block/index.d.ts.map +0 -1
- package/lib/slashingProtection/index.d.ts.map +0 -1
- package/lib/slashingProtection/interchange/errors.d.ts.map +0 -1
- package/lib/slashingProtection/interchange/formats/completeV4.d.ts.map +0 -1
- package/lib/slashingProtection/interchange/formats/index.d.ts.map +0 -1
- package/lib/slashingProtection/interchange/formats/v5.d.ts.map +0 -1
- package/lib/slashingProtection/interchange/index.d.ts.map +0 -1
- package/lib/slashingProtection/interchange/parseInterchange.d.ts.map +0 -1
- package/lib/slashingProtection/interchange/serializeInterchange.d.ts.map +0 -1
- package/lib/slashingProtection/interchange/types.d.ts.map +0 -1
- package/lib/slashingProtection/interface.d.ts.map +0 -1
- package/lib/slashingProtection/minMaxSurround/distanceStoreRepository.d.ts.map +0 -1
- package/lib/slashingProtection/minMaxSurround/errors.d.ts.map +0 -1
- package/lib/slashingProtection/minMaxSurround/index.d.ts.map +0 -1
- package/lib/slashingProtection/minMaxSurround/interface.d.ts.map +0 -1
- package/lib/slashingProtection/minMaxSurround/minMaxSurround.d.ts.map +0 -1
- package/lib/slashingProtection/types.d.ts.map +0 -1
- package/lib/slashingProtection/utils.d.ts.map +0 -1
- package/lib/types.d.ts.map +0 -1
- package/lib/util/batch.d.ts.map +0 -1
- package/lib/util/clock.d.ts.map +0 -1
- package/lib/util/difference.d.ts.map +0 -1
- package/lib/util/externalSignerClient.d.ts.map +0 -1
- package/lib/util/format.d.ts.map +0 -1
- package/lib/util/index.d.ts.map +0 -1
- package/lib/util/logger.d.ts.map +0 -1
- package/lib/util/params.d.ts.map +0 -1
- package/lib/util/url.d.ts.map +0 -1
- package/lib/validator.d.ts.map +0 -1
- package/src/buckets.ts +0 -30
- package/src/defaults.ts +0 -8
- package/src/genesis.ts +0 -19
- package/src/index.ts +0 -22
- package/src/metrics.ts +0 -417
- package/src/repositories/index.ts +0 -1
- package/src/repositories/metaDataRepository.ts +0 -42
- package/src/services/attestation.ts +0 -349
- package/src/services/attestationDuties.ts +0 -405
- package/src/services/block.ts +0 -261
- package/src/services/blockDuties.ts +0 -215
- package/src/services/chainHeaderTracker.ts +0 -89
- package/src/services/doppelgangerService.ts +0 -286
- package/src/services/emitter.ts +0 -43
- package/src/services/externalSignerSync.ts +0 -81
- package/src/services/indices.ts +0 -165
- package/src/services/prepareBeaconProposer.ts +0 -119
- package/src/services/syncCommittee.ts +0 -317
- package/src/services/syncCommitteeDuties.ts +0 -337
- package/src/services/syncingStatusTracker.ts +0 -74
- package/src/services/utils.ts +0 -58
- package/src/services/validatorStore.ts +0 -830
- package/src/slashingProtection/attestation/attestationByTargetRepository.ts +0 -77
- package/src/slashingProtection/attestation/attestationLowerBoundRepository.ts +0 -44
- package/src/slashingProtection/attestation/errors.ts +0 -66
- package/src/slashingProtection/attestation/index.ts +0 -171
- package/src/slashingProtection/block/blockBySlotRepository.ts +0 -78
- package/src/slashingProtection/block/errors.ts +0 -28
- package/src/slashingProtection/block/index.ts +0 -94
- package/src/slashingProtection/index.ts +0 -95
- package/src/slashingProtection/interchange/errors.ts +0 -15
- package/src/slashingProtection/interchange/formats/completeV4.ts +0 -125
- package/src/slashingProtection/interchange/formats/index.ts +0 -7
- package/src/slashingProtection/interchange/formats/v5.ts +0 -120
- package/src/slashingProtection/interchange/index.ts +0 -5
- package/src/slashingProtection/interchange/parseInterchange.ts +0 -55
- package/src/slashingProtection/interchange/serializeInterchange.ts +0 -35
- package/src/slashingProtection/interchange/types.ts +0 -18
- package/src/slashingProtection/interface.ts +0 -28
- package/src/slashingProtection/minMaxSurround/distanceStoreRepository.ts +0 -57
- package/src/slashingProtection/minMaxSurround/errors.ts +0 -27
- package/src/slashingProtection/minMaxSurround/index.ts +0 -4
- package/src/slashingProtection/minMaxSurround/interface.ts +0 -23
- package/src/slashingProtection/minMaxSurround/minMaxSurround.ts +0 -104
- package/src/slashingProtection/types.ts +0 -12
- package/src/slashingProtection/utils.ts +0 -42
- package/src/types.ts +0 -31
- package/src/util/batch.ts +0 -15
- package/src/util/clock.ts +0 -164
- package/src/util/difference.ts +0 -10
- package/src/util/externalSignerClient.ts +0 -277
- package/src/util/format.ts +0 -3
- package/src/util/index.ts +0 -6
- package/src/util/logger.ts +0 -51
- package/src/util/params.ts +0 -313
- package/src/util/url.ts +0 -16
- package/src/validator.ts +0 -418
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import {PublicKey} from "@chainsafe/blst";
|
|
2
|
-
import {ChainForkConfig} from "@lodestar/config";
|
|
3
|
-
import {SLOTS_PER_EPOCH} from "@lodestar/params";
|
|
4
|
-
import {fromHex, toPrintableUrl} from "@lodestar/utils";
|
|
5
|
-
import {externalSignerGetKeys} from "../util/externalSignerClient.js";
|
|
6
|
-
import {LoggerVc} from "../util/index.js";
|
|
7
|
-
import {SignerType, ValidatorStore} from "./validatorStore.js";
|
|
8
|
-
|
|
9
|
-
export type ExternalSignerOptions = {
|
|
10
|
-
url?: string;
|
|
11
|
-
fetch?: boolean;
|
|
12
|
-
fetchInterval?: number;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* This service is responsible for keeping the keys managed by the connected
|
|
17
|
-
* external signer and the validator client in sync by adding newly discovered keys
|
|
18
|
-
* and removing no longer present keys on external signer from the validator store.
|
|
19
|
-
*/
|
|
20
|
-
export function pollExternalSignerPubkeys(
|
|
21
|
-
config: ChainForkConfig,
|
|
22
|
-
logger: LoggerVc,
|
|
23
|
-
signal: AbortSignal,
|
|
24
|
-
validatorStore: ValidatorStore,
|
|
25
|
-
opts?: ExternalSignerOptions
|
|
26
|
-
): void {
|
|
27
|
-
const externalSigner = opts ?? {};
|
|
28
|
-
|
|
29
|
-
if (!externalSigner.url || !externalSigner.fetch) {
|
|
30
|
-
return; // Disabled
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async function fetchExternalSignerPubkeys(): Promise<void> {
|
|
34
|
-
// External signer URL is already validated earlier
|
|
35
|
-
const externalSignerUrl = externalSigner.url as string;
|
|
36
|
-
const printableUrl = toPrintableUrl(externalSignerUrl);
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
logger.debug("Fetching public keys from external signer", {url: printableUrl});
|
|
40
|
-
const externalPubkeys = await externalSignerGetKeys(externalSignerUrl);
|
|
41
|
-
assertValidPubkeysHex(externalPubkeys);
|
|
42
|
-
logger.debug("Received public keys from external signer", {url: printableUrl, count: externalPubkeys.length});
|
|
43
|
-
|
|
44
|
-
const localPubkeys = validatorStore.getRemoteSignerPubkeys(externalSignerUrl);
|
|
45
|
-
logger.debug("Local public keys stored for external signer", {url: printableUrl, count: localPubkeys.length});
|
|
46
|
-
|
|
47
|
-
const localPubkeysSet = new Set(localPubkeys);
|
|
48
|
-
for (const pubkey of externalPubkeys) {
|
|
49
|
-
if (!localPubkeysSet.has(pubkey)) {
|
|
50
|
-
await validatorStore.addSigner({type: SignerType.Remote, pubkey, url: externalSignerUrl});
|
|
51
|
-
logger.info("Added remote signer", {pubkey, url: printableUrl});
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const externalPubkeysSet = new Set(externalPubkeys);
|
|
56
|
-
for (const pubkey of localPubkeys) {
|
|
57
|
-
if (!externalPubkeysSet.has(pubkey)) {
|
|
58
|
-
validatorStore.removeSigner(pubkey);
|
|
59
|
-
logger.info("Removed remote signer", {pubkey, url: printableUrl});
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
} catch (e) {
|
|
63
|
-
logger.error("Failed to fetch public keys from external signer", {url: printableUrl}, e as Error);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const interval = setInterval(
|
|
68
|
-
fetchExternalSignerPubkeys,
|
|
69
|
-
externalSigner.fetchInterval ??
|
|
70
|
-
// Once per epoch by default
|
|
71
|
-
SLOTS_PER_EPOCH * config.SECONDS_PER_SLOT * 1000
|
|
72
|
-
);
|
|
73
|
-
signal.addEventListener("abort", () => clearInterval(interval), {once: true});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function assertValidPubkeysHex(pubkeysHex: string[]): void {
|
|
77
|
-
for (const pubkeyHex of pubkeysHex) {
|
|
78
|
-
const pubkeyBytes = fromHex(pubkeyHex);
|
|
79
|
-
PublicKey.fromBytes(pubkeyBytes, true);
|
|
80
|
-
}
|
|
81
|
-
}
|
package/src/services/indices.ts
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import {ApiClient, routes} from "@lodestar/api";
|
|
2
|
-
import {ValidatorIndex} from "@lodestar/types";
|
|
3
|
-
import {Logger, MapDef, toPubkeyHex} from "@lodestar/utils";
|
|
4
|
-
import {Metrics} from "../metrics.js";
|
|
5
|
-
import {batchItems} from "../util/index.js";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* This is to prevent the "Request body is too large" issue for http post.
|
|
9
|
-
* Typical servers accept up to 1MB (2 ** 20 bytes) of request body, for example fastify and nginx.
|
|
10
|
-
* A hex encoded public key with "0x"-prefix has a size of 98 bytes + 2 bytes to account for commas
|
|
11
|
-
* and other JSON padding. `Math.floor(2 ** 20 / 100) == 10485`, we can send up to ~10k keys per request.
|
|
12
|
-
*/
|
|
13
|
-
const PUBKEYS_PER_REQUEST = 10_000;
|
|
14
|
-
|
|
15
|
-
// To assist with readability
|
|
16
|
-
type PubkeyHex = string;
|
|
17
|
-
|
|
18
|
-
// To assist with logging statuses, we only log the statuses that are not active_exiting or withdrawal_possible
|
|
19
|
-
type SimpleValidatorStatus = "pending" | "active" | "exited" | "withdrawn";
|
|
20
|
-
|
|
21
|
-
const statusToSimpleStatusMapping = (status: routes.beacon.ValidatorStatus): SimpleValidatorStatus => {
|
|
22
|
-
switch (status) {
|
|
23
|
-
case "active_exiting":
|
|
24
|
-
case "active_slashed":
|
|
25
|
-
case "active_ongoing":
|
|
26
|
-
return "active";
|
|
27
|
-
|
|
28
|
-
case "withdrawal_possible":
|
|
29
|
-
case "exited_slashed":
|
|
30
|
-
case "exited_unslashed":
|
|
31
|
-
return "exited";
|
|
32
|
-
|
|
33
|
-
case "pending_initialized":
|
|
34
|
-
case "pending_queued":
|
|
35
|
-
return "pending";
|
|
36
|
-
|
|
37
|
-
case "withdrawal_done":
|
|
38
|
-
return "withdrawn";
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export class IndicesService {
|
|
43
|
-
readonly index2pubkey = new Map<ValidatorIndex, PubkeyHex>();
|
|
44
|
-
/** Indexed by pubkey in hex 0x prefixed */
|
|
45
|
-
readonly pubkey2index = new Map<PubkeyHex, ValidatorIndex>();
|
|
46
|
-
// Request indices once
|
|
47
|
-
private pollValidatorIndicesPromise: Promise<ValidatorIndex[]> | null = null;
|
|
48
|
-
|
|
49
|
-
constructor(
|
|
50
|
-
private readonly logger: Logger,
|
|
51
|
-
private readonly api: ApiClient,
|
|
52
|
-
private readonly metrics: Metrics | null
|
|
53
|
-
) {
|
|
54
|
-
if (metrics) {
|
|
55
|
-
metrics.indices.addCollect(() => metrics.indices.set(this.index2pubkey.size));
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
get indexCount(): number {
|
|
60
|
-
return this.index2pubkey.size;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** Returns the validator index for a given validator pubkey */
|
|
64
|
-
getValidatorIndex(pubKey: PubkeyHex): ValidatorIndex | undefined {
|
|
65
|
-
return this.pubkey2index.get(pubKey);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** Return all known indices from the validatorStore pubkeys */
|
|
69
|
-
getAllLocalIndices(): ValidatorIndex[] {
|
|
70
|
-
return Array.from(this.index2pubkey.keys());
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** Return true if `index` is active part of this validator client */
|
|
74
|
-
hasValidatorIndex(index: ValidatorIndex): boolean {
|
|
75
|
-
return this.index2pubkey.has(index);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async pollValidatorIndices(pubkeysHex: PubkeyHex[]): Promise<ValidatorIndex[]> {
|
|
79
|
-
// Ensures pollValidatorIndicesInternal() is not called more than once at the same time.
|
|
80
|
-
// AttestationDutiesService, SyncCommitteeDutiesService and DoppelgangerService will call this function at the same time, so this will
|
|
81
|
-
// cache the promise and return it to the second caller, preventing calling the API twice for the same data.
|
|
82
|
-
if (this.pollValidatorIndicesPromise) {
|
|
83
|
-
return this.pollValidatorIndicesPromise;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
this.pollValidatorIndicesPromise = this.pollValidatorIndicesInternal(pubkeysHex).finally(() => {
|
|
87
|
-
// Once the pollValidatorIndicesInternal() resolves or rejects null the cached promise so it can be called again.
|
|
88
|
-
this.pollValidatorIndicesPromise = null;
|
|
89
|
-
});
|
|
90
|
-
return this.pollValidatorIndicesPromise;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
removeForKey(pubkey: PubkeyHex): boolean {
|
|
94
|
-
for (const [index, value] of this.index2pubkey) {
|
|
95
|
-
if (value === pubkey) {
|
|
96
|
-
this.index2pubkey.delete(index);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return this.pubkey2index.delete(pubkey);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/** Iterate through all the voting pubkeys in the `ValidatorStore` and attempt to learn any unknown
|
|
103
|
-
validator indices. Returns the new discovered indexes */
|
|
104
|
-
private async pollValidatorIndicesInternal(pubkeysHex: PubkeyHex[]): Promise<ValidatorIndex[]> {
|
|
105
|
-
const pubkeysHexToDiscover = pubkeysHex.filter((pubkey) => !this.pubkey2index.has(pubkey));
|
|
106
|
-
|
|
107
|
-
if (pubkeysHexToDiscover.length === 0) {
|
|
108
|
-
return [];
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Query the remote BN to resolve a pubkey to a validator index.
|
|
112
|
-
// support up to 10k pubkeys per poll
|
|
113
|
-
const pubkeysHexBatches = batchItems(pubkeysHexToDiscover, {batchSize: PUBKEYS_PER_REQUEST});
|
|
114
|
-
|
|
115
|
-
const newIndices: number[] = [];
|
|
116
|
-
for (const pubkeysHexBatch of pubkeysHexBatches) {
|
|
117
|
-
const validatorIndicesArr = await this.fetchValidatorIndices(pubkeysHexBatch);
|
|
118
|
-
newIndices.push(...validatorIndicesArr);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
this.metrics?.discoveredIndices.inc(newIndices.length);
|
|
122
|
-
|
|
123
|
-
return newIndices;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
private async fetchValidatorIndices(pubkeysHex: string[]): Promise<ValidatorIndex[]> {
|
|
127
|
-
const validators = (await this.api.beacon.postStateValidators({stateId: "head", validatorIds: pubkeysHex})).value();
|
|
128
|
-
|
|
129
|
-
const newIndices = [];
|
|
130
|
-
|
|
131
|
-
const allValidatorStatuses = new MapDef<SimpleValidatorStatus, number>(() => 0);
|
|
132
|
-
|
|
133
|
-
for (const validator of validators) {
|
|
134
|
-
// Group all validators by status
|
|
135
|
-
const status = statusToSimpleStatusMapping(validator.status);
|
|
136
|
-
allValidatorStatuses.set(status, allValidatorStatuses.getOrDefault(status) + 1);
|
|
137
|
-
|
|
138
|
-
const pubkeyHex = toPubkeyHex(validator.validator.pubkey);
|
|
139
|
-
if (!this.pubkey2index.has(pubkeyHex)) {
|
|
140
|
-
this.logger.info("Validator seen on beacon chain", {
|
|
141
|
-
validatorIndex: validator.index,
|
|
142
|
-
pubKey: pubkeyHex,
|
|
143
|
-
});
|
|
144
|
-
this.pubkey2index.set(pubkeyHex, validator.index);
|
|
145
|
-
this.index2pubkey.set(validator.index, pubkeyHex);
|
|
146
|
-
newIndices.push(validator.index);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// The number of validators that are not in the beacon chain
|
|
151
|
-
const pendingCount = pubkeysHex.length - validators.length;
|
|
152
|
-
|
|
153
|
-
allValidatorStatuses.set("pending", allValidatorStatuses.getOrDefault("pending") + pendingCount);
|
|
154
|
-
|
|
155
|
-
// Retrieve the number of validators for each status
|
|
156
|
-
const statuses = Object.fromEntries(Array.from(allValidatorStatuses.entries()).filter((entry) => entry[1] > 0));
|
|
157
|
-
|
|
158
|
-
// The total number of validators
|
|
159
|
-
const total = pubkeysHex.length;
|
|
160
|
-
|
|
161
|
-
this.logger.info("Validator statuses", {...statuses, total});
|
|
162
|
-
|
|
163
|
-
return newIndices;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import {ApiClient, routes} from "@lodestar/api";
|
|
2
|
-
import {BeaconConfig} from "@lodestar/config";
|
|
3
|
-
import {GENESIS_EPOCH, SLOTS_PER_EPOCH} from "@lodestar/params";
|
|
4
|
-
import {Epoch, bellatrix} from "@lodestar/types";
|
|
5
|
-
import {Metrics} from "../metrics.js";
|
|
6
|
-
import {IClock, LoggerVc, batchItems} from "../util/index.js";
|
|
7
|
-
import {ValidatorStore} from "./validatorStore.js";
|
|
8
|
-
|
|
9
|
-
const REGISTRATION_CHUNK_SIZE = 512;
|
|
10
|
-
/**
|
|
11
|
-
* This service is responsible for registering validators to beacon node with the
|
|
12
|
-
* proposer data (currently `feeRecipient`) so that it can issue advance fcUs to
|
|
13
|
-
* the engine for building execution payload with transactions.
|
|
14
|
-
*
|
|
15
|
-
* This needs to be done every epoch because the BN will cache it at most for
|
|
16
|
-
* two epochs.
|
|
17
|
-
*/
|
|
18
|
-
export function pollPrepareBeaconProposer(
|
|
19
|
-
config: BeaconConfig,
|
|
20
|
-
logger: LoggerVc,
|
|
21
|
-
api: ApiClient,
|
|
22
|
-
clock: IClock,
|
|
23
|
-
validatorStore: ValidatorStore,
|
|
24
|
-
_metrics: Metrics | null
|
|
25
|
-
): void {
|
|
26
|
-
async function prepareBeaconProposer(epoch: Epoch): Promise<void> {
|
|
27
|
-
// Before bellatrix we don't need to update this data on bn/builder
|
|
28
|
-
if (epoch < config.BELLATRIX_FORK_EPOCH - 1) return;
|
|
29
|
-
|
|
30
|
-
// prepareBeaconProposer is not as time sensitive as attesting.
|
|
31
|
-
// Poll indices first, then call api.validator.prepareBeaconProposer once
|
|
32
|
-
await validatorStore.pollValidatorIndices().catch((e: Error) => {
|
|
33
|
-
logger.error("Error on pollValidatorIndices for prepareBeaconProposer", {epoch}, e);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
const indicesChunks = batchItems(validatorStore.getAllLocalIndices(), {batchSize: REGISTRATION_CHUNK_SIZE});
|
|
37
|
-
|
|
38
|
-
for (const indices of indicesChunks) {
|
|
39
|
-
try {
|
|
40
|
-
const proposers = indices.map(
|
|
41
|
-
(index): routes.validator.ProposerPreparationData => ({
|
|
42
|
-
validatorIndex: index,
|
|
43
|
-
feeRecipient: validatorStore.getFeeRecipientByIndex(index),
|
|
44
|
-
})
|
|
45
|
-
);
|
|
46
|
-
(await api.validator.prepareBeaconProposer({proposers})).assertOk();
|
|
47
|
-
logger.debug("Registered proposers with beacon node", {epoch, count: proposers.length});
|
|
48
|
-
} catch (e) {
|
|
49
|
-
logger.error("Failed to register proposers with beacon node", {epoch}, e as Error);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
clock.runEveryEpoch(prepareBeaconProposer);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* This service is responsible for registering validators with the mev builder as they
|
|
59
|
-
* might prepare and keep ready the execution payloads of just registered validators.
|
|
60
|
-
*
|
|
61
|
-
* This needs to be done every epoch because the builder(s) will cache it at most for
|
|
62
|
-
* two epochs.
|
|
63
|
-
*/
|
|
64
|
-
export function pollBuilderValidatorRegistration(
|
|
65
|
-
config: BeaconConfig,
|
|
66
|
-
logger: LoggerVc,
|
|
67
|
-
api: ApiClient,
|
|
68
|
-
clock: IClock,
|
|
69
|
-
validatorStore: ValidatorStore,
|
|
70
|
-
_metrics: Metrics | null
|
|
71
|
-
): void {
|
|
72
|
-
async function registerValidator(epoch: Epoch): Promise<void> {
|
|
73
|
-
// Don't send validator registrations pre-genesis as mev-boost-relay will reject
|
|
74
|
-
// those registrations anyways if timestamp is before genesis time and we wanna
|
|
75
|
-
// avoid caching and re-sending them in subsequent requests
|
|
76
|
-
if (epoch < GENESIS_EPOCH) return;
|
|
77
|
-
|
|
78
|
-
// Before bellatrix we don't need to update this data on bn/builder
|
|
79
|
-
if (epoch < config.BELLATRIX_FORK_EPOCH - 1) return;
|
|
80
|
-
const slot = epoch * SLOTS_PER_EPOCH;
|
|
81
|
-
|
|
82
|
-
// registerValidator is not as time sensitive as attesting.
|
|
83
|
-
// Poll indices first, then call api.validator.registerValidator once
|
|
84
|
-
await validatorStore.pollValidatorIndices().catch((e: Error) => {
|
|
85
|
-
logger.error("Error on pollValidatorIndices for registerValidator", {epoch}, e);
|
|
86
|
-
});
|
|
87
|
-
const pubkeyHexes = validatorStore
|
|
88
|
-
.getAllLocalIndices()
|
|
89
|
-
.map((index) => validatorStore.getPubkeyOfIndex(index))
|
|
90
|
-
.filter(
|
|
91
|
-
(pubkeyHex): pubkeyHex is string =>
|
|
92
|
-
pubkeyHex !== undefined &&
|
|
93
|
-
validatorStore.getBuilderSelectionParams(pubkeyHex).selection !==
|
|
94
|
-
routes.validator.BuilderSelection.ExecutionOnly
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
if (pubkeyHexes.length > 0) {
|
|
98
|
-
const pubkeyHexesChunks = batchItems(pubkeyHexes, {batchSize: REGISTRATION_CHUNK_SIZE});
|
|
99
|
-
|
|
100
|
-
for (const pubkeyHexes of pubkeyHexesChunks) {
|
|
101
|
-
try {
|
|
102
|
-
const registrations = await Promise.all(
|
|
103
|
-
pubkeyHexes.map((pubkeyHex): Promise<bellatrix.SignedValidatorRegistrationV1> => {
|
|
104
|
-
const feeRecipient = validatorStore.getFeeRecipient(pubkeyHex);
|
|
105
|
-
const gasLimit = validatorStore.getGasLimit(pubkeyHex);
|
|
106
|
-
return validatorStore.getValidatorRegistration(pubkeyHex, {feeRecipient, gasLimit}, slot);
|
|
107
|
-
})
|
|
108
|
-
);
|
|
109
|
-
(await api.validator.registerValidator({registrations})).assertOk();
|
|
110
|
-
logger.info("Published validator registrations to builder", {epoch, count: registrations.length});
|
|
111
|
-
} catch (e) {
|
|
112
|
-
logger.error("Failed to publish validator registrations to builder", {epoch}, e as Error);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
clock.runEveryEpoch(registerValidator);
|
|
119
|
-
}
|
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
import {ApiClient, routes} from "@lodestar/api";
|
|
2
|
-
import {ChainForkConfig} from "@lodestar/config";
|
|
3
|
-
import {computeEpochAtSlot, isSyncCommitteeAggregator} from "@lodestar/state-transition";
|
|
4
|
-
import {BLSSignature, CommitteeIndex, Root, Slot, altair} from "@lodestar/types";
|
|
5
|
-
import {sleep} from "@lodestar/utils";
|
|
6
|
-
import {Metrics} from "../metrics.js";
|
|
7
|
-
import {PubkeyHex} from "../types.js";
|
|
8
|
-
import {IClock, LoggerVc} from "../util/index.js";
|
|
9
|
-
import {ChainHeaderTracker} from "./chainHeaderTracker.js";
|
|
10
|
-
import {ValidatorEventEmitter} from "./emitter.js";
|
|
11
|
-
import {SyncCommitteeDutiesService, SyncDutyAndProofs} from "./syncCommitteeDuties.js";
|
|
12
|
-
import {SyncingStatusTracker} from "./syncingStatusTracker.js";
|
|
13
|
-
import {SubcommitteeDuty, groupSyncDutiesBySubcommitteeIndex} from "./utils.js";
|
|
14
|
-
import {ValidatorStore} from "./validatorStore.js";
|
|
15
|
-
|
|
16
|
-
export type SyncCommitteeServiceOpts = {
|
|
17
|
-
scAfterBlockDelaySlotFraction?: number;
|
|
18
|
-
distributedAggregationSelection?: boolean;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Service that sets up and handles validator sync duties.
|
|
23
|
-
*/
|
|
24
|
-
export class SyncCommitteeService {
|
|
25
|
-
private readonly dutiesService: SyncCommitteeDutiesService;
|
|
26
|
-
|
|
27
|
-
constructor(
|
|
28
|
-
private readonly config: ChainForkConfig,
|
|
29
|
-
private readonly logger: LoggerVc,
|
|
30
|
-
private readonly api: ApiClient,
|
|
31
|
-
private readonly clock: IClock,
|
|
32
|
-
private readonly validatorStore: ValidatorStore,
|
|
33
|
-
private readonly emitter: ValidatorEventEmitter,
|
|
34
|
-
private readonly chainHeaderTracker: ChainHeaderTracker,
|
|
35
|
-
readonly syncingStatusTracker: SyncingStatusTracker,
|
|
36
|
-
private readonly metrics: Metrics | null,
|
|
37
|
-
private readonly opts?: SyncCommitteeServiceOpts
|
|
38
|
-
) {
|
|
39
|
-
this.dutiesService = new SyncCommitteeDutiesService(
|
|
40
|
-
config,
|
|
41
|
-
logger,
|
|
42
|
-
api,
|
|
43
|
-
clock,
|
|
44
|
-
validatorStore,
|
|
45
|
-
syncingStatusTracker,
|
|
46
|
-
metrics,
|
|
47
|
-
{
|
|
48
|
-
distributedAggregationSelection: opts?.distributedAggregationSelection,
|
|
49
|
-
}
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
// At most every slot, check existing duties from SyncCommitteeDutiesService and run tasks
|
|
53
|
-
clock.runEverySlot(this.runSyncCommitteeTasks);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
removeDutiesForKey(pubkey: PubkeyHex): void {
|
|
57
|
-
this.dutiesService.removeDutiesForKey(pubkey);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
private runSyncCommitteeTasks = async (slot: Slot, signal: AbortSignal): Promise<void> => {
|
|
61
|
-
try {
|
|
62
|
-
// Before altair fork no need to check duties
|
|
63
|
-
if (computeEpochAtSlot(slot) < this.config.ALTAIR_FORK_EPOCH) {
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Fetch info first so a potential delay is absorbed by the sleep() below
|
|
68
|
-
const dutiesAtSlot = await this.dutiesService.getDutiesAtSlot(slot);
|
|
69
|
-
if (dutiesAtSlot.length === 0) {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (this.opts?.distributedAggregationSelection) {
|
|
74
|
-
// Validator in distributed cluster only has a key share, not the full private key.
|
|
75
|
-
// The partial selection proofs must be exchanged for combined selection proofs by
|
|
76
|
-
// calling submitSyncCommitteeSelections on the distributed validator middleware client.
|
|
77
|
-
// This will run in parallel to other sync committee tasks but must be finished before starting
|
|
78
|
-
// sync committee contributions as it is required to correctly determine if validator is aggregator
|
|
79
|
-
// and to produce a ContributionAndProof that can be threshold aggregated by the middleware client.
|
|
80
|
-
this.runDistributedAggregationSelectionTasks(dutiesAtSlot, slot, signal).catch((e) =>
|
|
81
|
-
this.logger.error("Error on sync committee aggregation selection", {slot}, e)
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// unlike Attestation, SyncCommitteeSignature could be published asap
|
|
86
|
-
// especially with lodestar, it's very busy at 1/3 of slot
|
|
87
|
-
// see https://github.com/ChainSafe/lodestar/issues/4608
|
|
88
|
-
await Promise.race([sleep(this.clock.msToSlot(slot + 1 / 3), signal), this.emitter.waitForBlockSlot(slot)]);
|
|
89
|
-
this.metrics?.syncCommitteeStepCallProduceMessage.observe(this.clock.secFromSlot(slot + 1 / 3));
|
|
90
|
-
|
|
91
|
-
// Step 1. Download, sign and publish an `SyncCommitteeMessage` for each validator.
|
|
92
|
-
// Differs from AttestationService, `SyncCommitteeMessage` are equal for all
|
|
93
|
-
const beaconBlockRoot = await this.produceAndPublishSyncCommittees(slot, dutiesAtSlot);
|
|
94
|
-
|
|
95
|
-
// Step 2. If an attestation was produced, make an aggregate.
|
|
96
|
-
// First, wait until the `aggregation_production_instant` (2/3rds of the way though the slot)
|
|
97
|
-
await sleep(this.clock.msToSlot(slot + 2 / 3), signal);
|
|
98
|
-
this.metrics?.syncCommitteeStepCallProduceAggregate.observe(this.clock.secFromSlot(slot + 2 / 3));
|
|
99
|
-
|
|
100
|
-
// await for all so if the Beacon node is overloaded it auto-throttles
|
|
101
|
-
// TODO: This approach is conservative to reduce the node's load, review
|
|
102
|
-
const dutiesBySubcommitteeIndex = groupSyncDutiesBySubcommitteeIndex(dutiesAtSlot);
|
|
103
|
-
await Promise.all(
|
|
104
|
-
Array.from(dutiesBySubcommitteeIndex.entries()).map(async ([subcommitteeIndex, duties]) => {
|
|
105
|
-
if (duties.length === 0) return;
|
|
106
|
-
// Then download, sign and publish a `SignedAggregateAndProof` for each
|
|
107
|
-
// validator that is elected to aggregate for this `slot` and `subcommitteeIndex`.
|
|
108
|
-
await this.produceAndPublishAggregates(slot, subcommitteeIndex, beaconBlockRoot, duties).catch((e: Error) => {
|
|
109
|
-
this.logger.error("Error on SyncCommitteeContribution", {slot, index: subcommitteeIndex}, e);
|
|
110
|
-
});
|
|
111
|
-
})
|
|
112
|
-
);
|
|
113
|
-
} catch (e) {
|
|
114
|
-
this.logger.error("Error on runSyncCommitteeTasks", {slot}, e as Error);
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Performs the first step of the attesting process: downloading `SyncCommittee` objects,
|
|
120
|
-
* signing them and returning them to the validator.
|
|
121
|
-
*
|
|
122
|
-
* https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/altair/validator.md#sync-committee-messages
|
|
123
|
-
*
|
|
124
|
-
* Only one `SyncCommittee` is downloaded from the BN. It is then signed by each
|
|
125
|
-
* validator and the list of individually-signed `SyncCommittee` objects is returned to the BN.
|
|
126
|
-
*/
|
|
127
|
-
private async produceAndPublishSyncCommittees(slot: Slot, duties: SyncDutyAndProofs[]): Promise<Root> {
|
|
128
|
-
const logCtx = {slot};
|
|
129
|
-
|
|
130
|
-
// /eth/v1/beacon/blocks/:blockId/root -> at slot -1
|
|
131
|
-
|
|
132
|
-
// Produce one attestation data per slot and subcommitteeIndex
|
|
133
|
-
// Spec: the validator should prepare a SyncCommitteeMessage for the previous slot (slot - 1)
|
|
134
|
-
// as soon as they have determined the head block of slot - 1
|
|
135
|
-
|
|
136
|
-
const blockRoot: Uint8Array =
|
|
137
|
-
this.chainHeaderTracker.getCurrentChainHead(slot) ??
|
|
138
|
-
(await this.api.beacon.getBlockRoot({blockId: "head"})).value().root;
|
|
139
|
-
|
|
140
|
-
const signatures: altair.SyncCommitteeMessage[] = [];
|
|
141
|
-
|
|
142
|
-
await Promise.all(
|
|
143
|
-
duties.map(async ({duty}) => {
|
|
144
|
-
const logCtxValidator = {...logCtx, validatorIndex: duty.validatorIndex};
|
|
145
|
-
try {
|
|
146
|
-
signatures.push(
|
|
147
|
-
await this.validatorStore.signSyncCommitteeSignature(duty.pubkey, duty.validatorIndex, slot, blockRoot)
|
|
148
|
-
);
|
|
149
|
-
this.logger.debug("Signed SyncCommitteeMessage", logCtxValidator);
|
|
150
|
-
} catch (e) {
|
|
151
|
-
this.logger.error("Error signing SyncCommitteeMessage", logCtxValidator, e as Error);
|
|
152
|
-
}
|
|
153
|
-
})
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
// by default we want to submit SyncCommitteeSignature asap after we receive block
|
|
157
|
-
// provide a delay option just in case any client implementation validate the existence of block in
|
|
158
|
-
// SyncCommitteeSignature gossip validation.
|
|
159
|
-
const msToOneThirdSlot = this.clock.msToSlot(slot + 1 / 3);
|
|
160
|
-
const afterBlockDelayMs = 1000 * this.clock.secondsPerSlot * (this.opts?.scAfterBlockDelaySlotFraction ?? 0);
|
|
161
|
-
const toDelayMs = Math.min(msToOneThirdSlot, afterBlockDelayMs);
|
|
162
|
-
if (toDelayMs > 0) {
|
|
163
|
-
await sleep(toDelayMs);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
this.metrics?.syncCommitteeStepCallPublishMessage.observe(this.clock.secFromSlot(slot + 1 / 3));
|
|
167
|
-
|
|
168
|
-
if (signatures.length > 0) {
|
|
169
|
-
try {
|
|
170
|
-
(await this.api.beacon.submitPoolSyncCommitteeSignatures({signatures})).assertOk();
|
|
171
|
-
this.logger.info("Published SyncCommitteeMessage", {...logCtx, count: signatures.length});
|
|
172
|
-
this.metrics?.publishedSyncCommitteeMessage.inc(signatures.length);
|
|
173
|
-
} catch (e) {
|
|
174
|
-
this.logger.error("Error publishing SyncCommitteeMessage", logCtx, e as Error);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return blockRoot;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Performs the second step of the attesting process: downloading an aggregated `SyncCommittee`,
|
|
183
|
-
* converting it into a `SignedAggregateAndProof` and returning it to the BN.
|
|
184
|
-
*
|
|
185
|
-
* https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/altair/validator.md#sync-committee-contributions
|
|
186
|
-
*
|
|
187
|
-
* Only one aggregated `SyncCommittee` is downloaded from the BN. It is then signed
|
|
188
|
-
* by each validator and the list of individually-signed `SignedAggregateAndProof` objects is
|
|
189
|
-
* returned to the BN.
|
|
190
|
-
*/
|
|
191
|
-
private async produceAndPublishAggregates(
|
|
192
|
-
slot: Slot,
|
|
193
|
-
subcommitteeIndex: CommitteeIndex,
|
|
194
|
-
beaconBlockRoot: Root,
|
|
195
|
-
duties: SubcommitteeDuty[]
|
|
196
|
-
): Promise<void> {
|
|
197
|
-
const logCtx = {slot, index: subcommitteeIndex};
|
|
198
|
-
|
|
199
|
-
// No validator is aggregator, skip
|
|
200
|
-
if (duties.every(({selectionProof}) => selectionProof === null)) {
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
this.logger.verbose("Producing SyncCommitteeContribution", logCtx);
|
|
205
|
-
const res = await this.api.validator.produceSyncCommitteeContribution({slot, subcommitteeIndex, beaconBlockRoot});
|
|
206
|
-
|
|
207
|
-
const signedContributions: altair.SignedContributionAndProof[] = [];
|
|
208
|
-
|
|
209
|
-
await Promise.all(
|
|
210
|
-
duties.map(async ({duty, selectionProof}) => {
|
|
211
|
-
const logCtxValidator = {...logCtx, validatorIndex: duty.validatorIndex};
|
|
212
|
-
try {
|
|
213
|
-
// Produce signed contributions only for validators that are subscribed aggregators.
|
|
214
|
-
if (selectionProof !== null) {
|
|
215
|
-
signedContributions.push(
|
|
216
|
-
await this.validatorStore.signContributionAndProof(duty, selectionProof, res.value())
|
|
217
|
-
);
|
|
218
|
-
this.logger.debug("Signed SyncCommitteeContribution", logCtxValidator);
|
|
219
|
-
}
|
|
220
|
-
} catch (e) {
|
|
221
|
-
this.logger.error("Error signing SyncCommitteeContribution", logCtxValidator, e as Error);
|
|
222
|
-
}
|
|
223
|
-
})
|
|
224
|
-
);
|
|
225
|
-
|
|
226
|
-
this.metrics?.syncCommitteeStepCallPublishAggregate.observe(this.clock.secFromSlot(slot + 2 / 3));
|
|
227
|
-
|
|
228
|
-
if (signedContributions.length > 0) {
|
|
229
|
-
try {
|
|
230
|
-
(
|
|
231
|
-
await this.api.validator.publishContributionAndProofs({contributionAndProofs: signedContributions})
|
|
232
|
-
).assertOk();
|
|
233
|
-
this.logger.info("Published SyncCommitteeContribution", {...logCtx, count: signedContributions.length});
|
|
234
|
-
this.metrics?.publishedSyncCommitteeContribution.inc(signedContributions.length);
|
|
235
|
-
} catch (e) {
|
|
236
|
-
this.logger.error("Error publishing SyncCommitteeContribution", logCtx, e as Error);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Performs additional sync committee contribution tasks required if validator is part of distributed cluster
|
|
243
|
-
*
|
|
244
|
-
* 1. Exchange partial for combined selection proofs
|
|
245
|
-
* 2. Determine validators that should produce sync committee contribution
|
|
246
|
-
* 3. Mutate duty objects to set selection proofs for aggregators
|
|
247
|
-
*
|
|
248
|
-
* See https://docs.google.com/document/d/1q9jOTPcYQa-3L8luRvQJ-M0eegtba4Nmon3dpO79TMk/mobilebasic
|
|
249
|
-
*/
|
|
250
|
-
private async runDistributedAggregationSelectionTasks(
|
|
251
|
-
duties: SyncDutyAndProofs[],
|
|
252
|
-
slot: number,
|
|
253
|
-
signal: AbortSignal
|
|
254
|
-
): Promise<void> {
|
|
255
|
-
const partialSelections: routes.validator.SyncCommitteeSelection[] = [];
|
|
256
|
-
|
|
257
|
-
for (const {duty, selectionProofs} of duties) {
|
|
258
|
-
const validatorSelections: routes.validator.SyncCommitteeSelection[] = selectionProofs.map(
|
|
259
|
-
({subcommitteeIndex, partialSelectionProof}) => ({
|
|
260
|
-
validatorIndex: duty.validatorIndex,
|
|
261
|
-
slot,
|
|
262
|
-
subcommitteeIndex,
|
|
263
|
-
selectionProof: partialSelectionProof as BLSSignature,
|
|
264
|
-
})
|
|
265
|
-
);
|
|
266
|
-
partialSelections.push(...validatorSelections);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
this.logger.debug("Submitting partial sync committee selection proofs", {slot, count: partialSelections.length});
|
|
270
|
-
|
|
271
|
-
const res = await Promise.race([
|
|
272
|
-
this.api.validator.submitSyncCommitteeSelections({selections: partialSelections}),
|
|
273
|
-
// Exit sync committee contributions flow if there is no response after 2/3 of slot.
|
|
274
|
-
// This is in contrast to attestations aggregations flow which is already exited at 1/3 of the slot
|
|
275
|
-
// because for sync committee is not required to resubscribe to subnets as beacon node will assume
|
|
276
|
-
// validator always aggregates. This allows us to wait until we have to produce sync committee contributions.
|
|
277
|
-
// Note that the sync committee contributions flow is not explicitly exited but rather will be skipped
|
|
278
|
-
// due to the fact that calculation of `is_sync_committee_aggregator` in SyncCommitteeDutiesService is not done
|
|
279
|
-
// and selectionProof is set to null, meaning no validator will be considered an aggregator.
|
|
280
|
-
sleep(this.clock.msToSlot(slot + 2 / 3), signal),
|
|
281
|
-
]);
|
|
282
|
-
|
|
283
|
-
if (!res) {
|
|
284
|
-
throw new Error("Failed to receive combined selection proofs before 2/3 of slot");
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const combinedSelections = res.value();
|
|
288
|
-
this.logger.debug("Received combined sync committee selection proofs", {slot, count: combinedSelections.length});
|
|
289
|
-
|
|
290
|
-
for (const dutyAndProofs of duties) {
|
|
291
|
-
const {validatorIndex, subnets} = dutyAndProofs.duty;
|
|
292
|
-
|
|
293
|
-
for (const subnet of subnets) {
|
|
294
|
-
const logCtxValidator = {slot, index: subnet, validatorIndex};
|
|
295
|
-
|
|
296
|
-
const combinedSelection = combinedSelections.find(
|
|
297
|
-
(s) => s.validatorIndex === validatorIndex && s.slot === slot && s.subcommitteeIndex === subnet
|
|
298
|
-
);
|
|
299
|
-
|
|
300
|
-
if (!combinedSelection) {
|
|
301
|
-
this.logger.warn("Did not receive combined sync committee selection proof", logCtxValidator);
|
|
302
|
-
continue;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const isAggregator = isSyncCommitteeAggregator(combinedSelection.selectionProof);
|
|
306
|
-
|
|
307
|
-
if (isAggregator) {
|
|
308
|
-
const selectionProofObject = dutyAndProofs.selectionProofs.find((p) => p.subcommitteeIndex === subnet);
|
|
309
|
-
if (selectionProofObject) {
|
|
310
|
-
// Update selection proof by mutating proof objects in duty object
|
|
311
|
-
selectionProofObject.selectionProof = combinedSelection.selectionProof;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|