@lodestar/state-transition 1.42.0-dev.5007d8c6d6 → 1.42.0-dev.57ea00d5ae

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 (48) hide show
  1. package/lib/block/processAttestationsAltair.d.ts +2 -1
  2. package/lib/block/processAttestationsAltair.d.ts.map +1 -1
  3. package/lib/block/processAttestationsAltair.js +5 -3
  4. package/lib/block/processAttestationsAltair.js.map +1 -1
  5. package/lib/signatureSets/index.d.ts +2 -2
  6. package/lib/signatureSets/index.d.ts.map +1 -1
  7. package/lib/signatureSets/index.js +1 -2
  8. package/lib/signatureSets/index.js.map +1 -1
  9. package/lib/slot/upgradeStateToAltair.d.ts.map +1 -1
  10. package/lib/slot/upgradeStateToAltair.js +2 -1
  11. package/lib/slot/upgradeStateToAltair.js.map +1 -1
  12. package/lib/stateView/beaconStateView.d.ts +6 -9
  13. package/lib/stateView/beaconStateView.d.ts.map +1 -1
  14. package/lib/stateView/beaconStateView.js +8 -32
  15. package/lib/stateView/beaconStateView.js.map +1 -1
  16. package/lib/stateView/index.d.ts +1 -0
  17. package/lib/stateView/index.d.ts.map +1 -1
  18. package/lib/stateView/index.js +1 -0
  19. package/lib/stateView/index.js.map +1 -1
  20. package/lib/stateView/interface.d.ts +12 -8
  21. package/lib/stateView/interface.d.ts.map +1 -1
  22. package/lib/stateView/stateViewFactory.d.ts +40 -0
  23. package/lib/stateView/stateViewFactory.d.ts.map +1 -0
  24. package/lib/stateView/stateViewFactory.js +46 -0
  25. package/lib/stateView/stateViewFactory.js.map +1 -0
  26. package/lib/testUtils/util.d.ts +13 -1
  27. package/lib/testUtils/util.d.ts.map +1 -1
  28. package/lib/testUtils/util.js +127 -22
  29. package/lib/testUtils/util.js.map +1 -1
  30. package/lib/util/rootCache.d.ts +2 -2
  31. package/lib/util/rootCache.d.ts.map +1 -1
  32. package/lib/util/rootCache.js +2 -3
  33. package/lib/util/rootCache.js.map +1 -1
  34. package/lib/util/shuffling.d.ts +2 -1
  35. package/lib/util/shuffling.d.ts.map +1 -1
  36. package/lib/util/shuffling.js +2 -2
  37. package/lib/util/shuffling.js.map +1 -1
  38. package/package.json +7 -7
  39. package/src/block/processAttestationsAltair.ts +7 -4
  40. package/src/signatureSets/index.ts +3 -4
  41. package/src/slot/upgradeStateToAltair.ts +2 -1
  42. package/src/stateView/beaconStateView.ts +16 -48
  43. package/src/stateView/index.ts +1 -0
  44. package/src/stateView/interface.ts +10 -5
  45. package/src/stateView/stateViewFactory.ts +78 -0
  46. package/src/testUtils/util.ts +144 -25
  47. package/src/util/rootCache.ts +4 -5
  48. package/src/util/shuffling.ts +5 -4
@@ -3,6 +3,7 @@ import {ForkSeq} from "@lodestar/params";
3
3
  import {ssz} from "@lodestar/types";
4
4
  import {getAttestationParticipationStatus} from "../block/processAttestationsAltair.js";
5
5
  import {getCachedBeaconState} from "../cache/stateCache.js";
6
+ import {BeaconStateView} from "../stateView/beaconStateView.js";
6
7
  import {CachedBeaconStateAltair, CachedBeaconStatePhase0} from "../types.js";
7
8
  import {RootCache, newZeroedArray} from "../util/index.js";
8
9
  import {getNextSyncCommittee} from "../util/syncCommittee.js";
