@lodestar/validator 1.35.0-dev.ba92bd8a88 → 1.35.0-dev.c0078a16b5
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/util/params.js +0 -9
- package/lib/util/params.js.map +1 -1
- package/package.json +16 -19
- 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,349 +0,0 @@
|
|
|
1
|
-
import {ApiClient, routes} from "@lodestar/api";
|
|
2
|
-
import {ChainForkConfig} from "@lodestar/config";
|
|
3
|
-
import {ForkSeq} from "@lodestar/params";
|
|
4
|
-
import {computeEpochAtSlot, isAggregatorFromCommitteeLength} from "@lodestar/state-transition";
|
|
5
|
-
import {BLSSignature, SignedAggregateAndProof, SingleAttestation, Slot, phase0, ssz} from "@lodestar/types";
|
|
6
|
-
import {prettyBytes, sleep, toRootHex} from "@lodestar/utils";
|
|
7
|
-
import {Metrics} from "../metrics.js";
|
|
8
|
-
import {PubkeyHex} from "../types.js";
|
|
9
|
-
import {IClock, LoggerVc} from "../util/index.js";
|
|
10
|
-
import {AttDutyAndProof, AttestationDutiesService} from "./attestationDuties.js";
|
|
11
|
-
import {ChainHeaderTracker} from "./chainHeaderTracker.js";
|
|
12
|
-
import {ValidatorEventEmitter} from "./emitter.js";
|
|
13
|
-
import {SyncingStatusTracker} from "./syncingStatusTracker.js";
|
|
14
|
-
import {groupAttDutiesByCommitteeIndex} from "./utils.js";
|
|
15
|
-
import {ValidatorStore} from "./validatorStore.js";
|
|
16
|
-
|
|
17
|
-
export type AttestationServiceOpts = {
|
|
18
|
-
afterBlockDelaySlotFraction?: number;
|
|
19
|
-
distributedAggregationSelection?: boolean;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Previously, submitting attestations too early may cause some attestations missed (because some clients may not queue attestations, and
|
|
24
|
-
* sent peers are few) so it was configured as 1/6. See https://github.com/ChainSafe/lodestar/issues/3943
|
|
25
|
-
*
|
|
26
|
-
* As of Nov 2022, it's proved that submitting attestations asap is better as it avoids busy time of node at around 1/3 of slot (and could be
|
|
27
|
-
* because sent peers are better than before). See https://github.com/ChainSafe/lodestar/issues/4600#issuecomment-1321546586
|
|
28
|
-
*/
|
|
29
|
-
const DEFAULT_AFTER_BLOCK_DELAY_SLOT_FRACTION = 0;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Service that sets up and handles validator attester duties.
|
|
33
|
-
*/
|
|
34
|
-
export class AttestationService {
|
|
35
|
-
private readonly dutiesService: AttestationDutiesService;
|
|
36
|
-
|
|
37
|
-
constructor(
|
|
38
|
-
private readonly logger: LoggerVc,
|
|
39
|
-
private readonly api: ApiClient,
|
|
40
|
-
private readonly clock: IClock,
|
|
41
|
-
private readonly validatorStore: ValidatorStore,
|
|
42
|
-
private readonly emitter: ValidatorEventEmitter,
|
|
43
|
-
chainHeadTracker: ChainHeaderTracker,
|
|
44
|
-
syncingStatusTracker: SyncingStatusTracker,
|
|
45
|
-
private readonly metrics: Metrics | null,
|
|
46
|
-
private readonly config: ChainForkConfig,
|
|
47
|
-
private readonly opts?: AttestationServiceOpts
|
|
48
|
-
) {
|
|
49
|
-
this.dutiesService = new AttestationDutiesService(
|
|
50
|
-
logger,
|
|
51
|
-
api,
|
|
52
|
-
clock,
|
|
53
|
-
validatorStore,
|
|
54
|
-
chainHeadTracker,
|
|
55
|
-
syncingStatusTracker,
|
|
56
|
-
metrics,
|
|
57
|
-
{
|
|
58
|
-
distributedAggregationSelection: opts?.distributedAggregationSelection,
|
|
59
|
-
}
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
// At most every slot, check existing duties from AttestationDutiesService and run tasks
|
|
63
|
-
clock.runEverySlot(this.runAttestationTasks);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
removeDutiesForKey(pubkey: PubkeyHex): void {
|
|
67
|
-
this.dutiesService.removeDutiesForKey(pubkey);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
private runAttestationTasks = async (slot: Slot, signal: AbortSignal): Promise<void> => {
|
|
71
|
-
// Fetch info first so a potential delay is absorbed by the sleep() below
|
|
72
|
-
const duties = this.dutiesService.getDutiesAtSlot(slot);
|
|
73
|
-
if (duties.length === 0) {
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (this.opts?.distributedAggregationSelection) {
|
|
78
|
-
// Validator in distributed cluster only has a key share, not the full private key.
|
|
79
|
-
// The partial selection proofs must be exchanged for combined selection proofs by
|
|
80
|
-
// calling submitBeaconCommitteeSelections on the distributed validator middleware client.
|
|
81
|
-
// This will run in parallel to other attestation tasks but must be finished before starting
|
|
82
|
-
// attestation aggregation as it is required to correctly determine if validator is aggregator
|
|
83
|
-
// and to produce a AggregateAndProof that can be threshold aggregated by the middleware client.
|
|
84
|
-
this.runDistributedAggregationSelectionTasks(duties, slot, signal).catch((e) =>
|
|
85
|
-
this.logger.error("Error on attestation aggregation selection", {slot}, e)
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// A validator should create and broadcast the attestation to the associated attestation subnet when either
|
|
90
|
-
// (a) the validator has received a valid block from the expected block proposer for the assigned slot or
|
|
91
|
-
// (b) one-third of the slot has transpired (SECONDS_PER_SLOT / 3 seconds after the start of slot) -- whichever comes first.
|
|
92
|
-
await Promise.race([sleep(this.clock.msToSlot(slot + 1 / 3), signal), this.emitter.waitForBlockSlot(slot)]);
|
|
93
|
-
this.metrics?.attesterStepCallProduceAttestation.observe(this.clock.secFromSlot(slot + 1 / 3));
|
|
94
|
-
|
|
95
|
-
// Beacon node's endpoint produceAttestationData return data is not dependent on committeeIndex.
|
|
96
|
-
// Produce a single attestation for all committees and submit unaggregated attestations in one go.
|
|
97
|
-
try {
|
|
98
|
-
// Produce a single attestation for all committees, and clone mutate before signing
|
|
99
|
-
const attestationNoCommittee = await this.produceAttestation(0, slot);
|
|
100
|
-
|
|
101
|
-
// Step 1. Mutate, and sign `Attestation` for each validator. Then publish all `Attestations` in one go
|
|
102
|
-
await this.signAndPublishAttestations(slot, attestationNoCommittee, duties);
|
|
103
|
-
|
|
104
|
-
// Step 2. after all attestations are submitted, make an aggregate.
|
|
105
|
-
// First, wait until the `aggregation_production_instant` (2/3rds of the way through the slot)
|
|
106
|
-
await sleep(this.clock.msToSlot(slot + 2 / 3), signal);
|
|
107
|
-
this.metrics?.attesterStepCallProduceAggregate.observe(this.clock.secFromSlot(slot + 2 / 3));
|
|
108
|
-
|
|
109
|
-
const dutiesByCommitteeIndex = groupAttDutiesByCommitteeIndex(duties);
|
|
110
|
-
const isPostElectra = this.config.getForkSeq(slot) >= ForkSeq.electra;
|
|
111
|
-
|
|
112
|
-
// Then download, sign and publish a `SignedAggregateAndProof` for each
|
|
113
|
-
// validator that is elected to aggregate for this `slot` and `committeeIndex`.
|
|
114
|
-
await Promise.all(
|
|
115
|
-
Array.from(dutiesByCommitteeIndex.entries()).map(([index, dutiesSameCommittee]) => {
|
|
116
|
-
const attestationData: phase0.AttestationData = {...attestationNoCommittee, index: isPostElectra ? 0 : index};
|
|
117
|
-
return this.produceAndPublishAggregates(attestationData, index, dutiesSameCommittee);
|
|
118
|
-
})
|
|
119
|
-
);
|
|
120
|
-
} catch (e) {
|
|
121
|
-
this.logger.error("Error on attestation routine", {slot}, e as Error);
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Performs the first step of the attesting process: downloading one `Attestation` object.
|
|
127
|
-
* Beacon node's endpoint produceAttestationData return data is not dependent on committeeIndex.
|
|
128
|
-
* For a validator client with many validators this allows to do a single call for all committees
|
|
129
|
-
* in a slot, saving resources in both the vc and beacon node
|
|
130
|
-
*/
|
|
131
|
-
private async produceAttestation(committeeIndex: number, slot: Slot): Promise<phase0.AttestationData> {
|
|
132
|
-
// Produce one attestation data per slot and committeeIndex
|
|
133
|
-
return (await this.api.validator.produceAttestationData({committeeIndex, slot})).value();
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Only one `Attestation` is downloaded from the BN. It is then signed by each
|
|
138
|
-
* validator and the list of individually-signed `Attestation` objects is returned to the BN.
|
|
139
|
-
*/
|
|
140
|
-
private async signAndPublishAttestations(
|
|
141
|
-
slot: Slot,
|
|
142
|
-
attestationNoCommittee: phase0.AttestationData,
|
|
143
|
-
duties: AttDutyAndProof[]
|
|
144
|
-
): Promise<void> {
|
|
145
|
-
const signedAttestations: SingleAttestation[] = [];
|
|
146
|
-
const headRootHex = toRootHex(attestationNoCommittee.beaconBlockRoot);
|
|
147
|
-
const currentEpoch = computeEpochAtSlot(slot);
|
|
148
|
-
const isPostElectra = this.config.getForkSeq(slot) >= ForkSeq.electra;
|
|
149
|
-
|
|
150
|
-
await Promise.all(
|
|
151
|
-
duties.map(async ({duty}) => {
|
|
152
|
-
const index = isPostElectra ? 0 : duty.committeeIndex;
|
|
153
|
-
const attestationData: phase0.AttestationData = {...attestationNoCommittee, index};
|
|
154
|
-
const logCtxValidator = {slot, index, head: headRootHex, validatorIndex: duty.validatorIndex};
|
|
155
|
-
|
|
156
|
-
try {
|
|
157
|
-
signedAttestations.push(await this.validatorStore.signAttestation(duty, attestationData, currentEpoch));
|
|
158
|
-
this.logger.debug("Signed attestation", logCtxValidator);
|
|
159
|
-
} catch (e) {
|
|
160
|
-
this.metrics?.attestaterError.inc({error: "sign"});
|
|
161
|
-
this.logger.error("Error signing attestation", logCtxValidator, e as Error);
|
|
162
|
-
}
|
|
163
|
-
})
|
|
164
|
-
);
|
|
165
|
-
|
|
166
|
-
// signAndPublishAttestations() may be called before the 1/3 cutoff time if the block was received early.
|
|
167
|
-
// If we produced the block or we got the block sooner than our peers, our attestations can be dropped because
|
|
168
|
-
// they reach our peers before the block. To prevent that, we wait 2 extra seconds AFTER block arrival, but
|
|
169
|
-
// never beyond the 1/3 cutoff time.
|
|
170
|
-
// https://github.com/status-im/nimbus-eth2/blob/7b64c1dce4392731a4a59ee3a36caef2e0a8357a/beacon_chain/validators/validator_duties.nim#L1123
|
|
171
|
-
const msToOneThirdSlot = this.clock.msToSlot(slot + 1 / 3);
|
|
172
|
-
// submitting attestations asap to avoid busy time at around 1/3 of slot
|
|
173
|
-
const afterBlockDelayMs =
|
|
174
|
-
1000 *
|
|
175
|
-
this.clock.secondsPerSlot *
|
|
176
|
-
(this.opts?.afterBlockDelaySlotFraction ?? DEFAULT_AFTER_BLOCK_DELAY_SLOT_FRACTION);
|
|
177
|
-
await sleep(Math.min(msToOneThirdSlot, afterBlockDelayMs));
|
|
178
|
-
|
|
179
|
-
this.metrics?.attesterStepCallPublishAttestation.observe(this.clock.secFromSlot(slot + 1 / 3));
|
|
180
|
-
|
|
181
|
-
// Step 2. Publish all `Attestations` in one go
|
|
182
|
-
try {
|
|
183
|
-
(await this.api.beacon.submitPoolAttestationsV2({signedAttestations})).assertOk();
|
|
184
|
-
this.logger.info("Published attestations", {
|
|
185
|
-
slot,
|
|
186
|
-
head: prettyBytes(headRootHex),
|
|
187
|
-
count: signedAttestations.length,
|
|
188
|
-
});
|
|
189
|
-
this.metrics?.publishedAttestations.inc(signedAttestations.length);
|
|
190
|
-
} catch (e) {
|
|
191
|
-
// Note: metric counts only 1 since we don't know how many signedAttestations are invalid
|
|
192
|
-
this.metrics?.attestaterError.inc({error: "publish"});
|
|
193
|
-
this.logger.error("Error publishing attestations", {slot}, e as Error);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Performs the second step of the attesting process: downloading an aggregated `Attestation`,
|
|
199
|
-
* converting it into a `SignedAggregateAndProof` and returning it to the BN.
|
|
200
|
-
*
|
|
201
|
-
* https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#broadcast-aggregate
|
|
202
|
-
*
|
|
203
|
-
* Only one aggregated `Attestation` is downloaded from the BN. It is then signed
|
|
204
|
-
* by each validator and the list of individually-signed `SignedAggregateAndProof` objects is
|
|
205
|
-
* returned to the BN.
|
|
206
|
-
*/
|
|
207
|
-
private async produceAndPublishAggregates(
|
|
208
|
-
attestation: phase0.AttestationData,
|
|
209
|
-
committeeIndex: number,
|
|
210
|
-
duties: AttDutyAndProof[]
|
|
211
|
-
): Promise<void> {
|
|
212
|
-
const logCtx = {slot: attestation.slot, index: committeeIndex};
|
|
213
|
-
|
|
214
|
-
// No validator is aggregator, skip
|
|
215
|
-
if (duties.every(({selectionProof}) => selectionProof === null)) {
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
this.logger.verbose("Aggregating attestations", logCtx);
|
|
220
|
-
const aggregate = (
|
|
221
|
-
await this.api.validator.getAggregatedAttestationV2({
|
|
222
|
-
attestationDataRoot: ssz.phase0.AttestationData.hashTreeRoot(attestation),
|
|
223
|
-
slot: attestation.slot,
|
|
224
|
-
committeeIndex,
|
|
225
|
-
})
|
|
226
|
-
).value();
|
|
227
|
-
const participants = aggregate.aggregationBits.getTrueBitIndexes().length;
|
|
228
|
-
this.metrics?.numParticipantsInAggregate.observe(participants);
|
|
229
|
-
|
|
230
|
-
const signedAggregateAndProofs: SignedAggregateAndProof[] = [];
|
|
231
|
-
|
|
232
|
-
await Promise.all(
|
|
233
|
-
duties.map(async ({duty, selectionProof}) => {
|
|
234
|
-
const logCtxValidator = {...logCtx, validatorIndex: duty.validatorIndex};
|
|
235
|
-
try {
|
|
236
|
-
// Produce signed aggregates only for validators that are subscribed aggregators.
|
|
237
|
-
if (selectionProof !== null) {
|
|
238
|
-
signedAggregateAndProofs.push(
|
|
239
|
-
await this.validatorStore.signAggregateAndProof(duty, selectionProof, aggregate)
|
|
240
|
-
);
|
|
241
|
-
this.logger.debug("Signed aggregateAndProofs", logCtxValidator);
|
|
242
|
-
}
|
|
243
|
-
} catch (e) {
|
|
244
|
-
this.logger.error("Error signing aggregateAndProofs", logCtxValidator, e as Error);
|
|
245
|
-
}
|
|
246
|
-
})
|
|
247
|
-
);
|
|
248
|
-
|
|
249
|
-
this.metrics?.attesterStepCallPublishAggregate.observe(this.clock.secFromSlot(attestation.slot + 2 / 3));
|
|
250
|
-
|
|
251
|
-
if (signedAggregateAndProofs.length > 0) {
|
|
252
|
-
try {
|
|
253
|
-
(await this.api.validator.publishAggregateAndProofsV2({signedAggregateAndProofs})).assertOk();
|
|
254
|
-
this.logger.info("Published aggregateAndProofs", {
|
|
255
|
-
...logCtx,
|
|
256
|
-
participants,
|
|
257
|
-
count: signedAggregateAndProofs.length,
|
|
258
|
-
});
|
|
259
|
-
this.metrics?.publishedAggregates.inc(signedAggregateAndProofs.length);
|
|
260
|
-
} catch (e) {
|
|
261
|
-
this.logger.error("Error publishing aggregateAndProofs", logCtx, e as Error);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Performs additional attestation aggregation tasks required if validator is part of distributed cluster
|
|
268
|
-
*
|
|
269
|
-
* 1. Exchange partial for combined selection proofs
|
|
270
|
-
* 2. Determine validators that should aggregate attestations
|
|
271
|
-
* 3. Mutate duty objects to set selection proofs for aggregators
|
|
272
|
-
* 4. Resubscribe validators as aggregators on beacon committee subnets
|
|
273
|
-
*
|
|
274
|
-
* See https://docs.google.com/document/d/1q9jOTPcYQa-3L8luRvQJ-M0eegtba4Nmon3dpO79TMk/mobilebasic
|
|
275
|
-
*/
|
|
276
|
-
private async runDistributedAggregationSelectionTasks(
|
|
277
|
-
duties: AttDutyAndProof[],
|
|
278
|
-
slot: number,
|
|
279
|
-
signal: AbortSignal
|
|
280
|
-
): Promise<void> {
|
|
281
|
-
const partialSelections: routes.validator.BeaconCommitteeSelection[] = duties.map(
|
|
282
|
-
({duty, partialSelectionProof}) => ({
|
|
283
|
-
validatorIndex: duty.validatorIndex,
|
|
284
|
-
slot,
|
|
285
|
-
selectionProof: partialSelectionProof as BLSSignature,
|
|
286
|
-
})
|
|
287
|
-
);
|
|
288
|
-
|
|
289
|
-
this.logger.debug("Submitting partial beacon committee selection proofs", {slot, count: partialSelections.length});
|
|
290
|
-
|
|
291
|
-
const res = await Promise.race([
|
|
292
|
-
this.api.validator.submitBeaconCommitteeSelections({selections: partialSelections}),
|
|
293
|
-
// Exit attestation aggregation flow if there is no response after 1/3 of slot as
|
|
294
|
-
// beacon node would likely not have enough time to prepare an aggregate attestation.
|
|
295
|
-
// Note that the aggregations flow is not explicitly exited but rather will be skipped
|
|
296
|
-
// due to the fact that calculation of `is_aggregator` in AttestationDutiesService is not done
|
|
297
|
-
// and selectionProof is set to null, meaning no validator will be considered an aggregator.
|
|
298
|
-
sleep(this.clock.msToSlot(slot + 1 / 3), signal),
|
|
299
|
-
]);
|
|
300
|
-
|
|
301
|
-
if (!res) {
|
|
302
|
-
throw new Error("Failed to receive combined selection proofs before 1/3 of slot");
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const combinedSelections = res.value();
|
|
306
|
-
this.logger.debug("Received combined beacon committee selection proofs", {slot, count: combinedSelections.length});
|
|
307
|
-
|
|
308
|
-
const beaconCommitteeSubscriptions: routes.validator.BeaconCommitteeSubscription[] = [];
|
|
309
|
-
|
|
310
|
-
for (const dutyAndProof of duties) {
|
|
311
|
-
const {validatorIndex, committeeIndex, committeeLength, committeesAtSlot} = dutyAndProof.duty;
|
|
312
|
-
const logCtxValidator = {slot, index: committeeIndex, validatorIndex};
|
|
313
|
-
|
|
314
|
-
const combinedSelection = combinedSelections.find((s) => s.validatorIndex === validatorIndex && s.slot === slot);
|
|
315
|
-
|
|
316
|
-
if (!combinedSelection) {
|
|
317
|
-
this.logger.warn("Did not receive combined beacon committee selection proof", logCtxValidator);
|
|
318
|
-
continue;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const isAggregator = isAggregatorFromCommitteeLength(committeeLength, combinedSelection.selectionProof);
|
|
322
|
-
|
|
323
|
-
if (isAggregator) {
|
|
324
|
-
// Update selection proof by mutating duty object
|
|
325
|
-
dutyAndProof.selectionProof = combinedSelection.selectionProof;
|
|
326
|
-
|
|
327
|
-
// Only push subnet subscriptions with `isAggregator=true` as all validators
|
|
328
|
-
// with duties for slot are already subscribed to subnets with `isAggregator=false`.
|
|
329
|
-
beaconCommitteeSubscriptions.push({
|
|
330
|
-
validatorIndex,
|
|
331
|
-
committeesAtSlot,
|
|
332
|
-
committeeIndex,
|
|
333
|
-
slot,
|
|
334
|
-
isAggregator,
|
|
335
|
-
});
|
|
336
|
-
this.logger.debug("Resubscribing validator as aggregator on beacon committee subnet", logCtxValidator);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// If there are any subscriptions with aggregators, push them out to the beacon node.
|
|
341
|
-
if (beaconCommitteeSubscriptions.length > 0) {
|
|
342
|
-
(await this.api.validator.prepareBeaconCommitteeSubnet({subscriptions: beaconCommitteeSubscriptions})).assertOk();
|
|
343
|
-
this.logger.debug("Resubscribed validators as aggregators on beacon committee subnets", {
|
|
344
|
-
slot,
|
|
345
|
-
count: beaconCommitteeSubscriptions.length,
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|