@lodestar/validator 1.35.0-dev.fcf8d024ea → 1.35.0-rc.0

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