@lodestar/state-transition 1.51.0-dev.fd649df985 → 1.51.0-dev.fe53cec080

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 (46) hide show
  1. package/lib/metrics.d.ts +1 -1
  2. package/lib/metrics.d.ts.map +1 -1
  3. package/lib/metrics.js +4 -4
  4. package/lib/metrics.js.map +1 -1
  5. package/lib/signatureSets/inclusionList.d.ts +5 -0
  6. package/lib/signatureSets/inclusionList.d.ts.map +1 -0
  7. package/lib/signatureSets/inclusionList.js +14 -0
  8. package/lib/signatureSets/inclusionList.js.map +1 -0
  9. package/lib/signatureSets/index.d.ts +1 -0
  10. package/lib/signatureSets/index.d.ts.map +1 -1
  11. package/lib/signatureSets/index.js +1 -0
  12. package/lib/signatureSets/index.js.map +1 -1
  13. package/lib/stateTransition.js +2 -2
  14. package/lib/stateTransition.js.map +1 -1
  15. package/lib/stateView/interface.d.ts +2 -4
  16. package/lib/stateView/interface.d.ts.map +1 -1
  17. package/lib/stateView/interface.js.map +1 -1
  18. package/lib/stateView/nativeBeaconStateView.d.ts +14 -6
  19. package/lib/stateView/nativeBeaconStateView.d.ts.map +1 -1
  20. package/lib/stateView/nativeBeaconStateView.js +85 -25
  21. package/lib/stateView/nativeBeaconStateView.js.map +1 -1
  22. package/lib/stateView/stateViewFactory.js +3 -2
  23. package/lib/stateView/stateViewFactory.js.map +1 -1
  24. package/lib/util/inclusionListCommittee.d.ts +9 -0
  25. package/lib/util/inclusionListCommittee.d.ts.map +1 -0
  26. package/lib/util/inclusionListCommittee.js +18 -0
  27. package/lib/util/inclusionListCommittee.js.map +1 -0
  28. package/lib/util/index.d.ts +1 -0
  29. package/lib/util/index.d.ts.map +1 -1
  30. package/lib/util/index.js +1 -0
  31. package/lib/util/index.js.map +1 -1
  32. package/lib/util/shuffling.d.ts +3 -0
  33. package/lib/util/shuffling.d.ts.map +1 -1
  34. package/lib/util/shuffling.js +41 -1
  35. package/lib/util/shuffling.js.map +1 -1
  36. package/package.json +8 -8
  37. package/src/metrics.ts +5 -4
  38. package/src/signatureSets/inclusionList.ts +20 -0
  39. package/src/signatureSets/index.ts +1 -0
  40. package/src/stateTransition.ts +2 -2
  41. package/src/stateView/interface.ts +1 -10
  42. package/src/stateView/nativeBeaconStateView.ts +86 -29
  43. package/src/stateView/stateViewFactory.ts +3 -2
  44. package/src/util/inclusionListCommittee.ts +17 -0
  45. package/src/util/index.ts +1 -0
  46. package/src/util/shuffling.ts +46 -1
@@ -1,7 +1,7 @@
1
1
  import {CompactMultiProof} from "@chainsafe/persistent-merkle-tree";
2
2
  import {BitArray, ByteViews} from "@chainsafe/ssz";
3
3
  import type {BeaconConfig} from "@lodestar/config";
