@lodestar/beacon-node 1.39.0-dev.268dcb02bf → 1.39.0-dev.3932675174

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.
Files changed (32) hide show
  1. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  2. package/lib/chain/blocks/importBlock.js +6 -0
  3. package/lib/chain/blocks/importBlock.js.map +1 -1
  4. package/lib/chain/chain.d.ts +1 -0
  5. package/lib/chain/chain.d.ts.map +1 -1
  6. package/lib/chain/chain.js +11 -2
  7. package/lib/chain/chain.js.map +1 -1
  8. package/lib/chain/opPools/aggregatedAttestationPool.d.ts +1 -1
  9. package/lib/chain/opPools/aggregatedAttestationPool.d.ts.map +1 -1
  10. package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
  11. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  12. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  13. package/lib/chain/prepareNextSlot.js +1 -6
  14. package/lib/chain/prepareNextSlot.js.map +1 -1
  15. package/lib/chain/regen/interface.d.ts +0 -4
  16. package/lib/chain/regen/interface.d.ts.map +1 -1
  17. package/lib/chain/shufflingCache.d.ts +5 -12
  18. package/lib/chain/shufflingCache.d.ts.map +1 -1
  19. package/lib/chain/shufflingCache.js +12 -50
  20. package/lib/chain/shufflingCache.js.map +1 -1
  21. package/lib/metrics/metrics/lodestar.d.ts +1 -6
  22. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  23. package/lib/metrics/metrics/lodestar.js +3 -17
  24. package/lib/metrics/metrics/lodestar.js.map +1 -1
  25. package/package.json +15 -15
  26. package/src/chain/blocks/importBlock.ts +7 -0
  27. package/src/chain/chain.ts +13 -3
  28. package/src/chain/opPools/aggregatedAttestationPool.ts +3 -3
  29. package/src/chain/prepareNextSlot.ts +1 -6
  30. package/src/chain/regen/interface.ts +0 -4
  31. package/src/chain/shufflingCache.ts +15 -61
  32. package/src/metrics/metrics/lodestar.ts +3 -17
@@ -52,6 +52,7 @@ import {computeNodeIdFromPrivateKey} from "../network/subnets/interface.js";
52
52
  import {BufferPool} from "../util/bufferPool.js";
53
53
  import {Clock, ClockEvent, IClock} from "../util/clock.js";
54
54
  import {CustodyConfig, getValidatorsCustodyRequirement} from "../util/dataColumns.js";
55
+ import {callInNextEventLoop} from "../util/eventLoop.js";
55
56
  import {ensureDir, writeIfNotExist} from "../util/file.js";
56
57
  import {isOptimisticBlock} from "../util/forkChoice.js";
57
58
  import {SerializedCache} from "../util/serializedCache.js";
@@ -291,7 +292,8 @@ export class BeaconChain implements IBeaconChain {
291
292
  });
292
293
 
293
294
  this._earliestAvailableSlot = anchorState.slot;
