@lodestar/validator 1.35.0-rc.0 → 1.35.0-rc.1
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/metrics.js +14 -14
- package/lib/metrics.js.map +1 -1
- package/lib/services/attestation.d.ts.map +1 -1
- package/lib/services/attestation.js +35 -27
- package/lib/services/attestation.js.map +1 -1
- package/lib/services/attestationDuties.d.ts.map +1 -1
- package/lib/services/attestationDuties.js +1 -0
- package/lib/services/attestationDuties.js.map +1 -1
- package/lib/services/blockDuties.d.ts +2 -2
- package/lib/services/blockDuties.d.ts.map +1 -1
- package/lib/services/blockDuties.js +4 -3
- package/lib/services/blockDuties.js.map +1 -1
- package/lib/services/syncCommittee.d.ts.map +1 -1
- package/lib/services/syncCommittee.js +30 -22
- package/lib/services/syncCommittee.js.map +1 -1
- package/lib/util/clock.d.ts +3 -0
- package/lib/util/clock.d.ts.map +1 -1
- package/lib/util/clock.js +4 -0
- package/lib/util/clock.js.map +1 -1
- package/lib/util/params.js +8 -1
- package/lib/util/params.js.map +1 -1
- package/package.json +10 -10
- package/src/metrics.ts +14 -14
- package/src/services/attestation.ts +37 -24
- package/src/services/attestationDuties.ts +1 -0
- package/src/services/blockDuties.ts +7 -5
- package/src/services/syncCommittee.ts +43 -22
- package/src/util/clock.ts +6 -0
- package/src/util/params.ts +8 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {ApiClient, routes} from "@lodestar/api";
|
|
2
|
-
import {
|
|
2
|
+
import {ChainForkConfig} from "@lodestar/config";
|
|
3
3
|
import {computeEpochAtSlot, computeStartSlotAtEpoch} from "@lodestar/state-transition";
|
|
4
4
|
import {BLSPubkey, Epoch, RootHex, Slot} from "@lodestar/types";
|
|
5
5
|
import {sleep, toPubkeyHex} from "@lodestar/utils";
|
|
@@ -9,10 +9,11 @@ import {IClock, LoggerVc, differenceHex} from "../util/index.js";
|
|
|
9
9
|
import {ValidatorStore} from "./validatorStore.js";
|
|
10
10
|
|
|
11
11
|
/** This polls block duties 1s before the next epoch */
|
|
12
|
-
// TODO: change to 6 to do it 2s before the next epoch
|
|
12
|
+
// TODO: change to 8333 (5/6 of slot) to do it 2s before the next epoch
|
|
13
13
|
// once we have some improvement on epoch transition time
|
|
14
14
|
// see https://github.com/ChainSafe/lodestar/issues/5792#issuecomment-1647457442
|
|
15
|
-
|
|
15
|
+
// TODO GLOAS: re-evaluate timing
|
|
16
|
+
const BLOCK_DUTIES_LOOKAHEAD_BPS = 9167;
|
|
16
17
|
/** Only retain `HISTORICAL_DUTIES_EPOCHS` duties prior to the current epoch */
|
|
17
18
|
const HISTORICAL_DUTIES_EPOCHS = 2;
|
|
18
19
|
// Re-declaring to not have to depend on `lodestar-params` just for this 0
|
|
@@ -30,7 +31,7 @@ export class BlockDutiesService {
|
|
|
30
31
|
private readonly proposers = new Map<Epoch, BlockDutyAtEpoch>();
|
|
31
32
|
|
|
32
33
|
constructor(
|
|
33
|
-
private readonly config:
|
|
34
|
+
private readonly config: ChainForkConfig,
|
|
34
35
|
private readonly logger: LoggerVc,
|
|
35
36
|
private readonly api: ApiClient,
|
|
36
37
|
private readonly clock: IClock,
|
|
@@ -169,7 +170,8 @@ export class BlockDutiesService {
|
|
|
169
170
|
*/
|
|
170
171
|
private async pollBeaconProposersNextEpoch(currentSlot: Slot, nextEpoch: Epoch, signal: AbortSignal): Promise<void> {
|
|
171
172
|
const nextSlot = currentSlot + 1;
|
|
172
|
-
const lookAheadMs =
|
|
173
|
+
const lookAheadMs =
|
|
174
|
+
this.config.SLOT_DURATION_MS - this.config.getSlotComponentDurationMs(BLOCK_DUTIES_LOOKAHEAD_BPS);
|
|
173
175
|
await sleep(this.clock.msToSlot(nextSlot) - lookAheadMs, signal);
|
|
174
176
|
this.logger.debug("Polling proposers for next epoch", {nextEpoch, nextSlot});
|
|
175
177
|
// Poll proposers for the next epoch
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {ApiClient, routes} from "@lodestar/api";
|
|
2
2
|
import {ChainForkConfig} from "@lodestar/config";
|
|
3
|
-
import {
|
|
3
|
+
import {ForkName, isForkPostAltair} from "@lodestar/params";
|
|
4
|
+
import {isSyncCommitteeAggregator} from "@lodestar/state-transition";
|
|
4
5
|
import {BLSSignature, CommitteeIndex, Root, Slot, altair} from "@lodestar/types";
|
|
5
6
|
import {sleep} from "@lodestar/utils";
|
|
6
7
|
import {Metrics} from "../metrics.js";
|
|
@@ -58,9 +59,11 @@ export class SyncCommitteeService {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
private runSyncCommitteeTasks = async (slot: Slot, signal: AbortSignal): Promise<void> => {
|
|
62
|
+
const fork = this.config.getForkName(slot);
|
|
63
|
+
|
|
61
64
|
try {
|
|
62
65
|
// Before altair fork no need to check duties
|
|
63
|
-
if (
|
|
66
|
+
if (!isForkPostAltair(fork)) {
|
|
64
67
|
return;
|
|
65
68
|
}
|
|
66
69
|
|
|
@@ -77,25 +80,32 @@ export class SyncCommitteeService {
|
|
|
77
80
|
// This will run in parallel to other sync committee tasks but must be finished before starting
|
|
78
81
|
// sync committee contributions as it is required to correctly determine if validator is aggregator
|
|
79
82
|
// and to produce a ContributionAndProof that can be threshold aggregated by the middleware client.
|
|
80
|
-
this.runDistributedAggregationSelectionTasks(dutiesAtSlot, slot, signal).catch((e) =>
|
|
83
|
+
this.runDistributedAggregationSelectionTasks(fork, dutiesAtSlot, slot, signal).catch((e) =>
|
|
81
84
|
this.logger.error("Error on sync committee aggregation selection", {slot}, e)
|
|
82
85
|
);
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
// unlike Attestation, SyncCommitteeSignature could be published asap
|
|
86
|
-
// especially with lodestar, it's very busy at
|
|
89
|
+
// especially with lodestar, it's very busy at ATTESTATION_DUE_BPS of the slot
|
|
87
90
|
// see https://github.com/ChainSafe/lodestar/issues/4608
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
const syncMessageDueMs = this.config.getSyncMessageDueMs(fork);
|
|
92
|
+
await Promise.race([
|
|
93
|
+
sleep(syncMessageDueMs - this.clock.msFromSlot(slot), signal),
|
|
94
|
+
this.emitter.waitForBlockSlot(slot),
|
|
95
|
+
]);
|
|
96
|
+
this.metrics?.syncCommitteeStepCallProduceMessage.observe(this.clock.secFromSlot(slot) - syncMessageDueMs / 1000);
|
|
90
97
|
|
|
91
98
|
// Step 1. Download, sign and publish an `SyncCommitteeMessage` for each validator.
|
|
92
99
|
// Differs from AttestationService, `SyncCommitteeMessage` are equal for all
|
|
93
|
-
const beaconBlockRoot = await this.produceAndPublishSyncCommittees(slot, dutiesAtSlot);
|
|
100
|
+
const beaconBlockRoot = await this.produceAndPublishSyncCommittees(fork, slot, dutiesAtSlot);
|
|
94
101
|
|
|
95
102
|
// Step 2. If an attestation was produced, make an aggregate.
|
|
96
|
-
// First, wait until the `
|
|
97
|
-
|
|
98
|
-
|
|
103
|
+
// First, wait until the `CONTRIBUTION_DUE_BPS` of the slot
|
|
104
|
+
const syncContributionDueMs = this.config.getSyncContributionDueMs(fork);
|
|
105
|
+
await sleep(syncContributionDueMs - this.clock.msFromSlot(slot), signal);
|
|
106
|
+
this.metrics?.syncCommitteeStepCallProduceAggregate.observe(
|
|
107
|
+
this.clock.secFromSlot(slot) - syncContributionDueMs / 1000
|
|
108
|
+
);
|
|
99
109
|
|
|
100
110
|
// await for all so if the Beacon node is overloaded it auto-throttles
|
|
101
111
|
// TODO: This approach is conservative to reduce the node's load, review
|
|
@@ -105,9 +115,11 @@ export class SyncCommitteeService {
|
|
|
105
115
|
if (duties.length === 0) return;
|
|
106
116
|
// Then download, sign and publish a `SignedAggregateAndProof` for each
|
|
107
117
|
// validator that is elected to aggregate for this `slot` and `subcommitteeIndex`.
|
|
108
|
-
await this.produceAndPublishAggregates(slot, subcommitteeIndex, beaconBlockRoot, duties).catch(
|
|
109
|
-
|
|
110
|
-
|
|
118
|
+
await this.produceAndPublishAggregates(fork, slot, subcommitteeIndex, beaconBlockRoot, duties).catch(
|
|
119
|
+
(e: Error) => {
|
|
120
|
+
this.logger.error("Error on SyncCommitteeContribution", {slot, index: subcommitteeIndex}, e);
|
|
121
|
+
}
|
|
122
|
+
);
|
|
111
123
|
})
|
|
112
124
|
);
|
|
113
125
|
} catch (e) {
|
|
@@ -124,7 +136,11 @@ export class SyncCommitteeService {
|
|
|
124
136
|
* Only one `SyncCommittee` is downloaded from the BN. It is then signed by each
|
|
125
137
|
* validator and the list of individually-signed `SyncCommittee` objects is returned to the BN.
|
|
126
138
|
*/
|
|
127
|
-
private async produceAndPublishSyncCommittees(
|
|
139
|
+
private async produceAndPublishSyncCommittees(
|
|
140
|
+
fork: ForkName,
|
|
141
|
+
slot: Slot,
|
|
142
|
+
duties: SyncDutyAndProofs[]
|
|
143
|
+
): Promise<Root> {
|
|
128
144
|
const logCtx = {slot};
|
|
129
145
|
|
|
130
146
|
// /eth/v1/beacon/blocks/:blockId/root -> at slot -1
|
|
@@ -156,14 +172,15 @@ export class SyncCommitteeService {
|
|
|
156
172
|
// by default we want to submit SyncCommitteeSignature asap after we receive block
|
|
157
173
|
// provide a delay option just in case any client implementation validate the existence of block in
|
|
158
174
|
// SyncCommitteeSignature gossip validation.
|
|
159
|
-
const
|
|
175
|
+
const syncMessageDueMs = this.config.getSyncMessageDueMs(fork);
|
|
176
|
+
const msToCutoffTime = syncMessageDueMs - this.clock.msFromSlot(slot);
|
|
160
177
|
const afterBlockDelayMs = 1000 * this.clock.secondsPerSlot * (this.opts?.scAfterBlockDelaySlotFraction ?? 0);
|
|
161
|
-
const toDelayMs = Math.min(
|
|
178
|
+
const toDelayMs = Math.min(msToCutoffTime, afterBlockDelayMs);
|
|
162
179
|
if (toDelayMs > 0) {
|
|
163
180
|
await sleep(toDelayMs);
|
|
164
181
|
}
|
|
165
182
|
|
|
166
|
-
this.metrics?.syncCommitteeStepCallPublishMessage.observe(this.clock.secFromSlot(slot
|
|
183
|
+
this.metrics?.syncCommitteeStepCallPublishMessage.observe(this.clock.secFromSlot(slot) - syncMessageDueMs / 1000);
|
|
167
184
|
|
|
168
185
|
if (signatures.length > 0) {
|
|
169
186
|
try {
|
|
@@ -189,6 +206,7 @@ export class SyncCommitteeService {
|
|
|
189
206
|
* returned to the BN.
|
|
190
207
|
*/
|
|
191
208
|
private async produceAndPublishAggregates(
|
|
209
|
+
fork: ForkName,
|
|
192
210
|
slot: Slot,
|
|
193
211
|
subcommitteeIndex: CommitteeIndex,
|
|
194
212
|
beaconBlockRoot: Root,
|
|
@@ -223,7 +241,9 @@ export class SyncCommitteeService {
|
|
|
223
241
|
})
|
|
224
242
|
);
|
|
225
243
|
|
|
226
|
-
this.metrics?.syncCommitteeStepCallPublishAggregate.observe(
|
|
244
|
+
this.metrics?.syncCommitteeStepCallPublishAggregate.observe(
|
|
245
|
+
this.clock.secFromSlot(slot) - this.config.getSyncContributionDueMs(fork) / 1000
|
|
246
|
+
);
|
|
227
247
|
|
|
228
248
|
if (signedContributions.length > 0) {
|
|
229
249
|
try {
|
|
@@ -248,6 +268,7 @@ export class SyncCommitteeService {
|
|
|
248
268
|
* See https://docs.google.com/document/d/1q9jOTPcYQa-3L8luRvQJ-M0eegtba4Nmon3dpO79TMk/mobilebasic
|
|
249
269
|
*/
|
|
250
270
|
private async runDistributedAggregationSelectionTasks(
|
|
271
|
+
fork: ForkName,
|
|
251
272
|
duties: SyncDutyAndProofs[],
|
|
252
273
|
slot: number,
|
|
253
274
|
signal: AbortSignal
|
|
@@ -270,18 +291,18 @@ export class SyncCommitteeService {
|
|
|
270
291
|
|
|
271
292
|
const res = await Promise.race([
|
|
272
293
|
this.api.validator.submitSyncCommitteeSelections({selections: partialSelections}),
|
|
273
|
-
// Exit sync committee contributions flow if there is no response after
|
|
274
|
-
// This is in contrast to attestations aggregations flow which is already exited at
|
|
294
|
+
// Exit sync committee contributions flow if there is no response after CONTRIBUTION_DUE_BPS of the slot.
|
|
295
|
+
// This is in contrast to attestations aggregations flow which is already exited at ATTESTATION_DUE_BPS of the slot
|
|
275
296
|
// because for sync committee is not required to resubscribe to subnets as beacon node will assume
|
|
276
297
|
// validator always aggregates. This allows us to wait until we have to produce sync committee contributions.
|
|
277
298
|
// Note that the sync committee contributions flow is not explicitly exited but rather will be skipped
|
|
278
299
|
// due to the fact that calculation of `is_sync_committee_aggregator` in SyncCommitteeDutiesService is not done
|
|
279
300
|
// and selectionProof is set to null, meaning no validator will be considered an aggregator.
|
|
280
|
-
sleep(this.clock.
|
|
301
|
+
sleep(this.config.getSyncContributionDueMs(fork) - this.clock.msFromSlot(slot), signal),
|
|
281
302
|
]);
|
|
282
303
|
|
|
283
304
|
if (!res) {
|
|
284
|
-
throw new Error("Failed to receive combined selection proofs before
|
|
305
|
+
throw new Error("Failed to receive combined selection proofs before CONTRIBUTION_DUE_BPS of the slot");
|
|
285
306
|
}
|
|
286
307
|
|
|
287
308
|
const combinedSelections = res.value();
|
package/src/util/clock.ts
CHANGED
|
@@ -16,6 +16,7 @@ export interface IClock {
|
|
|
16
16
|
runEverySlot(fn: (slot: Slot, signal: AbortSignal) => Promise<void>): void;
|
|
17
17
|
runEveryEpoch(fn: (epoch: Epoch, signal: AbortSignal) => Promise<void>): void;
|
|
18
18
|
msToSlot(slot: Slot): number;
|
|
19
|
+
msFromSlot(slot: Slot): number;
|
|
19
20
|
secFromSlot(slot: Slot): number;
|
|
20
21
|
getCurrentSlot(): Slot;
|
|
21
22
|
getCurrentEpoch(): Epoch;
|
|
@@ -76,6 +77,11 @@ export class Clock implements IClock {
|
|
|
76
77
|
return timeAt * 1000 - Date.now();
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
/** Milliseconds elapsed from a specific slot to now */
|
|
81
|
+
msFromSlot(slot: Slot): number {
|
|
82
|
+
return Date.now() - (this.genesisTime * 1000 + this.config.SLOT_DURATION_MS * slot);
|
|
83
|
+
}
|
|
84
|
+
|
|
79
85
|
/** Seconds elapsed from a specific slot to now */
|
|
80
86
|
secFromSlot(slot: Slot): number {
|
|
81
87
|
return Date.now() / 1000 - (this.genesisTime + this.config.SECONDS_PER_SLOT * slot);
|
package/src/util/params.ts
CHANGED
|
@@ -146,11 +146,18 @@ function getSpecCriticalParams(localConfig: ChainConfig): Record<keyof ConfigWit
|
|
|
146
146
|
GLOAS_FORK_EPOCH: gloasForkRelevant,
|
|
147
147
|
|
|
148
148
|
// Time parameters
|
|
149
|
-
SECONDS_PER_SLOT: true,
|
|
149
|
+
SECONDS_PER_SLOT: true, // Deprecated
|
|
150
|
+
SLOT_DURATION_MS: true,
|
|
150
151
|
SECONDS_PER_ETH1_BLOCK: false, // Legacy
|
|
151
152
|
MIN_VALIDATOR_WITHDRAWABILITY_DELAY: true,
|
|
152
153
|
SHARD_COMMITTEE_PERIOD: true,
|
|
153
154
|
ETH1_FOLLOW_DISTANCE: true,
|
|
155
|
+
PROPOSER_REORG_CUTOFF_BPS: true,
|
|
156
|
+
ATTESTATION_DUE_BPS: true,
|
|
157
|
+
AGGREGATE_DUE_BPS: true,
|
|
158
|
+
// Altair
|
|
159
|
+
SYNC_MESSAGE_DUE_BPS: altairForkRelevant,
|
|
160
|
+
CONTRIBUTION_DUE_BPS: altairForkRelevant,
|
|
154
161
|
|
|
155
162
|
// Validator cycle
|
|
156
163
|
INACTIVITY_SCORE_BIAS: true,
|