4
- import {ForkName, ForkSeq} from "@lodestar/params";
4
+ import {ForkName, ForkPostElectra, ForkSeq} from "@lodestar/params";
5
5
  import {
6
6
  BeaconBlock,
7
7
  BeaconState,
@@ -28,6 +28,7 @@ import {
28
28
  phase0,
29
29
  rewards,
30
30
  ssz,
31
+ sszTypesFor,
31
32
  } from "@lodestar/types";
32
33
  import {Checkpoint, Fork} from "@lodestar/types/phase0";
33
34
  import {VoluntaryExitValidity} from "../block/processVoluntaryExit.js";
@@ -39,6 +40,7 @@ import {SyncCommitteeWitness} from "../lightClient/types.js";
39
40
  import {StateTransitionModules, StateTransitionOpts} from "../stateTransition.js";
40
41
  import {EpochShuffling} from "../util/epochShuffling.js";
41
42
  import {PreVerifyBuilderDepositsResult} from "../util/preVerifyBuilderDeposits.js";
43
+ import {getStateSlotFromBytes} from "../util/sszBytes.js";
42
44
  import {computeNewStateRootStateTransitionOpts, getComputeNewStateRootResult} from "./computeNewStateRoot.js";
43
45
  import {
44
46
  BlockSTFInput,
@@ -47,8 +49,15 @@ import {
47
49
  IBeaconStateViewGloas,
48
50
  IBeaconStateViewLatestFork,
49
51
  IBeaconStateViewNative,
52
+ isStatePostGloas,
50
53
  } from "./interface.js";
51
54
 
55
+ export function assertNativeForkSupported(config: BeaconConfig, slot: Slot): void {
56
+ if (config.getForkSeq(slot) > ForkSeq.gloas) {
57
+ throw Error(`Native state transition does not support ${config.getForkName(slot)}`);
58
+ }
59
+ }
60
+
52
61
  /**
53
62
  * Wraps a native binding (the auto-generated JS interface produced by a `.node`
54
63
  * file) and exposes it as a fully-conformant `IBeaconStateViewLatestFork`.
@@ -115,6 +124,14 @@ export class NativeBeaconStateView implements IBeaconStateViewLatestFork {
115
124
  private cachedPendingConsolidationsCount: number | null = null;
116
125
  // fulu
117
126
  private cachedProposerLookahead: fulu.ProposerLookahead | null = null;
127
+ // gloas
128
+ private cachedLatestBlockHash: Bytes32 | null = null;
129
+ private cachedExecutionPayloadAvailability: BitArray | null = null;
130
+ private cachedLatestExecutionPayloadBid: ExecutionPayloadBid | null = null;
131
+ private cachedPayloadExpectedWithdrawals: capella.Withdrawal[] | null = null;
132
+ private cachedBuilderPendingPayments: gloas.BuilderPendingPayments | null = null;
133
+ private cachedBuilderPendingWithdrawals: gloas.BuilderPendingWithdrawals | null = null;
134
+ private cachedBuildersLength: number | null = null;
118
135
  // Per-argument caches for argument-taking methods. The binding is treated as
119
136
  // immutable for the view's lifetime, so a given argument always yields the
120
137
  // same result. Maps grow only with touched arguments — typical call patterns
@@ -174,7 +191,11 @@ export class NativeBeaconStateView implements IBeaconStateViewLatestFork {
174
191
  }
175
192
 
176
193
  get executionPayloadAvailability(): BitArray {
177
- throw new Error("NativeBeaconStateView does not support Gloas");
194
+ if (this.cachedExecutionPayloadAvailability === null) {
195
+ const {uint8Array, bitLen} = this.binding.executionPayloadAvailability;
196
+ this.cachedExecutionPayloadAvailability = new BitArray(uint8Array, bitLen);
197
+ }
198
+ return this.cachedExecutionPayloadAvailability;
178
199
  }
179
200
 
180
201
  // ─── phase0 ──────────────────────────────────────────────────────────────
@@ -598,6 +619,7 @@ export class NativeBeaconStateView implements IBeaconStateViewLatestFork {
598
619
  seedValidatorsBytes?: Uint8Array,
599
620
  opts?: {preloadValidatorsAndBalances?: boolean}
600
621
  ): IBeaconStateView {
622
+ assertNativeForkSupported(this.config, getStateSlotFromBytes(stateBytes));
601
623
  return new NativeBeaconStateView(this.config, this.binding.loadOtherState(stateBytes, seedValidatorsBytes, opts));
602
624
  }
603
625
 
@@ -663,6 +685,7 @@ export class NativeBeaconStateView implements IBeaconStateViewLatestFork {
663
685
  options: StateTransitionOpts,
664
686
  _modules: StateTransitionModules
665
687
  ): IBeaconStateView {
688
+ assertNativeForkSupported(this.config, block.message.slot);
666
689
  const isBlinded = isBlindedBeaconBlock(block.message);
667
690
  const signedBlockBytes =
668
691
  ssz ??
@@ -675,6 +698,7 @@ export class NativeBeaconStateView implements IBeaconStateViewLatestFork {
675
698
  }
676
699
 
677
700
  processSlots(slot: Slot, opts?: {dontTransferCache?: boolean}, _modules?: StateTransitionModules): IBeaconStateView {
701
+ assertNativeForkSupported(this.config, slot);
678
702
  return new NativeBeaconStateView(this.config, this.binding.processSlots(slot, opts));
679
703
  }
680
704
 
@@ -832,7 +856,8 @@ export class NativeBeaconStateView implements IBeaconStateViewLatestFork {
832
856
 
833
857
  get pendingDeposits(): electra.PendingDeposits {
834
858
  if (this.cachedPendingDeposits === null) {
835
- this.cachedPendingDeposits = ssz.electra.PendingDeposits.deserialize(this.binding.pendingDeposits);
859
+ const type = sszTypesFor(this.forkName as ForkPostElectra).PendingDeposits;
860
+ this.cachedPendingDeposits = type.deserialize(this.binding.pendingDeposits);
836
861
  }
837
862
  return this.cachedPendingDeposits;
838
863
  }
@@ -846,9 +871,8 @@ export class NativeBeaconStateView implements IBeaconStateViewLatestFork {
846
871
 
847
872
  get pendingPartialWithdrawals(): electra.PendingPartialWithdrawals {
848
873
  if (this.cachedPendingPartialWithdrawals === null) {
849
- this.cachedPendingPartialWithdrawals = ssz.electra.PendingPartialWithdrawals.deserialize(
850
- this.binding.pendingPartialWithdrawals
851
- );
874
+ const type = sszTypesFor(this.forkName as ForkPostElectra).PendingPartialWithdrawals;
875
+ this.cachedPendingPartialWithdrawals = type.deserialize(this.binding.pendingPartialWithdrawals);
852
876
  }
853
877
  return this.cachedPendingPartialWithdrawals;
854
878
  }
@@ -862,9 +886,8 @@ export class NativeBeaconStateView implements IBeaconStateViewLatestFork {
862
886
 
863
887
  get pendingConsolidations(): electra.PendingConsolidations {
864
888
  if (this.cachedPendingConsolidations === null) {
865
- this.cachedPendingConsolidations = ssz.electra.PendingConsolidations.deserialize(
866
- this.binding.pendingConsolidations
867
- );
889
+ const type = sszTypesFor(this.forkName as ForkPostElectra).PendingConsolidations;
890
+ this.cachedPendingConsolidations = type.deserialize(this.binding.pendingConsolidations);
868
891
  }
869
892
  return this.cachedPendingConsolidations;
870
893
  }
@@ -889,60 +912,94 @@ export class NativeBeaconStateView implements IBeaconStateViewLatestFork {
889
912
  _maxBuilderDeposits: number,
890
913
  _maxDurationMs: number
891
914
  ): PreVerifyBuilderDepositsResult {
892
- throw new Error("NativeBeaconStateView does not support Gloas");
915
+ // The native fork upgrade verifies directly; it does not maintain a warm-up cache.
916
+ return {
917
+ validBuilderSignaturesCount: 0,
918
+ invalidBuilderSignaturesCount: 0,
919
+ validValidatorSignaturesCount: 0,
920
+ invalidValidatorSignaturesCount: 0,
921
+ scannedPendingDeposits: 0,
922
+ totalCachedDeposits: 0,
923
+ totalBuilderPubkeys: 0,
924
+ pendingDepositsCount: this.pendingDepositsCount,
925
+ };
893
926
  }
894
927
 
895
928
  clearPreGloasBuilderDepositCache(): void {
896
- throw new Error("NativeBeaconStateView does not support Gloas");
929
+ // The native implementation has no pre-Gloas deposit cache to clear.
897
930
  }
898
931
 
899
932
  // ─── gloas ───────────────────────────────────────────────────────────────
900
933
 
901
934
  get latestBlockHash(): Bytes32 {
902
- throw new Error("NativeBeaconStateView does not support Gloas");
935
+ if (this.cachedLatestBlockHash === null) {
936
+ this.cachedLatestBlockHash = this.binding.latestBlockHash;
937
+ }
938
+ return this.cachedLatestBlockHash;
903
939
  }
904
940
 
905
941
  get latestExecutionPayloadBid(): ExecutionPayloadBid {
906
- throw new Error("NativeBeaconStateView does not support Gloas");
942
+ if (this.cachedLatestExecutionPayloadBid === null) {
943
+ this.cachedLatestExecutionPayloadBid = this.binding.latestExecutionPayloadBid;
944
+ }
945
+ return this.cachedLatestExecutionPayloadBid;
907
946
  }
908
947
 
909
948
  get payloadExpectedWithdrawals(): capella.Withdrawal[] {
910
- throw new Error("NativeBeaconStateView does not support Gloas");
949
+ if (this.cachedPayloadExpectedWithdrawals === null) {
950
+ this.cachedPayloadExpectedWithdrawals = this.binding.payloadExpectedWithdrawals;
951
+ }
952
+ return this.cachedPayloadExpectedWithdrawals;
911
953
  }
912
954
 
913
955
  get builderPendingPayments(): gloas.BuilderPendingPayments {
914
- throw new Error("NativeBeaconStateView does not support Gloas");
956
+ if (this.cachedBuilderPendingPayments === null) {
957
+ this.cachedBuilderPendingPayments = this.binding.builderPendingPayments;
958
+ }
959
+ return this.cachedBuilderPendingPayments;
915
960
  }
916
961
 
917
962
  get builderPendingWithdrawals(): gloas.BuilderPendingWithdrawals {
918
- throw new Error("NativeBeaconStateView does not support Gloas");
963
+ if (this.cachedBuilderPendingWithdrawals === null) {
964
+ this.cachedBuilderPendingWithdrawals = this.binding.builderPendingWithdrawals;
965
+ }
966
+ return this.cachedBuilderPendingWithdrawals;
919
967
  }
920
968
 
921
- getBuilder(_index: BuilderIndex): gloas.Builder {
922
- throw new Error("NativeBeaconStateView does not support Gloas");
969
+ getBuilder(index: BuilderIndex): gloas.Builder {
970
+ return this.binding.getBuilder(index);
923
971
  }
924
972
 
925
973
  getBuildersLength(): number {
926
- throw new Error("NativeBeaconStateView does not support Gloas");
974
+ if (this.cachedBuildersLength === null) {
975
+ this.cachedBuildersLength = this.binding.getBuildersLength();
976
+ }
977
+ return this.cachedBuildersLength;
927
978
  }
928
979
 
929
- canBuilderCoverBid(_builderIndex: BuilderIndex, _bidAmount: number): boolean {
930
- throw new Error("NativeBeaconStateView does not support Gloas");
980
+ canBuilderCoverBid(builderIndex: BuilderIndex, bidAmount: number): boolean {
981
+ return this.binding.canBuilderCoverBid(builderIndex, bidAmount);
931
982
  }
932
983
 
933
- getEpochPTCs(_epoch: Epoch): Uint32Array[] {
934
- throw new Error("NativeBeaconStateView does not support Gloas");
984
+ getEpochPTCs(epoch: Epoch): Uint32Array[] {
985
+ return this.binding.getEpochPTCs(epoch);
935
986
  }
936
987
 
937
- getPayloadTimelinessCommittee(_slot: Slot): Uint32Array {
938
- throw new Error("NativeBeaconStateView does not support Gloas");
988
+ getPayloadTimelinessCommittee(slot: Slot): Uint32Array {
989
+ return this.binding.getPayloadTimelinessCommittee(slot);
939
990
  }
940
991
 
941
- getIndicesInPayloadTimelinessCommittee(_validatorIndex: ValidatorIndex, _slot: Slot): number[] {
942
- throw new Error("NativeBeaconStateView does not support Gloas");
992
+ getIndicesInPayloadTimelinessCommittee(validatorIndex: ValidatorIndex, slot: Slot): number[] {
993
+ return this.binding.getIndicesInPayloadTimelinessCommittee(validatorIndex, slot);
943
994
  }
944
995
 
945
- withParentPayloadApplied(_executionRequests: gloas.ExecutionRequests): IBeaconStateViewGloas {
946
- throw new Error("NativeBeaconStateView does not support Gloas");
996
+ withParentPayloadApplied(executionRequests: gloas.ExecutionRequests): IBeaconStateViewGloas {
997
+ const bytes = ssz.gloas.ExecutionRequests.serialize(executionRequests);
998
+ const state = new NativeBeaconStateView(this.config, this.binding.withParentPayloadApplied(bytes));
999
+ if (!isStatePostGloas(state)) {
1000
+ state.release();
1001
+ throw Error("Expected Gloas state after applying the parent payload");
1002
+ }
1003
+ return state;
947
1004
  }
948
1005
  }
@@ -2,10 +2,10 @@ import bindings from "@chainsafe/lodestar-z";
2
2
  import {pubkeyCache} from "@chainsafe/lodestar-z/pubkeys";
3
3
  import {BeaconConfig} from "@lodestar/config";
4
4
  import {createCachedBeaconState} from "../cache/stateCache.js";
5
- import {getStateTypeFromBytes} from "../util/sszBytes.js";
5
+ import {getStateSlotFromBytes, getStateTypeFromBytes} from "../util/sszBytes.js";
6
6
  import {BeaconStateView} from "./beaconStateView.js";
7
7
  import {IBeaconStateView, IBeaconStateViewNative} from "./interface.js";
8
- import {NativeBeaconStateView} from "./nativeBeaconStateView.js";
8
+ import {NativeBeaconStateView, assertNativeForkSupported} from "./nativeBeaconStateView.js";
9
9
 
10
10
  // ---- createBeaconStateView (startup path) ----
11
11
 
@@ -62,6 +62,7 @@ export function createBeaconStateViewForHistoricalRegen(opts: RegenNodeJSOpts |
62
62
  }
63
63
 
64
64
  function createNativeBeaconStateView(config: BeaconConfig, stateBytes: Uint8Array): IBeaconStateView {
65
+ assertNativeForkSupported(config, getStateSlotFromBytes(stateBytes));
65
66
  const nativeConfig = new bindings.BeaconConfig(config, config.genesisValidatorsRoot);
66
67
  return new NativeBeaconStateView(
67
68
  config,
@@ -0,0 +1,17 @@
1
+ import {ValidatorIndex} from "@lodestar/types";
2
+
3
+ /** Spec `InclusionListCommittee` of one slot */
4
+ export class InclusionListCommittee {
5
+ constructor(readonly validatorIndices: Uint32Array) {}
6
+
7
+ /** Positions of `validatorIndex`, several when the committee cycles over a slot with fewer validators than its size */
8
+ getCommitteeIndices(validatorIndex: ValidatorIndex): number[] {
9
+ const committeeIndices: number[] = [];
10
+ for (let i = 0; i < this.validatorIndices.length; i++) {
11
+ if (this.validatorIndices[i] === validatorIndex) {
12
+ committeeIndices.push(i);
13
+ }
14
+ }
15
+ return committeeIndices;
16
+ }
17
+ }
package/src/util/index.ts CHANGED
@@ -17,6 +17,7 @@ export * from "./execution.js";
17
17
  export * from "./finality.js";
18
18
  export * from "./genesis.js";
19
19
  export * from "./gloas.js";
20
+ export * from "./inclusionListCommittee.js";
20
21
  export * from "./interop.js";
21
22
  export * from "./loadState/index.js";
22
23
  export * from "./pendingDepositsLookup.js";
@@ -1,4 +1,4 @@
1
- import {ForkName, ForkSeq, SLOTS_PER_EPOCH, isForkPostFulu} from "@lodestar/params";
1
+ import {ForkName, ForkSeq, INCLUSION_LIST_COMMITTEE_SIZE, SLOTS_PER_EPOCH, isForkPostFulu} from "@lodestar/params";
2
2
  import {
3
3
  Attestation,
4
4
  CommitteeIndex,
@@ -15,6 +15,7 @@ import {IBeaconStateView} from "../stateView/interface.js";
15
15
  import {getBlockRootAtSlot} from "./blockRoot.js";
16
16
  import {computeStartSlotAtEpoch} from "./epoch.js";
17
17
  import {EpochShuffling} from "./epochShuffling.js";
18
+ import {InclusionListCommittee} from "./inclusionListCommittee.js";
18
19
 
19
20
  /**
20
21
  * Block root that decided the proposer shuffling for `proposalEpoch` (keys that shuffling).
@@ -196,6 +197,50 @@ export function getBeaconCommittee(epochShuffling: EpochShuffling, slot: Slot, i
196
197
  return getBeaconCommittees(epochShuffling, slot, [index])[0];
197
198
  }
198
199
 
200
+ /** Inclusion list committees of a shuffling by slot index, computed on first use and freed with the shuffling */
201
+ // TODO HEZE: replace this module-level map with an extended EpochShuffling tracked by the beacon-node ShufflingCache
202
+ const inclusionListCommitteesByShuffling = new WeakMap<EpochShuffling, InclusionListCommittee[]>();
203
+
204
+ /** Spec get_inclusion_list_committee: cycle over the slot's concatenated beacon committees */
205
+ export function getInclusionListCommittee(epochShuffling: EpochShuffling, slot: Slot): InclusionListCommittee {
206
+ let committees = inclusionListCommitteesByShuffling.get(epochShuffling);
207
+ if (committees === undefined) {
208
+ committees = [];
209
+ inclusionListCommitteesByShuffling.set(epochShuffling, committees);
210
+ }
211
+ const slotIndex = slot % SLOTS_PER_EPOCH;
212
+ let committee = committees[slotIndex];
213
+ if (committee === undefined) {
214
+ committee = new InclusionListCommittee(computeInclusionListCommittee(epochShuffling.committees[slotIndex]));
215
+ committees[slotIndex] = committee;
216
+ }
217
+ return committee;
218
+ }
219
+
220
+ function computeInclusionListCommittee(slotCommittees: Uint32Array[]): Uint32Array {
221
+ let totalLen = 0;
222
+ for (const committee of slotCommittees) {
223
+ totalLen += committee.length;
224
+ }
225
+
226
+ const result = new Uint32Array(INCLUSION_LIST_COMMITTEE_SIZE);
227
+ if (totalLen === 0) {
228
+ return result;
229
+ }
230
+
231
+ for (let i = 0; i < INCLUSION_LIST_COMMITTEE_SIZE; i++) {
232
+ let offset = i % totalLen;
233
+ for (const committee of slotCommittees) {
234
+ if (offset < committee.length) {
235
+ result[i] = committee[offset];
236
+ break;
237
+ }
238
+ offset -= committee.length;
239
+ }
240
+ }
241
+ return result;
242
+ }
243
+
199
244
  /**
200
245
  * Return a Uint32Array[] representing committees validator indices
201
246
  */