@@ -125,7 +126,7 @@ function translateParticipation(
125
126
  pendingAttesations: CompositeViewDU<typeof ssz.phase0.EpochAttestations>
126
127
  ): void {
127
128
  const {epochCtx} = state;
128
- const rootCache = new RootCache(state);
129
+ const rootCache = new RootCache(new BeaconStateView(state));
129
130
  const epochParticipation = state.previousEpochParticipation;
130
131
 
131
132
  for (const attestation of pendingAttesations.getAllReadonly()) {
@@ -1,9 +1,10 @@
1
1
  import {CompactMultiProof, ProofType, Tree, createProof} from "@chainsafe/persistent-merkle-tree";
2
- import {ByteViews} from "@chainsafe/ssz";
2
+ import {BitArray, ByteViews} from "@chainsafe/ssz";
3
3
  import {BeaconConfig} from "@lodestar/config";
4
4
  import {ForkSeq, SLOTS_PER_HISTORICAL_ROOT, isForkPostGloas} from "@lodestar/params";
5
5
  import {
6
6
  BeaconBlock,
7
+ BeaconState,
7
8
  BlindedBeaconBlock,
8
9
  BuilderIndex,
9
10
  Bytes32,
@@ -33,7 +34,6 @@ import {VoluntaryExitValidity, getVoluntaryExitValidity} from "../block/processV
33
34
  import {getExpectedWithdrawals} from "../block/processWithdrawals.js";
34
35
  import {EffectiveBalanceIncrements} from "../cache/effectiveBalanceIncrements.js";
35
36
  import {EpochTransitionCacheOpts} from "../cache/epochTransitionCache.js";
36
- import {PubkeyCache, createPubkeyCache} from "../cache/pubkeyCache.js";
37
37
  import {RewardCache} from "../cache/rewardCache.js";
38
38
  import {
39
39
  CachedBeaconStateAllForks,
@@ -47,7 +47,6 @@ import {
47
47
  isStateValidatorsNodesPopulated,
48
48
  } from "../cache/stateCache.js";
49
49
  import {SyncCommitteeCache} from "../cache/syncCommitteeCache.js";
50
- import {BeaconStateAllForks} from "../cache/types.js";
51
50
  import {computeUnrealizedCheckpoints} from "../epoch/computeUnrealizedCheckpoints.js";
52
51
  import {getFinalizedRootProof, getSyncCommitteesWitness} from "../lightClient/proofs.js";
53
52
  import {SyncCommitteeWitness} from "../lightClient/types.js";
@@ -64,7 +63,6 @@ import {isExecutionEnabled, isExecutionStateType, isMergeTransitionComplete} fro
64
63
  import {canBuilderCoverBid} from "../util/gloas.js";
65
64
  import {loadState} from "../util/loadState/loadState.js";
66
65
  import {getRandaoMix} from "../util/seed.js";
67
- import {getStateTypeFromBytes} from "../util/sszBytes.js";
68
66
  import {getLatestWeakSubjectivityCheckpointEpoch} from "../util/weakSubjectivity.js";
69
67
  import {IBeaconStateView} from "./interface.js";
70
68
 
@@ -92,7 +90,7 @@ export class BeaconStateView implements IBeaconStateView {
92
90
  // fulu
93
91
  private _proposerLookahead: fulu.ProposerLookahead | null = null;
94
92
  // gloas
95
- private _executionPayloadAvailability: boolean[] | null = null;
93
+ private _executionPayloadAvailability: BitArray | null = null;
96
94
  private _latestExecutionPayloadBid: ExecutionPayloadBid | null = null;
97
95
 
98
96
  constructor(readonly cachedState: CachedBeaconStateAllForks) {
@@ -357,15 +355,15 @@ export class BeaconStateView implements IBeaconStateView {
357
355
 
358
356
  // gloas
359
357
 
360
- get executionPayloadAvailability(): boolean[] {
358
+ get executionPayloadAvailability(): BitArray {
361
359
  if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.gloas) {
362
360
  throw new Error("executionPayloadAvailability is not available before GLOAS");
363
361
  }
364
362
 
365
363
  if (this._executionPayloadAvailability === null) {
366
- this._executionPayloadAvailability = (this.cachedState as CachedBeaconStateGloas).executionPayloadAvailability
367
- .toValue()
368
- .toBoolArray();
364
+ this._executionPayloadAvailability = (
365
+ this.cachedState as CachedBeaconStateGloas
366
+ ).executionPayloadAvailability.toValue();
369
367
  }
370
368
 
371
369
  return this._executionPayloadAvailability;
@@ -404,7 +402,7 @@ export class BeaconStateView implements IBeaconStateView {
404
402
  * Return the index of the validator in the PTC committee for the given slot.
405
403
  * return -1 if validator is not in the PTC committee for the given slot.
406
404
  */
407
- validatorPTCCommitteeIndex(validatorIndex: ValidatorIndex, slot: Slot): number {
405
+ getIndexInPayloadTimelinessCommittee(validatorIndex: ValidatorIndex, slot: Slot): number {
408
406
  if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.gloas) {
409
407
  throw new Error("PTC committees are not supported before GLOAS");
410
408
  }
@@ -503,6 +501,10 @@ export class BeaconStateView implements IBeaconStateView {
503
501
  return this.cachedState.epochCtx.syncProposerReward;
504
502
  }
505
503
 
504
+ getIndexedSyncCommittee(slot: Slot): SyncCommitteeCache {
505
+ return this.cachedState.epochCtx.getIndexedSyncCommittee(slot);
506
+ }
507
+
506
508
  getIndexedSyncCommitteeAtEpoch(epoch: Epoch): SyncCommitteeCache {
507
509
  return this.cachedState.epochCtx.getIndexedSyncCommitteeAtEpoch(epoch);
508
510
  }
@@ -713,6 +715,10 @@ export class BeaconStateView implements IBeaconStateView {
713
715
  return new BeaconStateView(cachedState);
714
716
  }
715
717
 
718
+ toValue(): BeaconState {
719
+ return this.cachedState.toValue();
720
+ }
721
+
716
722
  serialize(): Uint8Array {
717
723
  return this.cachedState.serialize();
718
724
  }
@@ -778,41 +784,3 @@ export class BeaconStateView implements IBeaconStateView {
778
784
  return new BeaconStateView(postPayloadState);
779
785
  }
780
786
  }
781
-
782
- /**
783
- * Create BeaconStateView for historical state regen, no need to sync pubkey cache there.
784
- */
785
- export function createBeaconStateViewForHistoricalRegen(
786
- config: BeaconConfig,
787
- stateBytes: Uint8Array
788
- ): IBeaconStateView {
789
- const state = getStateTypeFromBytes(config, stateBytes).deserializeToViewDU(stateBytes);
790
-
791
- const pubkeyCache = createPubkeyCache();
792
- syncPubkeyCache(state, pubkeyCache);
793
- const cachedState = createCachedBeaconState(
794
- state,
795
- {
796
- config,
797
- pubkeyCache,
798
- },
799
- {
800
- skipSyncPubkeys: true,
801
- }
802
- );
803
-
804
- return new BeaconStateView(cachedState);
805
- }
806
-
807
- /**
808
- * Populate a PubkeyIndexMap with any new entries based on a BeaconState
809
- */
810
- function syncPubkeyCache(state: BeaconStateAllForks, pubkeyCache: PubkeyCache): void {
811
- // Get the validators sub tree once for all the loop
812
-
813
- const newCount = state.validators.length;
814
- for (let i = pubkeyCache.size; i < newCount; i++) {
815
- const pubkey = state.validators.getReadonly(i).pubkey;
816
- pubkeyCache.set(i, pubkey);
817
- }
818
- }
@@ -1,2 +1,3 @@
1
1
  export * from "./beaconStateView.js";
2
2
  export * from "./interface.js";
3
+ export * from "./stateViewFactory.js";
@@ -1,7 +1,8 @@
1
1
  import {CompactMultiProof} from "@chainsafe/persistent-merkle-tree";
2
- import {ByteViews} from "@chainsafe/ssz";
2
+ import {BitArray, ByteViews} from "@chainsafe/ssz";
3
3
  import {
4
4
  BeaconBlock,
5
+ BeaconState,
5
6
  BlindedBeaconBlock,
6
7
  BuilderIndex,
7
8
  Bytes32,
@@ -93,11 +94,11 @@ export interface IBeaconStateView {
93
94
  proposerLookahead: fulu.ProposerLookahead;
94
95
 
95
96
  // gloas
96
- executionPayloadAvailability: boolean[];
97
+ executionPayloadAvailability: BitArray;
97
98
  latestExecutionPayloadBid: ExecutionPayloadBid;
98
99
  getBuilder(index: BuilderIndex): gloas.Builder;
99
100
  canBuilderCoverBid(builderIndex: BuilderIndex, bidAmount: number): boolean;
100
- validatorPTCCommitteeIndex(validatorIndex: ValidatorIndex, slot: Slot): number;
101
+ getIndexInPayloadTimelinessCommittee(validatorIndex: ValidatorIndex, slot: Slot): number;
101
102
 
102
103
  // Shuffling and committees
103
104
  getShufflingAtEpoch(epoch: Epoch): EpochShuffling;
@@ -110,12 +111,11 @@ export interface IBeaconStateView {
110
111
  getCurrentShuffling(): EpochShuffling;
111
112
  getNextShuffling(): EpochShuffling;
112
113
 
113
- // utils: proposers, anchor checkpoint
114
+ // Proposer shuffling
114
115
  previousProposers: ValidatorIndex[] | null;
115
116
  currentProposers: ValidatorIndex[];
116
117
  nextProposers: ValidatorIndex[];
117
118
  getBeaconProposer(slot: Slot): ValidatorIndex;
118
- computeAnchorCheckpoint(): {checkpoint: phase0.Checkpoint; blockHeader: phase0.BeaconBlockHeader};
119
119
 
120
120
  // Sync committees
121
121
  currentSyncCommittee: altair.SyncCommittee;
@@ -123,6 +123,8 @@ export interface IBeaconStateView {
123
123
  currentSyncCommitteeIndexed: SyncCommitteeCache;
124
124
  syncProposerReward: number;
125
125
  getIndexedSyncCommitteeAtEpoch(epoch: Epoch): SyncCommitteeCache;
126
+ /** Get indexed sync committee with slot+1 offset for duty lookups */
127
+ getIndexedSyncCommittee(slot: Slot): SyncCommitteeCache;
126
128
 
127
129
  // Validators and balances
128
130
  effectiveBalanceIncrements: EffectiveBalanceIncrements;
@@ -149,6 +151,7 @@ export interface IBeaconStateView {
149
151
  expectedWithdrawals: capella.Withdrawal[];
150
152
  processedBuilderWithdrawalsCount: number;
151
153
  processedPartialWithdrawalsCount: number;
154
+ processedBuildersSweepCount: number;
152
155
  processedValidatorSweepCount: number;
153
156
  };
154
157
 
@@ -180,6 +183,7 @@ export interface IBeaconStateView {
180
183
  justifiedCheckpoint: phase0.Checkpoint;
181
184
  finalizedCheckpoint: phase0.Checkpoint;
182
185
  };
186
+ computeAnchorCheckpoint(): {checkpoint: phase0.Checkpoint; blockHeader: phase0.BeaconBlockHeader};
183
187
 
184
188
  // this is for backward compatible
185
189
  clonedCount: number;
@@ -190,6 +194,7 @@ export interface IBeaconStateView {
190
194
 
191
195
  // Serialization
192
196
  loadOtherState(stateBytes: Uint8Array, seedValidatorsBytes?: Uint8Array): IBeaconStateView;
197
+ toValue(): BeaconState;
193
198
  serialize(): Uint8Array;
194
199
  serializedSize(): number;
195
200
  serializeToBytes(output: ByteViews, offset: number): number;
@@ -0,0 +1,78 @@
1
+ import {BeaconConfig} from "@lodestar/config";
2
+ import {PubkeyCache, createPubkeyCache} from "../cache/pubkeyCache.js";
3
+ import {createCachedBeaconState} from "../cache/stateCache.js";
4
+ import {BeaconStateAllForks} from "../cache/types.js";
5
+ import {getStateTypeFromBytes} from "../util/sszBytes.js";
6
+ import {BeaconStateView} from "./beaconStateView.js";
7
+ import {IBeaconStateView} from "./interface.js";
8
+
9
+ // ---- createBeaconStateView (startup path) ----
10
+
11
+ type NodeJSOpts = {
12
+ useNative: false;
13
+ anchorState: BeaconStateAllForks;
14
+ config: BeaconConfig;
15
+ pubkeyCache: PubkeyCache;
16
+ };
17
+
18
+ type NativeOpts = {
19
+ useNative: true;
20
+ stateBytes: Uint8Array;
21
+ };
22
+
23
+ /**
24
+ * Create a BeaconStateView from a pre-deserialized state. Used at node startup.
25
+ *
26
+ * The caller is responsible for creating and populating `pubkeyCache` (it is also
27
+ * passed separately to BeaconNode.init, so it must live outside this factory).
28
+ *
29
+ * Set `useNative: true` to use the native (Zig) implementation once available.
30
+ */
31
+ export function createBeaconStateView(opts: NodeJSOpts | NativeOpts): IBeaconStateView {
32
+ if (opts.useNative) {
33
+ throw new Error("Native (Zig) BeaconStateView not yet implemented");
34
+ }
35
+ const {anchorState, config, pubkeyCache} = opts;
36
+ const cachedState = createCachedBeaconState(anchorState, {config, pubkeyCache}, {skipSyncPubkeys: true});
37
+ return new BeaconStateView(cachedState);
38
+ }
39
+
40
+ // ---- createBeaconStateViewForHistoricalRegen (regen path) ----
41
+
42
+ // Reused across all historical state regen calls in the worker thread
43
+ const pubkeyCacheRegen = createPubkeyCache();
44
+
45
+ function syncPubkeyCache(state: BeaconStateAllForks, pubkeyCache: PubkeyCache): void {
46
+ const newCount = state.validators.length;
47
+ for (let i = pubkeyCache.size; i < newCount; i++) {
48
+ const pubkey = state.validators.getReadonly(i).pubkey;
49
+ pubkeyCache.set(i, pubkey);
50
+ }
51
+ }
52
+
53
+ type RegenNodeJSOpts = {
54
+ useNative: false;
55
+ config: BeaconConfig;
56
+ stateBytes: Uint8Array;
57
+ };
58
+
59
+ type RegenNativeOpts = {
60
+ useNative: true;
61
+ stateBytes: Uint8Array;
62
+ };
63
+
64
+ /**
65
+ * Create a BeaconStateView from raw SSZ bytes. Used in the historical state regen worker thread.
66
+ *
67
+ * Set `useNative: true` to use the native (Zig) implementation once available.
68
+ */
69
+ export function createBeaconStateViewForHistoricalRegen(opts: RegenNodeJSOpts | RegenNativeOpts): IBeaconStateView {
70
+ if (opts.useNative) {
71
+ throw new Error("Native (Zig) BeaconStateView not yet implemented");
72
+ }
73
+ const {config, stateBytes} = opts;
74
+ const state = getStateTypeFromBytes(config, stateBytes).deserializeToViewDU(stateBytes);
75
+ syncPubkeyCache(state, pubkeyCacheRegen);
76
+ const cachedState = createCachedBeaconState(state, {config, pubkeyCache: pubkeyCacheRegen}, {skipSyncPubkeys: true});
77
+ return new BeaconStateView(cachedState);
78
+ }
@@ -25,9 +25,11 @@ import {
25
25
  } from "../index.js";
26
26
  import {
27
27
  BeaconStateAltair,
28
+ BeaconStateElectra,
28
29
  BeaconStatePhase0,
29
30
  CachedBeaconStateAllForks,
30
31
  CachedBeaconStateAltair,
32
+ CachedBeaconStateElectra,
31
33
  CachedBeaconStatePhase0,
32
34
  } from "../types.js";
33
35
  import {getNextSyncCommittee} from "../util/syncCommittee.js";
@@ -41,6 +43,9 @@ let phase0SignedBlock: phase0.SignedBeaconBlock | null = null;
41
43
  let altairState: BeaconStateAltair | null = null;
42
44
  let altairCachedState23637: CachedBeaconStateAltair | null = null;
43
45
  let altairCachedState23638: CachedBeaconStateAltair | null = null;
46
+ let electraState: BeaconStateElectra | null = null;
47
+ let electraCachedState23637: CachedBeaconStateElectra | null = null;
48
+ let electraCachedState23638: CachedBeaconStateElectra | null = null;
44
49
 
45
50
  /**
46
51
  * Number of validators in prater is 210000 as of May 2021
@@ -84,10 +89,10 @@ export function getSecretKeyFromIndexCached(validatorIndex: number): SecretKey {
84
89
  return sk;
85
90
  }
86
91
 
87
- function getPubkeyCaches({pubkeysMod}: ReturnType<typeof getPubkeys>) {
92
+ function getPubkeyCaches({pubkeysMod}: ReturnType<typeof getPubkeys>, vc = numValidators) {
88
93
  // Manually sync pubkeys to prevent doing BLS opts 110_000 times
89
94
  const pubkeyCache = createPubkeyCache();
90
- for (let i = 0; i < numValidators; i++) {
95
+ for (let i = 0; i < vc; i++) {
91
96
  const pubkey = pubkeysMod[i % keypairsMod];
92
97
  pubkeyCache.set(i, pubkey);
93
98
  }
@@ -184,7 +189,8 @@ export function generatePerfTestCachedStatePhase0(opts?: {goBackOneSlot: boolean
184
189
  }
185
190
  const resultingState = opts?.goBackOneSlot ? phase0CachedState23637 : phase0CachedState23638;
186
191
 
187
- return resultingState.clone();
192
+ // Use dontTransferCache to preserve the global cache for subsequent callers
193
+ return resultingState.clone(true);
188
194
  }
189
195
 
190
196
  export function cachedStateAltairPopulateCaches(state: CachedBeaconStateAltair): void {
@@ -206,39 +212,96 @@ export function generatePerfTestCachedStateAltair(opts?: {
206
212
  goBackOneSlot: boolean;
207
213
  vc?: number;
208
214
  }): CachedBeaconStateAltair {
209
- const {pubkeys, pubkeysMod, pubkeysModObj} = getPubkeys(opts?.vc);
210
- const {pubkeyCache} = getPubkeyCaches({pubkeys, pubkeysMod, pubkeysModObj});
215
+ const vc = opts?.vc ?? numValidators;
216
+ const {pubkeys, pubkeysMod, pubkeysModObj} = getPubkeys(vc);
217
+ const {pubkeyCache} = getPubkeyCaches({pubkeys, pubkeysMod, pubkeysModObj}, vc);
211
218
 
212
219
  const altairConfig = createChainForkConfig({ALTAIR_FORK_EPOCH: 0});
213
220
 
214
221
  const origState = generatePerformanceStateAltair(pubkeys);
215
222
 
216
- if (!altairCachedState23637) {
217
- const state = origState.clone();
223
+ // For non-default vc, generate fresh without caching to avoid accumulating large states in memory
224
+ const isDefaultVc = vc === numValidators;
225
+ let cachedState23637 = isDefaultVc ? altairCachedState23637 : null;
226
+ if (!cachedState23637) {
227
+ const state = origState.clone(true);
218
228
  state.slot -= 1;
219
- altairCachedState23637 = createCachedBeaconState(state, {
229
+ cachedState23637 = createCachedBeaconState(state, {
220
230
  config: createBeaconConfig(altairConfig, state.genesisValidatorsRoot),
221
231
  pubkeyCache,
222
232
  });
233
+ if (isDefaultVc) altairCachedState23637 = cachedState23637;
223
234
  }
224
- if (!altairCachedState23638) {
225
- altairCachedState23638 = processSlots(
226
- altairCachedState23637,
227
- altairCachedState23637.slot + 1
228
- ) as CachedBeaconStateAltair;
229
- altairCachedState23638.slot += 1;
235
+
236
+ let cachedState23638 = isDefaultVc ? altairCachedState23638 : null;
237
+ if (!cachedState23638) {
238
+ cachedState23638 = processSlots(cachedState23637, cachedState23637.slot + 1) as CachedBeaconStateAltair;
239
+ cachedState23638.slot += 1;
240
+ if (isDefaultVc) altairCachedState23638 = cachedState23638;
230
241
  }
231
- const resultingState = opts?.goBackOneSlot ? altairCachedState23637 : altairCachedState23638;
232
242
 
233
- return resultingState.clone();
243
+ const resultingState = opts?.goBackOneSlot ? cachedState23637 : cachedState23638;
244
+
245
+ // Use dontTransferCache to preserve the global cache for subsequent callers
246
+ return resultingState.clone(true);
247
+ }
248
+
249
+ /**
250
+ * Warning: This function has side effects on the cached state
251
+ * The order in which the caches are populated is important and can cause stable tests to fail.
252
+ */
253
+ export function generatePerfTestCachedStateElectra(opts?: {
254
+ goBackOneSlot: boolean;
255
+ vc?: number;
256
+ }): CachedBeaconStateElectra {
257
+ const vc = opts?.vc ?? numValidators;
258
+ const {pubkeys, pubkeysMod, pubkeysModObj} = getPubkeys(vc);
259
+ const {pubkeyCache} = getPubkeyCaches({pubkeys, pubkeysMod, pubkeysModObj}, vc);
260
+
261
+ const electraConfig = createChainForkConfig({
262
+ ALTAIR_FORK_EPOCH: 0,
263
+ BELLATRIX_FORK_EPOCH: 0,
264
+ CAPELLA_FORK_EPOCH: 0,
265
+ DENEB_FORK_EPOCH: 0,
266
+ ELECTRA_FORK_EPOCH: 0,
267
+ });
268
+
269
+ const origState = generatePerformanceStateElectra(pubkeys);
270
+
271
+ // For non-default vc, generate fresh without caching to avoid accumulating large states in memory
272
+ const isDefaultVc = vc === numValidators;
273
+ let cachedState23637 = isDefaultVc ? electraCachedState23637 : null;
274
+ if (!cachedState23637) {
275
+ const state = origState.clone(true);
276
+ state.slot -= 1;
277
+ cachedState23637 = createCachedBeaconState(state, {
278
+ config: createBeaconConfig(electraConfig, state.genesisValidatorsRoot),
279
+ pubkeyCache,
280
+ });
281
+ if (isDefaultVc) electraCachedState23637 = cachedState23637;
282
+ }
283
+ let cachedState23638 = isDefaultVc ? electraCachedState23638 : null;
284
+ if (!cachedState23638) {
285
+ cachedState23638 = processSlots(cachedState23637, cachedState23637.slot + 1) as CachedBeaconStateElectra;
286
+ cachedState23638.slot += 1;
287
+ if (isDefaultVc) electraCachedState23638 = cachedState23638;
288
+ }
289
+ const resultingState = opts?.goBackOneSlot ? cachedState23637 : cachedState23638;
290
+
291
+ // Use dontTransferCache to preserve the global cache for subsequent callers
292
+ return resultingState.clone(true);
234
293
  }
235
294
 
236
295
  /**
237
296
  * This is generated from Medalla state 756416
238
297
  */
239
298
  export function generatePerformanceStateAltair(pubkeysArg?: Uint8Array[]): BeaconStateAltair {
240
- if (!altairState) {
241
- const pubkeys = pubkeysArg || getPubkeys().pubkeys;
299
+ const pubkeys = pubkeysArg || getPubkeys().pubkeys;
300
+ const vc = pubkeys.length;
301
+ const isDefaultVc = vc === numValidators;
302
+ // Only use cached state for default vc to avoid accumulating large states in memory
303
+ let cached = isDefaultVc ? altairState : null;
304
+ if (!cached) {
242
305
  const statePhase0 = buildPerformanceStatePhase0(pubkeys);
243
306
  const state = statePhase0 as BeaconState as BeaconState<ForkName.altair>;
244
307
 
@@ -251,27 +314,83 @@ export function generatePerformanceStateAltair(pubkeysArg?: Uint8Array[]): Beaco
251
314
  state.nextSyncCommittee = state.currentSyncCommittee;
252
315
 
253
316
  // Now the state is fully populated to convert to ViewDU
254
- altairState = ssz.altair.BeaconState.toViewDU(state);
317
+ cached = ssz.altair.BeaconState.toViewDU(state);
255
318
 
256
319
  // Now set correct syncCommittees
257
320
  const epoch = computeEpochAtSlot(state.slot);
258
- const activeValidatorIndices = getActiveValidatorIndices(altairState, epoch);
321
+ const activeValidatorIndices = getActiveValidatorIndices(cached, epoch);
259
322
 
260
- const effectiveBalanceIncrements = getEffectiveBalanceIncrements(altairState);
323
+ const effectiveBalanceIncrements = getEffectiveBalanceIncrements(cached);
261
324
  const {syncCommittee} = getNextSyncCommittee(
262
325
  ForkSeq.altair,
263
- altairState,
326
+ cached,
264
327
  activeValidatorIndices,
265
328
  effectiveBalanceIncrements
266
329
  );
267
330
  state.currentSyncCommittee = syncCommittee;
268
331
  state.nextSyncCommittee = syncCommittee;
269
332
 
270
- altairState = ssz.altair.BeaconState.toViewDU(state);
333
+ cached = ssz.altair.BeaconState.toViewDU(state);
271
334
  // cache roots
272
- altairState.hashTreeRoot();
335
+ cached.hashTreeRoot();
336
+ if (isDefaultVc) altairState = cached;
337
+ }
338
+ // Use dontTransferCache to preserve the global cache for subsequent callers
339
+ return cached.clone(true);
340
+ }
341
+
342
+ /**
343
+ * This is generated from the same performance state as Altair, upgraded to Electra fields.
344
+ */
345
+ export function generatePerformanceStateElectra(pubkeysArg?: Uint8Array[]): BeaconStateElectra {
346
+ const pubkeys = pubkeysArg || getPubkeys().pubkeys;
347
+ const vc = pubkeys.length;
348
+ const isDefaultVc = vc === numValidators;
349
+ // Only use cached state for default vc to avoid accumulating large states in memory
350
+ let cached = isDefaultVc ? electraState : null;
351
+ if (!cached) {
352
+ const electraConfig = createChainForkConfig({
353
+ ALTAIR_FORK_EPOCH: 0,
354
+ BELLATRIX_FORK_EPOCH: 0,
355
+ CAPELLA_FORK_EPOCH: 0,
356
+ DENEB_FORK_EPOCH: 0,
357
+ ELECTRA_FORK_EPOCH: 0,
358
+ });
359
+ const state = ssz.electra.BeaconState.defaultValue();
360
+
361
+ Object.assign(state, buildPerformanceStatePhase0(pubkeys));
362
+
363
+ state.fork.previousVersion = electraConfig.DENEB_FORK_VERSION;
364
+ state.fork.currentVersion = electraConfig.ELECTRA_FORK_VERSION;
365
+ state.fork.epoch = electraConfig.ELECTRA_FORK_EPOCH;
366
+ state.previousEpochParticipation = newFilledArray(pubkeys.length, 0b111);
367
+ state.currentEpochParticipation = state.previousEpochParticipation;
368
+ state.inactivityScores = Array.from({length: pubkeys.length}, (_, i) => i % 2);
369
+ state.currentSyncCommittee = ssz.altair.SyncCommittee.defaultValue();
370
+ state.nextSyncCommittee = state.currentSyncCommittee;
371
+ state.latestExecutionPayloadHeader = ssz.electra.ExecutionPayloadHeader.defaultValue();
372
+ state.depositRequestsStartIndex = 2023n;
373
+
374
+ cached = ssz.electra.BeaconState.toViewDU(state);
375
+
376
+ const epoch = computeEpochAtSlot(state.slot);
377
+ const activeValidatorIndices = getActiveValidatorIndices(cached, epoch);
378
+ const effectiveBalanceIncrements = getEffectiveBalanceIncrements(cached);
379
+ const {syncCommittee} = getNextSyncCommittee(
380
+ ForkSeq.electra,
381
+ cached,
382
+ activeValidatorIndices,
383
+ effectiveBalanceIncrements
384
+ );
385
+ state.currentSyncCommittee = syncCommittee;
386
+ state.nextSyncCommittee = syncCommittee;
387
+
388
+ cached = ssz.electra.BeaconState.toViewDU(state);
389
+ cached.hashTreeRoot();
390
+ if (isDefaultVc) electraState = cached;
273
391
  }
274
- return altairState.clone();
392
+ // Use dontTransferCache to preserve the global cache for subsequent callers
393
+ return cached.clone(true);
275
394
  }
276
395
 
277
396
  /**
@@ -1,6 +1,5 @@
1
1
  import {Epoch, Root, Slot, phase0} from "@lodestar/types";
2
- import {CachedBeaconStateAllForks} from "../types.js";
3
- import {getBlockRoot, getBlockRootAtSlot} from "./blockRoot.js";
2
+ import {IBeaconStateView} from "../stateView/interface.js";
4
3
 
5
4
  /**
6
5
  * Cache to prevent accessing the state tree to fetch block roots repeteadly.
@@ -12,7 +11,7 @@ export class RootCache {
12
11
  private readonly blockRootEpochCache = new Map<Epoch, Root>();
13
12
  private readonly blockRootSlotCache = new Map<Slot, Root>();
14
13
 
15
- constructor(private readonly state: CachedBeaconStateAllForks) {
14
+ constructor(private readonly state: IBeaconStateView) {
16
15
  this.currentJustifiedCheckpoint = state.currentJustifiedCheckpoint;
17
16
  this.previousJustifiedCheckpoint = state.previousJustifiedCheckpoint;
18
17
  }
@@ -20,7 +19,7 @@ export class RootCache {
20
19
  getBlockRoot(epoch: Epoch): Root {
21
20
  let root = this.blockRootEpochCache.get(epoch);
22
21
  if (!root) {
23
- root = getBlockRoot(this.state, epoch);
22
+ root = this.state.getBlockRootAtEpoch(epoch);
24
23
  this.blockRootEpochCache.set(epoch, root);
25
24
  }
26
25
  return root;
@@ -29,7 +28,7 @@ export class RootCache {
29
28
  getBlockRootAtSlot(slot: Slot): Root {
30
29
  let root = this.blockRootSlotCache.get(slot);
31
30
  if (!root) {
32
- root = getBlockRootAtSlot(this.state, slot);
31
+ root = this.state.getBlockRootAtSlot(slot);
33
32
  this.blockRootSlotCache.set(slot, root);
34
33
  }
35
34
  return root;
@@ -11,6 +11,7 @@ import {
11
11
  } from "@lodestar/types";
12
12
  import {LodestarError} from "@lodestar/utils";
13
13
  import {CachedBeaconStateAllForks} from "../cache/stateCache.js";
14
+ import {IBeaconStateView} from "../stateView/interface.js";
14
15
  import {getBlockRootAtSlot} from "./blockRoot.js";
15
16
  import {computeStartSlotAtEpoch} from "./epoch.js";
16
17
  import {EpochShuffling} from "./epochShuffling.js";
@@ -22,21 +23,21 @@ import {EpochShuffling} from "./epochShuffling.js";
22
23
  * Returns `null` on the one-off scenario where the genesis block decides its own shuffling.
23
24
  * It should be set to the latest block applied to this `state` or the genesis block root.
24
25
  */
25
- export function proposerShufflingDecisionRoot(fork: ForkName, state: CachedBeaconStateAllForks): Root | null {
26
+ export function proposerShufflingDecisionRoot(fork: ForkName, state: IBeaconStateView): Root | null {
26
27
  const decisionSlot = proposerShufflingDecisionSlot(fork, state);
27
28
  if (state.slot === decisionSlot) {
28
29
  return null;
29
30
  }
30
- return getBlockRootAtSlot(state, decisionSlot);
31
+ return state.getBlockRootAtSlot(decisionSlot);
31
32
  }
32
33
 
33
34
  /**
34
35
  * Returns the slot at which the proposer shuffling was decided. The block root at this slot
35
36
  * can be used to key the proposer shuffling for the current epoch.
36
37
  */
37
- function proposerShufflingDecisionSlot(fork: ForkName, state: CachedBeaconStateAllForks): Slot {
38
+ function proposerShufflingDecisionSlot(fork: ForkName, state: IBeaconStateView): Slot {
38
39
  // After fulu, the decision slot is in previous epoch due to deterministic proposer lookahead
39
- const epoch = isForkPostFulu(fork) ? state.epochCtx.epoch - 1 : state.epochCtx.epoch;
40
+ const epoch = isForkPostFulu(fork) ? state.epoch - 1 : state.epoch;
40
41
  const startSlot = computeStartSlotAtEpoch(epoch);
41
42
  return Math.max(startSlot - 1, 0);
42
43
  }