@lodestar/validator 1.35.0-dev.8ea34e52ba → 1.35.0-dev.91dadf81de
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 +7 -7
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +5 -5
- package/lib/index.js.map +1 -1
- package/lib/metrics.d.ts.map +1 -0
- package/lib/metrics.js +14 -14
- package/lib/metrics.js.map +1 -1
- package/lib/repositories/index.d.ts.map +1 -0
- package/lib/repositories/metaDataRepository.d.ts.map +1 -0
- package/lib/repositories/metaDataRepository.js.map +1 -1
- package/lib/services/attestation.d.ts.map +1 -0
- package/lib/services/attestation.js +35 -27
- package/lib/services/attestation.js.map +1 -1
- package/lib/services/attestationDuties.d.ts.map +1 -0
- package/lib/services/attestationDuties.js +1 -0
- package/lib/services/attestationDuties.js.map +1 -1
- package/lib/services/block.d.ts.map +1 -0
- package/lib/services/blockDuties.d.ts +2 -2
- package/lib/services/blockDuties.d.ts.map +1 -0
- package/lib/services/blockDuties.js +4 -3
- package/lib/services/blockDuties.js.map +1 -1
- package/lib/services/chainHeaderTracker.d.ts.map +1 -0
- package/lib/services/doppelgangerService.d.ts.map +1 -0
- package/lib/services/emitter.d.ts +1 -1
- package/lib/services/emitter.d.ts.map +1 -0
- package/lib/services/externalSignerSync.d.ts.map +1 -0
- package/lib/services/externalSignerSync.js.map +1 -1
- package/lib/services/indices.d.ts.map +1 -0
- package/lib/services/prepareBeaconProposer.d.ts.map +1 -0
- package/lib/services/prepareBeaconProposer.js.map +1 -1
- package/lib/services/syncCommittee.d.ts.map +1 -0
- package/lib/services/syncCommittee.js +30 -22
- package/lib/services/syncCommittee.js.map +1 -1
- 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 +1 -1
- package/lib/slashingProtection/index.d.ts.map +1 -0
- package/lib/slashingProtection/index.js.map +1 -1
- 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 +3 -0
- package/lib/util/clock.d.ts.map +1 -0
- package/lib/util/clock.js +4 -0
- package/lib/util/clock.js.map +1 -1
- 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/params.js +17 -1
- package/lib/util/params.js.map +1 -1
- package/lib/util/url.d.ts.map +1 -0
- package/lib/validator.d.ts.map +1 -0
- package/package.json +19 -16
- 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 +362 -0
- package/src/services/attestationDuties.ts +406 -0
- package/src/services/block.ts +261 -0
- package/src/services/blockDuties.ts +217 -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 +338 -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 +170 -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 +320 -0
- package/src/util/url.ts +16 -0
- package/src/validator.ts +418 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import {ApiClient, routes} from "@lodestar/api";
|
|
2
|
+
import {SLOTS_PER_EPOCH} from "@lodestar/params";
|
|
3
|
+
import {computeEpochAtSlot, isAggregatorFromCommitteeLength, isStartSlotOfEpoch} from "@lodestar/state-transition";
|
|
4
|
+
import {BLSSignature, Epoch, RootHex, Slot, ValidatorIndex} from "@lodestar/types";
|
|
5
|
+
import {sleep, toPubkeyHex} from "@lodestar/utils";
|
|
6
|
+
import {Metrics} from "../metrics.js";
|
|
7
|
+
import {PubkeyHex} from "../types.js";
|
|
8
|
+
import {IClock, LoggerVc, batchItems} from "../util/index.js";
|
|
9
|
+
import {ChainHeaderTracker, HeadEventData} from "./chainHeaderTracker.js";
|
|
10
|
+
import {SyncingStatusTracker} from "./syncingStatusTracker.js";
|
|
11
|
+
import {ValidatorStore} from "./validatorStore.js";
|
|
12
|
+
|
|
13
|
+
/** Only retain `HISTORICAL_DUTIES_EPOCHS` duties prior to the current epoch. */
|
|
14
|
+
const HISTORICAL_DUTIES_EPOCHS = 2;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* This is to prevent the "Request body is too large" issue for http post.
|
|
18
|
+
* Typical server accept up to1MB (2 ** 20 bytes) of request body, for example fastify and nginx.
|
|
19
|
+
* A typical subscription request is 107 bytes in length, make it 120 to buffer.
|
|
20
|
+
* This number is Math.floor(2 ** 20 / 120)
|
|
21
|
+
**/
|
|
22
|
+
const SUBSCRIPTIONS_PER_REQUEST = 8738;
|
|
23
|
+
|
|
24
|
+
/** Neatly joins the server-generated `AttesterData` with the locally-generated `selectionProof`. */
|
|
25
|
+
export type AttDutyAndProof = {
|
|
26
|
+
duty: routes.validator.AttesterDuty;
|
|
27
|
+
/** This value is only set to not null if the proof indicates that the validator is an aggregator. */
|
|
28
|
+
selectionProof: BLSSignature | null;
|
|
29
|
+
/** This value will only be set if validator is part of distributed cluster and only has a key share */
|
|
30
|
+
partialSelectionProof?: BLSSignature;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// To assist with readability
|
|
34
|
+
type AttDutiesAtEpoch = {dependentRoot: RootHex; dutiesByIndex: Map<ValidatorIndex, AttDutyAndProof>};
|
|
35
|
+
|
|
36
|
+
type AttestationDutiesServiceOpts = {
|
|
37
|
+
distributedAggregationSelection?: boolean;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export class AttestationDutiesService {
|
|
41
|
+
/** Maps a validator public key to their duties for each epoch */
|
|
42
|
+
private readonly dutiesByIndexByEpoch = new Map<Epoch, AttDutiesAtEpoch>();
|
|
43
|
+
/**
|
|
44
|
+
* We may receive new dependentRoot of an epoch but it's not the last slot of epoch
|
|
45
|
+
* so we have to wait for getting close to the next epoch to redownload new attesterDuties.
|
|
46
|
+
*/
|
|
47
|
+
private readonly pendingDependentRootByEpoch = new Map<Epoch, RootHex>();
|
|
48
|
+
|
|
49
|
+
constructor(
|
|
50
|
+
private readonly logger: LoggerVc,
|
|
51
|
+
private readonly api: ApiClient,
|
|
52
|
+
private clock: IClock,
|
|
53
|
+
private readonly validatorStore: ValidatorStore,
|
|
54
|
+
chainHeadTracker: ChainHeaderTracker,
|
|
55
|
+
syncingStatusTracker: SyncingStatusTracker,
|
|
56
|
+
private readonly metrics: Metrics | null,
|
|
57
|
+
private readonly opts?: AttestationDutiesServiceOpts
|
|
58
|
+
) {
|
|
59
|
+
// Running this task every epoch is safe since a re-org of two epochs is very unlikely
|
|
60
|
+
// TODO: If the re-org event is reliable consider re-running then
|
|
61
|
+
clock.runEveryEpoch(this.runDutiesTasks);
|
|
62
|
+
clock.runEverySlot(this.prepareForNextEpoch);
|
|
63
|
+
chainHeadTracker.runOnNewHead(this.onNewHead);
|
|
64
|
+
syncingStatusTracker.runOnResynced(async (slot) => {
|
|
65
|
+
// Skip on first slot of epoch since tasks are already scheduled
|
|
66
|
+
if (!isStartSlotOfEpoch(slot)) {
|
|
67
|
+
return this.runDutiesTasks(computeEpochAtSlot(slot));
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (metrics) {
|
|
72
|
+
metrics.attesterDutiesCount.addCollect(() => {
|
|
73
|
+
const currentSlot = this.clock.getCurrentSlot();
|
|
74
|
+
let duties = 0;
|
|
75
|
+
let nextDutySlot = null;
|
|
76
|
+
for (const [epoch, attDutiesAtEpoch] of this.dutiesByIndexByEpoch) {
|
|
77
|
+
duties += attDutiesAtEpoch.dutiesByIndex.size;
|
|
78
|
+
|
|
79
|
+
// Epochs are sorted, stop searching once a next duty slot is found
|
|
80
|
+
if (epoch < this.clock.currentEpoch || nextDutySlot !== null) continue;
|
|
81
|
+
|
|
82
|
+
for (const {duty} of attDutiesAtEpoch.dutiesByIndex.values()) {
|
|
83
|
+
// Set next duty slot to the closest future slot found in all duties
|
|
84
|
+
if (duty.slot > currentSlot && (nextDutySlot === null || duty.slot < nextDutySlot)) {
|
|
85
|
+
nextDutySlot = duty.slot;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
metrics.attesterDutiesCount.set(duties);
|
|
90
|
+
metrics.attesterDutiesEpochCount.set(this.dutiesByIndexByEpoch.size);
|
|
91
|
+
if (nextDutySlot !== null) metrics.attesterDutiesNextSlot.set(nextDutySlot);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
removeDutiesForKey(pubkey: PubkeyHex): void {
|
|
97
|
+
for (const [epoch, attDutiesAtEpoch] of this.dutiesByIndexByEpoch) {
|
|
98
|
+
for (const [vIndex, attDutyAndProof] of attDutiesAtEpoch.dutiesByIndex) {
|
|
99
|
+
if (toPubkeyHex(attDutyAndProof.duty.pubkey) === pubkey) {
|
|
100
|
+
attDutiesAtEpoch.dutiesByIndex.delete(vIndex);
|
|
101
|
+
if (attDutiesAtEpoch.dutiesByIndex.size === 0) {
|
|
102
|
+
this.dutiesByIndexByEpoch.delete(epoch);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Returns all `ValidatorDuty` for the given `slot` */
|
|
110
|
+
getDutiesAtSlot(slot: Slot): AttDutyAndProof[] {
|
|
111
|
+
const epoch = computeEpochAtSlot(slot);
|
|
112
|
+
const duties: AttDutyAndProof[] = [];
|
|
113
|
+
const epochDuties = this.dutiesByIndexByEpoch.get(epoch);
|
|
114
|
+
if (epochDuties === undefined) {
|
|
115
|
+
return duties;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
for (const validatorDuty of epochDuties.dutiesByIndex.values()) {
|
|
119
|
+
if (validatorDuty.duty.slot === slot) {
|
|
120
|
+
duties.push(validatorDuty);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return duties;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* If a reorg dependent root comes at a slot other than last slot of epoch
|
|
129
|
+
* just update this.pendingDependentRootByEpoch() and process here
|
|
130
|
+
*/
|
|
131
|
+
private prepareForNextEpoch = async (slot: Slot, signal: AbortSignal): Promise<void> => {
|
|
132
|
+
// only interested in last slot of epoch
|
|
133
|
+
if ((slot + 1) % SLOTS_PER_EPOCH !== 0) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// TODO GLOAS: re-evaluate this timing
|
|
138
|
+
// during the 1 / 3 of epoch, last block of epoch may come
|
|
139
|
+
await sleep(this.clock.msToSlot(slot + 1 / 3), signal);
|
|
140
|
+
|
|
141
|
+
const nextEpoch = computeEpochAtSlot(slot) + 1;
|
|
142
|
+
const dependentRoot = this.dutiesByIndexByEpoch.get(nextEpoch)?.dependentRoot;
|
|
143
|
+
const pendingDependentRoot = this.pendingDependentRootByEpoch.get(nextEpoch);
|
|
144
|
+
if (dependentRoot && pendingDependentRoot && dependentRoot !== pendingDependentRoot) {
|
|
145
|
+
// this happens when pendingDependentRoot is not the last block of an epoch
|
|
146
|
+
this.logger.info("Redownload attester duties when it's close to epoch boundary", {nextEpoch, slot});
|
|
147
|
+
await this.handleAttesterDutiesReorg(nextEpoch, slot, dependentRoot, pendingDependentRoot);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
private runDutiesTasks = async (epoch: Epoch): Promise<void> => {
|
|
152
|
+
await Promise.all([
|
|
153
|
+
// Run pollBeaconAttesters immediately for all known local indices
|
|
154
|
+
this.pollBeaconAttesters(epoch, this.validatorStore.getAllLocalIndices()).catch((e: Error) => {
|
|
155
|
+
this.logger.error("Error on poll attesters", {epoch}, e);
|
|
156
|
+
}),
|
|
157
|
+
|
|
158
|
+
// At the same time fetch any remaining unknown validator indices, then poll duties for those newIndices only
|
|
159
|
+
this.validatorStore
|
|
160
|
+
.pollValidatorIndices()
|
|
161
|
+
.then((newIndices) => this.pollBeaconAttesters(epoch, newIndices))
|
|
162
|
+
.catch((e: Error) => {
|
|
163
|
+
this.logger.error("Error on poll indices and attesters", {epoch}, e);
|
|
164
|
+
}),
|
|
165
|
+
]);
|
|
166
|
+
|
|
167
|
+
// After both, prune
|
|
168
|
+
this.pruneOldDuties(epoch);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Query the beacon node for attestation duties for any known validators.
|
|
173
|
+
*
|
|
174
|
+
* This function will perform (in the following order):
|
|
175
|
+
*
|
|
176
|
+
* 1. Poll for current-epoch duties and update the local duties map.
|
|
177
|
+
* 2. As above, but for the next-epoch.
|
|
178
|
+
* 3. Push out any attestation subnet subscriptions to the BN.
|
|
179
|
+
* 4. Prune old entries from duties.
|
|
180
|
+
*/
|
|
181
|
+
private async pollBeaconAttesters(currentEpoch: Epoch, indexArr: ValidatorIndex[]): Promise<void> {
|
|
182
|
+
const nextEpoch = currentEpoch + 1;
|
|
183
|
+
|
|
184
|
+
// No need to bother the BN if we don't have any validators.
|
|
185
|
+
if (indexArr.length === 0) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
for (const epoch of [currentEpoch, nextEpoch]) {
|
|
190
|
+
// Download the duties and update the duties for the current and next epoch.
|
|
191
|
+
await this.pollBeaconAttestersForEpoch(epoch, indexArr).catch((e: Error) => {
|
|
192
|
+
this.logger.error("Failed to download attester duties", {epoch}, e);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const beaconCommitteeSubscriptions: routes.validator.BeaconCommitteeSubscription[] = [];
|
|
197
|
+
|
|
198
|
+
// For this epoch and the next epoch, produce any beacon committee subscriptions.
|
|
199
|
+
//
|
|
200
|
+
// We are *always* pushing out subscriptions, even if we've subscribed before. This is
|
|
201
|
+
// potentially excessive on the BN in normal cases, but it will help with fast re-subscriptions
|
|
202
|
+
// if the BN goes offline or we swap to a different one.
|
|
203
|
+
const indexSet = new Set(indexArr);
|
|
204
|
+
for (const epoch of [currentEpoch, nextEpoch]) {
|
|
205
|
+
const epochDuties = this.dutiesByIndexByEpoch.get(epoch)?.dutiesByIndex;
|
|
206
|
+
if (epochDuties) {
|
|
207
|
+
for (const {duty, selectionProof} of epochDuties.values()) {
|
|
208
|
+
if (indexSet.has(duty.validatorIndex)) {
|
|
209
|
+
beaconCommitteeSubscriptions.push({
|
|
210
|
+
validatorIndex: duty.validatorIndex,
|
|
211
|
+
committeesAtSlot: duty.committeesAtSlot,
|
|
212
|
+
committeeIndex: duty.committeeIndex,
|
|
213
|
+
slot: duty.slot,
|
|
214
|
+
isAggregator: selectionProof !== null,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// If there are any subscriptions, push them out to the beacon node.
|
|
222
|
+
if (beaconCommitteeSubscriptions.length > 0) {
|
|
223
|
+
const subscriptionsBatches = batchItems(beaconCommitteeSubscriptions, {batchSize: SUBSCRIPTIONS_PER_REQUEST});
|
|
224
|
+
const responses = await Promise.all(
|
|
225
|
+
subscriptionsBatches.map((subscriptions) => this.api.validator.prepareBeaconCommitteeSubnet({subscriptions}))
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
for (const res of responses) {
|
|
229
|
+
res.assertOk();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* For the given `indexArr`, download the duties for the given `epoch` and store them in duties.
|
|
236
|
+
*/
|
|
237
|
+
private async pollBeaconAttestersForEpoch(epoch: Epoch, indexArr: ValidatorIndex[]): Promise<void> {
|
|
238
|
+
// Don't fetch duties for epochs before genesis. However, should fetch epoch 0 duties at epoch -1
|
|
239
|
+
if (epoch < 0) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const res = await this.api.validator.getAttesterDuties({epoch, indices: indexArr});
|
|
244
|
+
const attesterDuties = res.value();
|
|
245
|
+
const {dependentRoot} = res.meta();
|
|
246
|
+
const relevantDuties = attesterDuties.filter((duty) => {
|
|
247
|
+
const pubkeyHex = toPubkeyHex(duty.pubkey);
|
|
248
|
+
return this.validatorStore.hasVotingPubkey(pubkeyHex) && this.validatorStore.isDoppelgangerSafe(pubkeyHex);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
this.logger.debug("Downloaded attester duties", {epoch, dependentRoot, count: relevantDuties.length});
|
|
252
|
+
|
|
253
|
+
const dutiesAtEpoch = this.dutiesByIndexByEpoch.get(epoch);
|
|
254
|
+
const priorDependentRoot = dutiesAtEpoch?.dependentRoot;
|
|
255
|
+
const dependentRootChanged = priorDependentRoot !== undefined && priorDependentRoot !== dependentRoot;
|
|
256
|
+
|
|
257
|
+
if (!priorDependentRoot || dependentRootChanged) {
|
|
258
|
+
const dutiesByIndex = new Map<ValidatorIndex, AttDutyAndProof>();
|
|
259
|
+
for (const duty of relevantDuties) {
|
|
260
|
+
const dutyAndProof = await this.getDutyAndProof(duty);
|
|
261
|
+
dutiesByIndex.set(duty.validatorIndex, dutyAndProof);
|
|
262
|
+
}
|
|
263
|
+
this.dutiesByIndexByEpoch.set(epoch, {dependentRoot, dutiesByIndex});
|
|
264
|
+
|
|
265
|
+
if (priorDependentRoot && dependentRootChanged) {
|
|
266
|
+
this.metrics?.attesterDutiesReorg.inc();
|
|
267
|
+
this.logger.warn("Attester duties re-org. This may happen from time to time", {
|
|
268
|
+
priorDependentRoot: priorDependentRoot,
|
|
269
|
+
dependentRoot: dependentRoot,
|
|
270
|
+
epoch,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
const existingDuties = dutiesAtEpoch.dutiesByIndex;
|
|
275
|
+
const existingDutiesCount = existingDuties.size;
|
|
276
|
+
const discoveredNewDuties = relevantDuties.length > existingDutiesCount;
|
|
277
|
+
|
|
278
|
+
if (discoveredNewDuties) {
|
|
279
|
+
for (const duty of relevantDuties) {
|
|
280
|
+
if (!existingDuties.has(duty.validatorIndex)) {
|
|
281
|
+
const dutyAndProof = await this.getDutyAndProof(duty);
|
|
282
|
+
existingDuties.set(duty.validatorIndex, dutyAndProof);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
this.logger.debug("Discovered new attester duties", {
|
|
287
|
+
epoch,
|
|
288
|
+
dependentRoot,
|
|
289
|
+
count: relevantDuties.length - existingDutiesCount,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* attester duties may be reorged due to 2 scenarios:
|
|
297
|
+
* 1. node is syncing (for nextEpoch duties)
|
|
298
|
+
* 2. node is reorged
|
|
299
|
+
* previousDutyDependentRoot = get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch - 1) - 1)
|
|
300
|
+
* => dependent root of current epoch
|
|
301
|
+
* currentDutyDependentRoot = get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch) - 1)
|
|
302
|
+
* => dependent root of next epoch
|
|
303
|
+
*/
|
|
304
|
+
private onNewHead = async ({
|
|
305
|
+
slot,
|
|
306
|
+
head,
|
|
307
|
+
previousDutyDependentRoot,
|
|
308
|
+
currentDutyDependentRoot,
|
|
309
|
+
}: HeadEventData): Promise<void> => {
|
|
310
|
+
const currentEpoch = computeEpochAtSlot(slot);
|
|
311
|
+
const nextEpoch = currentEpoch + 1;
|
|
312
|
+
const nextTwoEpoch = currentEpoch + 2;
|
|
313
|
+
const nextTwoEpochDependentRoot = this.dutiesByIndexByEpoch.get(currentEpoch + 2)?.dependentRoot;
|
|
314
|
+
|
|
315
|
+
// this may happen ONLY when node is syncing
|
|
316
|
+
// it's safe to get attester duties at epoch n + 1 thanks to nextEpochShuffling cache
|
|
317
|
+
// but it's an issue to request attester duties for epoch n + 2 as dependent root keeps changing while node is syncing
|
|
318
|
+
// see https://github.com/ChainSafe/lodestar/issues/3211
|
|
319
|
+
if (nextTwoEpochDependentRoot && head !== nextTwoEpochDependentRoot) {
|
|
320
|
+
// last slot of epoch, we're sure it's the correct dependent root
|
|
321
|
+
if ((slot + 1) % SLOTS_PER_EPOCH === 0) {
|
|
322
|
+
this.logger.info("Next 2 epoch attester duties reorg", {slot, dutyEpoch: nextTwoEpoch, head});
|
|
323
|
+
await this.handleAttesterDutiesReorg(nextTwoEpoch, slot, nextTwoEpochDependentRoot, head);
|
|
324
|
+
} else {
|
|
325
|
+
this.logger.debug("Potential next 2 epoch attester duties reorg", {slot, dutyEpoch: nextTwoEpoch, head});
|
|
326
|
+
// node may send adjacent onHead events while it's syncing
|
|
327
|
+
// wait for getting close to next epoch to make sure the dependRoot
|
|
328
|
+
this.pendingDependentRootByEpoch.set(nextTwoEpoch, head);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// dependent root for next epoch changed
|
|
333
|
+
const nextEpochDependentRoot = this.dutiesByIndexByEpoch.get(nextEpoch)?.dependentRoot;
|
|
334
|
+
if (nextEpochDependentRoot && currentDutyDependentRoot !== nextEpochDependentRoot) {
|
|
335
|
+
this.logger.warn("Potential next epoch attester duties reorg", {
|
|
336
|
+
slot,
|
|
337
|
+
dutyEpoch: nextEpoch,
|
|
338
|
+
priorDependentRoot: nextEpochDependentRoot,
|
|
339
|
+
newDependentRoot: currentDutyDependentRoot,
|
|
340
|
+
});
|
|
341
|
+
await this.handleAttesterDutiesReorg(nextEpoch, slot, nextEpochDependentRoot, currentDutyDependentRoot);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// dependent root for current epoch changed
|
|
345
|
+
const currentEpochDependentRoot = this.dutiesByIndexByEpoch.get(currentEpoch)?.dependentRoot;
|
|
346
|
+
if (currentEpochDependentRoot && currentEpochDependentRoot !== previousDutyDependentRoot) {
|
|
347
|
+
this.logger.warn("Potential current epoch attester duties reorg", {
|
|
348
|
+
slot,
|
|
349
|
+
dutyEpoch: currentEpoch,
|
|
350
|
+
priorDependentRoot: currentEpochDependentRoot,
|
|
351
|
+
newDependentRoot: previousDutyDependentRoot,
|
|
352
|
+
});
|
|
353
|
+
await this.handleAttesterDutiesReorg(currentEpoch, slot, currentEpochDependentRoot, previousDutyDependentRoot);
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
private async handleAttesterDutiesReorg(
|
|
358
|
+
dutyEpoch: Epoch,
|
|
359
|
+
slot: Slot,
|
|
360
|
+
oldDependentRoot: RootHex,
|
|
361
|
+
newDependentRoot: RootHex
|
|
362
|
+
): Promise<void> {
|
|
363
|
+
this.metrics?.attesterDutiesReorg.inc();
|
|
364
|
+
const logContext = {dutyEpoch, slot, oldDependentRoot, newDependentRoot};
|
|
365
|
+
this.logger.debug("Redownload attester duties", logContext);
|
|
366
|
+
|
|
367
|
+
await this.pollBeaconAttestersForEpoch(dutyEpoch, this.validatorStore.getAllLocalIndices())
|
|
368
|
+
.then(() => {
|
|
369
|
+
this.pendingDependentRootByEpoch.delete(dutyEpoch);
|
|
370
|
+
})
|
|
371
|
+
.catch((e: Error) => {
|
|
372
|
+
this.logger.error("Failed to redownload attester duties when reorg happens", logContext, e);
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
private async getDutyAndProof(duty: routes.validator.AttesterDuty): Promise<AttDutyAndProof> {
|
|
377
|
+
const selectionProof = await this.validatorStore.signAttestationSelectionProof(duty.pubkey, duty.slot);
|
|
378
|
+
|
|
379
|
+
if (this.opts?.distributedAggregationSelection) {
|
|
380
|
+
// Validator in distributed cluster only has a key share, not the full private key.
|
|
381
|
+
// Passing a partial selection proof to `is_aggregator` would produce incorrect result.
|
|
382
|
+
// AttestationService will exchange partial for combined selection proofs retrieved from
|
|
383
|
+
// distributed validator middleware client and determine aggregators at beginning of every slot.
|
|
384
|
+
return {duty, selectionProof: null, partialSelectionProof: selectionProof};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const isAggregator = isAggregatorFromCommitteeLength(duty.committeeLength, selectionProof);
|
|
388
|
+
|
|
389
|
+
return {
|
|
390
|
+
duty,
|
|
391
|
+
// selectionProof === null is used to check if is aggregator
|
|
392
|
+
selectionProof: isAggregator ? selectionProof : null,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** Run once per epoch to prune duties map */
|
|
397
|
+
private pruneOldDuties(currentEpoch: Epoch): void {
|
|
398
|
+
for (const byEpochMap of [this.dutiesByIndexByEpoch, this.pendingDependentRootByEpoch]) {
|
|
399
|
+
for (const epoch of byEpochMap.keys()) {
|
|
400
|
+
if (epoch + HISTORICAL_DUTIES_EPOCHS < currentEpoch) {
|
|
401
|
+
byEpochMap.delete(epoch);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import {ApiClient, routes} from "@lodestar/api";
|
|
2
|
+
import {ChainForkConfig} from "@lodestar/config";
|
|
3
|
+
import {
|
|
4
|
+
BLSPubkey,
|
|
5
|
+
BLSSignature,
|
|
6
|
+
BeaconBlock,
|
|
7
|
+
BlindedBeaconBlock,
|
|
8
|
+
BlockContents,
|
|
9
|
+
ProducedBlockSource,
|
|
10
|
+
SignedBlindedBeaconBlock,
|
|
11
|
+
SignedBlockContents,
|
|
12
|
+
Slot,
|
|
13
|
+
isBlindedSignedBeaconBlock,
|
|
14
|
+
} from "@lodestar/types";
|
|
15
|
+
import {extendError, prettyBytes, prettyWeiToEth, toPubkeyHex} from "@lodestar/utils";
|
|
16
|
+
import {Metrics} from "../metrics.js";
|
|
17
|
+
import {PubkeyHex} from "../types.js";
|
|
18
|
+
import {IClock, LoggerVc} from "../util/index.js";
|
|
19
|
+
import {BlockDutiesService, GENESIS_SLOT} from "./blockDuties.js";
|
|
20
|
+
import {ValidatorStore} from "./validatorStore.js";
|
|
21
|
+
|
|
22
|
+
// The following combination of blocks and blobs can be produced
|
|
23
|
+
// i) a full block contents (eg block and all related data-layer data)
|
|
24
|
+
// ii) a blinded block post bellatrix
|
|
25
|
+
type BlindedBlockOrBlockContents =
|
|
26
|
+
| {
|
|
27
|
+
blockContents: BlockContents;
|
|
28
|
+
executionPayloadBlinded: false;
|
|
29
|
+
executionPayloadSource: ProducedBlockSource.engine;
|
|
30
|
+
}
|
|
31
|
+
| {
|
|
32
|
+
block: BlindedBeaconBlock;
|
|
33
|
+
executionPayloadBlinded: true;
|
|
34
|
+
executionPayloadSource: ProducedBlockSource;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type DebugLogCtx = {debugLogCtx: Record<string, string | boolean | undefined>};
|
|
38
|
+
type BlockProposalOpts = {
|
|
39
|
+
broadcastValidation: routes.beacon.BroadcastValidation;
|
|
40
|
+
blindedLocal: boolean;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Service that sets up and handles validator block proposal duties.
|
|
44
|
+
*/
|
|
45
|
+
export class BlockProposingService {
|
|
46
|
+
private readonly dutiesService: BlockDutiesService;
|
|
47
|
+
|
|
48
|
+
constructor(
|
|
49
|
+
private readonly config: ChainForkConfig,
|
|
50
|
+
private readonly logger: LoggerVc,
|
|
51
|
+
private readonly api: ApiClient,
|
|
52
|
+
private readonly clock: IClock,
|
|
53
|
+
private readonly validatorStore: ValidatorStore,
|
|
54
|
+
private readonly metrics: Metrics | null,
|
|
55
|
+
private readonly opts: BlockProposalOpts
|
|
56
|
+
) {
|
|
57
|
+
this.dutiesService = new BlockDutiesService(
|
|
58
|
+
config,
|
|
59
|
+
logger,
|
|
60
|
+
api,
|
|
61
|
+
clock,
|
|
62
|
+
validatorStore,
|
|
63
|
+
metrics,
|
|
64
|
+
this.notifyBlockProductionFn
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
removeDutiesForKey(pubkey: PubkeyHex): void {
|
|
69
|
+
this.dutiesService.removeDutiesForKey(pubkey);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* `BlockDutiesService` must call this fn to trigger block creation
|
|
74
|
+
* This function may run more than once at a time, rationale in `BlockDutiesService.pollBeaconProposers`
|
|
75
|
+
*/
|
|
76
|
+
private notifyBlockProductionFn = (slot: Slot, proposers: BLSPubkey[]): void => {
|
|
77
|
+
if (slot <= GENESIS_SLOT) {
|
|
78
|
+
this.logger.debug("Not producing block before or at genesis slot");
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (proposers.length > 1) {
|
|
83
|
+
this.logger.warn("Multiple block proposers", {slot, count: proposers.length});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
Promise.all(proposers.map((pubkey) => this.createAndPublishBlock(pubkey, slot))).catch((e: Error) => {
|
|
87
|
+
this.logger.error("Error on block duties", {slot}, e);
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/** Produce a block at the given slot for pubkey */
|
|
92
|
+
private async createAndPublishBlock(pubkey: BLSPubkey, slot: Slot): Promise<void> {
|
|
93
|
+
const pubkeyHex = toPubkeyHex(pubkey);
|
|
94
|
+
const logCtx = {slot, validator: prettyBytes(pubkeyHex)};
|
|
95
|
+
|
|
96
|
+
// Wrap with try catch here to re-use `logCtx`
|
|
97
|
+
try {
|
|
98
|
+
const randaoReveal = await this.validatorStore.signRandao(pubkey, slot);
|
|
99
|
+
const graffiti = this.validatorStore.getGraffiti(pubkeyHex);
|
|
100
|
+
|
|
101
|
+
const debugLogCtx = {...logCtx, validator: pubkeyHex};
|
|
102
|
+
|
|
103
|
+
const strictFeeRecipientCheck = this.validatorStore.strictFeeRecipientCheck(pubkeyHex);
|
|
104
|
+
const {selection: builderSelection, boostFactor: builderBoostFactor} =
|
|
105
|
+
this.validatorStore.getBuilderSelectionParams(pubkeyHex);
|
|
106
|
+
const feeRecipient = this.validatorStore.getFeeRecipient(pubkeyHex);
|
|
107
|
+
const blindedLocal = this.opts.blindedLocal;
|
|
108
|
+
|
|
109
|
+
this.logger.debug("Producing block", {
|
|
110
|
+
...debugLogCtx,
|
|
111
|
+
builderSelection,
|
|
112
|
+
builderBoostFactor,
|
|
113
|
+
feeRecipient,
|
|
114
|
+
strictFeeRecipientCheck,
|
|
115
|
+
blindedLocal,
|
|
116
|
+
});
|
|
117
|
+
this.metrics?.proposerStepCallProduceBlock.observe(this.clock.secFromSlot(slot));
|
|
118
|
+
|
|
119
|
+
const produceOpts = {
|
|
120
|
+
feeRecipient,
|
|
121
|
+
strictFeeRecipientCheck,
|
|
122
|
+
blindedLocal,
|
|
123
|
+
};
|
|
124
|
+
const blockContentsWrapper = await this.produceBlockWrapper(
|
|
125
|
+
this.config,
|
|
126
|
+
slot,
|
|
127
|
+
randaoReveal,
|
|
128
|
+
graffiti,
|
|
129
|
+
builderBoostFactor,
|
|
130
|
+
produceOpts,
|
|
131
|
+
builderSelection
|
|
132
|
+
).catch((e: Error) => {
|
|
133
|
+
this.metrics?.blockProposingErrors.inc({error: "produce"});
|
|
134
|
+
throw extendError(e, "Failed to produce block");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
this.logger.debug("Produced block", {...debugLogCtx, ...blockContentsWrapper.debugLogCtx});
|
|
138
|
+
this.metrics?.blocksProduced.inc();
|
|
139
|
+
|
|
140
|
+
const block = blockContentsWrapper.executionPayloadBlinded
|
|
141
|
+
? blockContentsWrapper.block
|
|
142
|
+
: blockContentsWrapper.blockContents.block;
|
|
143
|
+
const signedBlock = await this.validatorStore.signBlock(pubkey, block, slot, this.logger);
|
|
144
|
+
|
|
145
|
+
const {broadcastValidation} = this.opts;
|
|
146
|
+
const publishOpts = {broadcastValidation};
|
|
147
|
+
|
|
148
|
+
const signedBlindedBlockOrBlockContents = blockContentsWrapper.executionPayloadBlinded
|
|
149
|
+
? {signedBlock}
|
|
150
|
+
: {signedBlock, ...blockContentsWrapper.blockContents};
|
|
151
|
+
delete (signedBlindedBlockOrBlockContents as {block?: BeaconBlock}).block; // remove block if present
|
|
152
|
+
|
|
153
|
+
await this.publishBlockWrapper(signedBlindedBlockOrBlockContents, publishOpts).catch((e: Error) => {
|
|
154
|
+
this.metrics?.blockProposingErrors.inc({error: "publish"});
|
|
155
|
+
throw extendError(e, "Failed to publish block");
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
this.metrics?.proposerStepCallPublishBlock.observe(this.clock.secFromSlot(slot));
|
|
159
|
+
this.metrics?.blocksPublished.inc();
|
|
160
|
+
this.logger.info("Published block", {...logCtx, graffiti, ...blockContentsWrapper.debugLogCtx});
|
|
161
|
+
} catch (e) {
|
|
162
|
+
this.logger.error("Error proposing block", logCtx, e as Error);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private publishBlockWrapper = async (
|
|
167
|
+
signedBlindedBlockOrBlockContents: SignedBlockContents | {signedBlock: SignedBlindedBeaconBlock},
|
|
168
|
+
opts: {broadcastValidation?: routes.beacon.BroadcastValidation} = {}
|
|
169
|
+
): Promise<void> => {
|
|
170
|
+
if (isBlindedSignedBeaconBlock(signedBlindedBlockOrBlockContents.signedBlock)) {
|
|
171
|
+
(
|
|
172
|
+
await this.api.beacon.publishBlindedBlockV2({
|
|
173
|
+
signedBlindedBlock: signedBlindedBlockOrBlockContents.signedBlock,
|
|
174
|
+
...opts,
|
|
175
|
+
})
|
|
176
|
+
).assertOk();
|
|
177
|
+
} else {
|
|
178
|
+
(
|
|
179
|
+
await this.api.beacon.publishBlockV2({
|
|
180
|
+
signedBlockContents: signedBlindedBlockOrBlockContents,
|
|
181
|
+
...opts,
|
|
182
|
+
})
|
|
183
|
+
).assertOk();
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
private produceBlockWrapper = async (
|
|
188
|
+
_config: ChainForkConfig,
|
|
189
|
+
slot: Slot,
|
|
190
|
+
randaoReveal: BLSSignature,
|
|
191
|
+
graffiti: string | undefined,
|
|
192
|
+
builderBoostFactor: bigint,
|
|
193
|
+
{feeRecipient, strictFeeRecipientCheck, blindedLocal}: routes.validator.ExtraProduceBlockOpts,
|
|
194
|
+
builderSelection: routes.validator.BuilderSelection
|
|
195
|
+
): Promise<BlindedBlockOrBlockContents & DebugLogCtx> => {
|
|
196
|
+
const res = await this.api.validator.produceBlockV3({
|
|
197
|
+
slot,
|
|
198
|
+
randaoReveal,
|
|
199
|
+
graffiti,
|
|
200
|
+
skipRandaoVerification: false,
|
|
201
|
+
feeRecipient,
|
|
202
|
+
builderSelection,
|
|
203
|
+
strictFeeRecipientCheck,
|
|
204
|
+
blindedLocal,
|
|
205
|
+
builderBoostFactor,
|
|
206
|
+
});
|
|
207
|
+
const meta = res.meta();
|
|
208
|
+
|
|
209
|
+
const debugLogCtx = {
|
|
210
|
+
executionPayloadSource: meta.executionPayloadSource,
|
|
211
|
+
executionPayloadBlinded: meta.executionPayloadBlinded,
|
|
212
|
+
executionPayloadValue: prettyWeiToEth(meta.executionPayloadValue),
|
|
213
|
+
consensusBlockValue: prettyWeiToEth(meta.consensusBlockValue),
|
|
214
|
+
totalBlockValue: prettyWeiToEth(meta.executionPayloadValue + meta.consensusBlockValue),
|
|
215
|
+
// TODO PR: should be used in api call instead of adding in log
|
|
216
|
+
strictFeeRecipientCheck,
|
|
217
|
+
builderSelection,
|
|
218
|
+
api: "produceBlockV3",
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
return parseProduceBlockResponse({data: res.value(), ...meta}, debugLogCtx, builderSelection);
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function parseProduceBlockResponse(
|
|
226
|
+
response: {data: BlockContents | BlindedBeaconBlock} & {
|
|
227
|
+
executionPayloadSource: ProducedBlockSource;
|
|
228
|
+
executionPayloadBlinded: boolean;
|
|
229
|
+
},
|
|
230
|
+
debugLogCtx: Record<string, string | boolean | undefined>,
|
|
231
|
+
builderSelection: routes.validator.BuilderSelection
|
|
232
|
+
): BlindedBlockOrBlockContents & DebugLogCtx {
|
|
233
|
+
const executionPayloadSource = response.executionPayloadSource;
|
|
234
|
+
|
|
235
|
+
if (
|
|
236
|
+
(builderSelection === routes.validator.BuilderSelection.BuilderOnly &&
|
|
237
|
+
executionPayloadSource === ProducedBlockSource.engine) ||
|
|
238
|
+
(builderSelection === routes.validator.BuilderSelection.ExecutionOnly &&
|
|
239
|
+
executionPayloadSource === ProducedBlockSource.builder)
|
|
240
|
+
) {
|
|
241
|
+
throw Error(
|
|
242
|
+
`Block not produced as per desired builderSelection=${builderSelection} executionPayloadSource=${executionPayloadSource}`
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (response.executionPayloadBlinded) {
|
|
247
|
+
return {
|
|
248
|
+
block: response.data,
|
|
249
|
+
executionPayloadBlinded: true,
|
|
250
|
+
executionPayloadSource,
|
|
251
|
+
debugLogCtx,
|
|
252
|
+
} as BlindedBlockOrBlockContents & DebugLogCtx;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
blockContents: response.data,
|
|
257
|
+
executionPayloadBlinded: false,
|
|
258
|
+
executionPayloadSource,
|
|
259
|
+
debugLogCtx,
|
|
260
|
+
} as BlindedBlockOrBlockContents & DebugLogCtx;
|
|
261
|
+
}
|