294
- this.shufflingCache = anchorState.epochCtx.shufflingCache = new ShufflingCache(metrics, logger, this.opts, [
295
+
296
+ this.shufflingCache = new ShufflingCache(metrics, logger, this.opts, [
295
297
  {
296
298
  shuffling: anchorState.epochCtx.previousShuffling,
297
299
  decisionRoot: anchorState.epochCtx.previousDecisionRoot,
@@ -417,6 +419,7 @@ export class BeaconChain implements IBeaconChain {
417
419
  clock.addListener(ClockEvent.epoch, this.onClockEpoch.bind(this));
418
420
  emitter.addListener(ChainEvent.forkChoiceFinalized, this.onForkChoiceFinalized.bind(this));
419
421
  emitter.addListener(ChainEvent.forkChoiceJustified, this.onForkChoiceJustified.bind(this));
422
+ emitter.addListener(ChainEvent.checkpoint, this.onCheckpoint.bind(this));
420
423
  }
421
424
 
422
425
  async init(): Promise<void> {
@@ -980,8 +983,8 @@ export class BeaconChain implements IBeaconChain {
980
983
  this.metrics?.gossipAttestation.useHeadBlockState.inc({caller: regenCaller});
981
984
  state = await this.regen.getState(attHeadBlock.stateRoot, regenCaller);
982
985
  }
983
-
984
- // should always be the current epoch of the active context so no need to await a result from the ShufflingCache
986
+ // resolve the promise to unblock other calls of the same epoch and dependent root
987
+ this.shufflingCache.processState(state);
985
988
  return state.epochCtx.getShufflingAtEpoch(attEpoch);
986
989
  }
987
990
 
@@ -1165,6 +1168,13 @@ export class BeaconChain implements IBeaconChain {
1165
1168
  this.logger.verbose("Fork choice justified", {epoch: cp.epoch, root: cp.rootHex});
1166
1169
  }
1167
1170
 
1171
+ private onCheckpoint(this: BeaconChain, _checkpoint: phase0.Checkpoint, state: CachedBeaconStateAllForks): void {
1172
+ // Defer to not block other checkpoint event handlers, which can cause lightclient update delays
1173
+ callInNextEventLoop(() => {
1174
+ this.shufflingCache.processState(state);
1175
+ });
1176
+ }
1177
+
1168
1178
  private async onForkChoiceFinalized(this: BeaconChain, cp: CheckpointWithHex): Promise<void> {
1169
1179
  this.logger.verbose("Fork choice finalized", {epoch: cp.epoch, root: cp.rootHex});
1170
1180
  this.seenBlockProposers.prune(computeStartSlotAtEpoch(cp.epoch));
@@ -218,9 +218,9 @@ export class AggregatedAttestationPool {
218
218
  * Get attestations to be included in a block pre-electra. Returns up to $MAX_ATTESTATIONS items
219
219
  */
220
220
  getAttestationsForBlockPreElectra(
221
- fork: ForkName,
222
- forkChoice: IForkChoice,
223
- state: CachedBeaconStateAllForks
221
+ _fork: ForkName,
222
+ _forkChoice: IForkChoice,
223
+ _state: CachedBeaconStateAllForks
224
224
  ): phase0.Attestation[] {
225
225
  throw new Error("Does not support producing blocks for pre-electra forks anymore");
226
226
  }
@@ -117,12 +117,7 @@ export class PrepareNextSlotScheduler {
117
117
  // the slot 0 of next epoch will likely use this Previous Root Checkpoint state for state transition so we transfer cache here
118
118
  // the resulting state with cache will be cached in Checkpoint State Cache which is used for the upcoming block processing
119
119
  // for other slots dontTransferCached=true because we don't run state transition on this state
120
- //
121
- // Shuffling calculation will be done asynchronously when passing asyncShufflingCalculation=true. Shuffling will be queued in
122
- // beforeProcessEpoch and should theoretically be ready immediately after the synchronous epoch transition finished and the
123
- // event loop is free. In long periods of non-finality too many forks will cause the shufflingCache to throw an error for
124
- // too many queued shufflings so only run async during normal epoch transition. See issue ChainSafe/lodestar#7244
125
- {dontTransferCache: !isEpochTransition, asyncShufflingCalculation: true},
120
+ {dontTransferCache: !isEpochTransition},
126
121
  RegenCaller.precomputeEpoch
127
122
  );
128
123
 
@@ -31,10 +31,6 @@ export enum RegenFnName {
31
31
 
32
32
  export type StateRegenerationOpts = {
33
33
  dontTransferCache: boolean;
34
- /**
35
- * Do not queue shuffling calculation async. Forces sync JIT calculation in afterProcessEpoch if not passed as `true`
36
- */
37
- asyncShufflingCalculation?: boolean;
38
34
  };
39
35
 
40
36
  export interface IStateRegenerator extends IStateRegeneratorInternal {
@@ -1,11 +1,4 @@
1
- import {
2
- BeaconStateAllForks,
3
- EpochShuffling,
4
- IShufflingCache,
5
- ShufflingBuildProps,
6
- computeEpochShuffling,
7
- computeEpochShufflingAsync,
8
- } from "@lodestar/state-transition";
1
+ import {CachedBeaconStateAllForks, EpochShuffling} from "@lodestar/state-transition";
9
2
  import {Epoch, RootHex} from "@lodestar/types";
10
3
  import {LodestarError, Logger, MapDef, pruneSetToMax} from "@lodestar/utils";
11
4
  import {Metrics} from "../metrics/metrics.js";
@@ -53,7 +46,7 @@ export type ShufflingCacheOpts = {
53
46
  * - if a shuffling is not available (which does not happen with default chain option of maxSkipSlots = 32), track a promise to make sure we don't compute the same shuffling twice
54
47
  * - skip computing shuffling when loading state bytes from disk
55
48
  */
56
- export class ShufflingCache implements IShufflingCache {
49
+ export class ShufflingCache {
57
50
  /** LRU cache implemented as a map, pruned every time we add an item */
58
51
  private readonly itemsByDecisionRootByEpoch: MapDef<Epoch, Map<RootHex, CacheItem>> = new MapDef(
59
52
  () => new Map<RootHex, CacheItem>()
@@ -136,60 +129,20 @@ export class ShufflingCache implements IShufflingCache {
136
129
  }
137
130
 
138
131
  /**
139
- * Gets a cached shuffling via the epoch and decision root. If the shuffling is not
140
- * available it will build it synchronously and return the shuffling.
141
- *
142
- * NOTE: If a shuffling is already queued and not calculated it will build and resolve
143
- * the promise but the already queued build will happen at some later time
132
+ * Process a state to extract and cache all shufflings (previous, current, next).
133
+ * Uses the stored decision roots from epochCtx.
144
134
  */
145
- getSync<T extends ShufflingBuildProps | undefined>(
146
- epoch: Epoch,
147
- decisionRoot: RootHex,
148
- buildProps?: T
149
- ): T extends ShufflingBuildProps ? EpochShuffling : EpochShuffling | null {
150
- const cacheItem = this.itemsByDecisionRootByEpoch.getOrDefault(epoch).get(decisionRoot);
151
- if (!cacheItem) {
152
- this.metrics?.shufflingCache.miss.inc();
153
- } else if (isShufflingCacheItem(cacheItem)) {
154
- this.metrics?.shufflingCache.hit.inc();
155
- return cacheItem.shuffling;
156
- } else if (buildProps) {
157
- // TODO: (@matthewkeil) This should possible log a warning??
158
- this.metrics?.shufflingCache.shufflingPromiseNotResolvedAndThrownAway.inc();
159
- } else {
160
- this.metrics?.shufflingCache.shufflingPromiseNotResolved.inc();
161
- }
135
+ processState(state: CachedBeaconStateAllForks): void {
136
+ const {epochCtx} = state;
162
137
 
163
- let shuffling: EpochShuffling | null = null;
164
- if (buildProps) {
165
- const timer = this.metrics?.shufflingCache.shufflingCalculationTime.startTimer({source: "getSync"});
166
- shuffling = computeEpochShuffling(buildProps.state, buildProps.activeIndices, epoch);
167
- timer?.();
168
- this.set(shuffling, decisionRoot);
169
- }
170
- return shuffling as T extends ShufflingBuildProps ? EpochShuffling : EpochShuffling | null;
171
- }
138
+ // Cache previous shuffling
139
+ this.set(epochCtx.previousShuffling, epochCtx.previousDecisionRoot);
172
140
 
173
- /**
174
- * Queue asynchronous build for an EpochShuffling, triggered from state-transition
175
- */
176
- build(epoch: number, decisionRoot: string, state: BeaconStateAllForks, activeIndices: Uint32Array): void {
177
- this.insertPromise(epoch, decisionRoot);
178
- /**
179
- * TODO: (@matthewkeil) This will get replaced by a proper build queue and a worker to do calculations
180
- * on a NICE thread
181
- */
182
- const timer = this.metrics?.shufflingCache.shufflingCalculationTime.startTimer({source: "build"});
183
- computeEpochShufflingAsync(state, activeIndices, epoch)
184
- .then((shuffling) => {
185
- this.set(shuffling, decisionRoot);
186
- })
187
- .catch((err) =>
188
- this.logger?.error(`error building shuffling for epoch ${epoch} at decisionRoot ${decisionRoot}`, {}, err)
189
- )
190
- .finally(() => {
191
- timer?.();
192
- });
141
+ // Cache current shuffling
142
+ this.set(epochCtx.currentShuffling, epochCtx.currentDecisionRoot);
143
+
144
+ // Cache next shuffling
145
+ this.set(epochCtx.nextShuffling, epochCtx.nextDecisionRoot);
193
146
  }
194
147
 
195
148
  /**
@@ -207,7 +160,8 @@ export class ShufflingCache implements IShufflingCache {
207
160
  (Date.now() - cacheItem.timeInsertedMs) / 1000
208
161
  );
209
162
  } else {
210
- this.metrics?.shufflingCache.shufflingBuiltMultipleTimes.inc();
163
+ this.metrics?.shufflingCache.shufflingSetMultipleTimes.inc();
164
+ return;
211
165
  }
212
166
  }
213
167
  // set the shuffling
@@ -1308,33 +1308,19 @@ export function createLodestarMetrics(
1308
1308
  name: "lodestar_shuffling_cache_miss_count",
1309
1309
  help: "Count of shuffling cache miss",
1310
1310
  }),
1311
- shufflingBuiltMultipleTimes: register.gauge({
1312
- name: "lodestar_shuffling_cache_recalculated_shuffling_count",
1313
- help: "Count of shuffling that were build multiple times",
1314
- }),
1315
- shufflingPromiseNotResolvedAndThrownAway: register.gauge({
1316
- name: "lodestar_shuffling_cache_promise_not_resolved_and_thrown_away_count",
1317
- help: "Count of shuffling cache promises that were discarded and the shuffling was built synchronously",
1311
+ shufflingSetMultipleTimes: register.gauge({
1312
+ name: "lodestar_shuffling_cache_set_multiple_times_count",
1313
+ help: "Count of shuffling that were set multiple times",
1318
1314
  }),
1319
1315
  shufflingPromiseNotResolved: register.gauge({
1320
1316
  name: "lodestar_shuffling_cache_promise_not_resolved_count",
1321
1317
  help: "Count of shuffling cache promises that were requested before the promise was resolved",
1322
1318
  }),
1323
- nextShufflingNotOnEpochCache: register.gauge({
1324
- name: "lodestar_shuffling_cache_next_shuffling_not_on_epoch_cache",
1325
- help: "The next shuffling was not on the epoch cache before the epoch transition",
1326
- }),
1327
1319
  shufflingPromiseResolutionTime: register.histogram({
1328
1320
  name: "lodestar_shuffling_cache_promise_resolution_time_seconds",
1329
1321
  help: "Time from promise insertion until promise resolution when shuffling was ready in seconds",
1330
1322
  buckets: [0.5, 1, 1.5, 2],
1331
1323
  }),
1332
- shufflingCalculationTime: register.histogram<{source: "build" | "getSync"}>({
1333
- name: "lodestar_shuffling_cache_shuffling_calculation_time_seconds",
1334
- help: "Run time of shuffling calculation",
1335
- buckets: [0.5, 0.75, 1, 1.25, 1.5],
1336
- labelNames: ["source"],
1337
- }),
1338
1324
  },
1339
1325
 
1340
1326
  seenCache: {