@lodestar/state-transition 1.42.0 → 1.43.0-dev.aef3645690

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/processDepositRequest.d.ts +11 -2
  2. package/lib/block/processDepositRequest.d.ts.map +1 -1
  3. package/lib/block/processDepositRequest.js +34 -4
  4. package/lib/block/processDepositRequest.js.map +1 -1
  5. package/lib/block/processExecutionPayloadEnvelope.d.ts.map +1 -1
  6. package/lib/block/processExecutionPayloadEnvelope.js +7 -3
  7. package/lib/block/processExecutionPayloadEnvelope.js.map +1 -1
  8. package/lib/index.d.ts +4 -2
  9. package/lib/index.d.ts.map +1 -1
  10. package/lib/index.js +4 -1
  11. package/lib/index.js.map +1 -1
  12. package/lib/signatureSets/executionPayloadEnvelope.d.ts.map +1 -1
  13. package/lib/signatureSets/executionPayloadEnvelope.js +4 -0
  14. package/lib/signatureSets/executionPayloadEnvelope.js.map +1 -1
  15. package/lib/signatureSets/voluntaryExits.d.ts +2 -2
  16. package/lib/signatureSets/voluntaryExits.d.ts.map +1 -1
  17. package/lib/signatureSets/voluntaryExits.js +4 -0
  18. package/lib/signatureSets/voluntaryExits.js.map +1 -1
  19. package/lib/stateTransition.d.ts +1 -1
  20. package/lib/stateTransition.d.ts.map +1 -1
  21. package/lib/stateTransition.js +1 -1
  22. package/lib/stateTransition.js.map +1 -1
  23. package/lib/stateView/beaconStateView.d.ts +6 -2
  24. package/lib/stateView/beaconStateView.d.ts.map +1 -1
  25. package/lib/stateView/beaconStateView.js +18 -5
  26. package/lib/stateView/beaconStateView.js.map +1 -1
  27. package/lib/stateView/interface.d.ts +98 -51
  28. package/lib/stateView/interface.d.ts.map +1 -1
  29. package/lib/stateView/interface.js +22 -1
  30. package/lib/stateView/interface.js.map +1 -1
  31. package/lib/util/attestation.d.ts +12 -1
  32. package/lib/util/attestation.d.ts.map +1 -1
  33. package/lib/util/attestation.js +23 -8
  34. package/lib/util/attestation.js.map +1 -1
  35. package/lib/util/gloas.d.ts +2 -1
  36. package/lib/util/gloas.d.ts.map +1 -1
  37. package/lib/util/gloas.js.map +1 -1
  38. package/package.json +7 -7
  39. package/src/block/processDepositRequest.ts +50 -5
  40. package/src/block/processExecutionPayloadEnvelope.ts +8 -3
  41. package/src/index.ts +20 -2
  42. package/src/signatureSets/executionPayloadEnvelope.ts +5 -1
  43. package/src/signatureSets/voluntaryExits.ts +5 -2
  44. package/src/stateTransition.ts +1 -1
  45. package/src/stateView/beaconStateView.ts +26 -8
  46. package/src/stateView/interface.ts +143 -73
  47. package/src/util/attestation.ts +37 -8
  48. package/src/util/gloas.ts +2 -1
@@ -1,5 +1,7 @@
1
+ import {BeaconConfig} from "@lodestar/config";
1
2
  import {FAR_FUTURE_EPOCH, ForkSeq, UNSET_DEPOSIT_REQUESTS_START_INDEX} from "@lodestar/params";
2
- import {BLSPubkey, Bytes32, UintNum64, electra, ssz} from "@lodestar/types";
3
+ import {BLSPubkey, Bytes32, PubkeyHex, UintNum64, electra, ssz} from "@lodestar/types";
4
+ import {toPubkeyHex} from "@lodestar/utils";
3
5
  import {CachedBeaconStateElectra, CachedBeaconStateGloas} from "../types.js";
4
6
  import {findBuilderIndexByPubkey, isBuilderWithdrawalCredential} from "../util/gloas.js";
5
7
  import {computeEpochAtSlot, isValidatorKnown} from "../util/index.js";
@@ -74,16 +76,24 @@ function addBuilderToRegistry(
74
76
  }
75
77
  }
76
78
 
79
+ // TODO GLOAS: pendingValidatorPubkeys cache is currently naive and has room for improvement.
80
+ // Currently the cache lives in process_block, but we should put it in epochCache or elsewhere that has longer
81
+ // lifetime to avoid duplicated deposit signature computation
82
+ // See https://github.com/ChainSafe/lodestar/issues/9181
77
83
  export function processDepositRequest(
78
84
  fork: ForkSeq,
79
85
  state: CachedBeaconStateElectra | CachedBeaconStateGloas,
80
- depositRequest: electra.DepositRequest
86
+ depositRequest: electra.DepositRequest,
87
+ pendingValidatorPubkeysCache?: Set<PubkeyHex>
81
88
  ): void {
82
89
  const {pubkey, withdrawalCredentials, amount, signature} = depositRequest;
83
90
 
84
91
  // Check if this is a builder or validator deposit
85
92
  if (fork >= ForkSeq.gloas) {
86
93
  const stateGloas = state as CachedBeaconStateGloas;
94
+ const pendingValidatorPubkeys =
95
+ pendingValidatorPubkeysCache ?? getPendingValidatorPubkeys(state.config, stateGloas);
96
+ const pubkeyHex = toPubkeyHex(pubkey);
87
97
  const builderIndex = findBuilderIndexByPubkey(stateGloas, pubkey);
88
98
  const validatorIndex = state.epochCtx.getValidatorIndex(pubkey);
89
99
 
@@ -91,14 +101,24 @@ export function processDepositRequest(
91
101
  // already exists with this pubkey, apply the deposit to their balance
92
102
  const isBuilder = builderIndex !== null;
93
103
  const isValidator = isValidatorKnown(state, validatorIndex);
94
- const isBuilderPrefix = isBuilderWithdrawalCredential(withdrawalCredentials);
104
+ const isPendingValidator = pendingValidatorPubkeys.has(pubkeyHex);
95
105
 
96
- // Route to builder if it's an existing builder OR has builder prefix and is not a validator
97
- if (isBuilder || (isBuilderPrefix && !isValidator)) {
106
+ if (isBuilder || (isBuilderWithdrawalCredential(withdrawalCredentials) && !isValidator && !isPendingValidator)) {
98
107
  // Apply builder deposits immediately
99
108
  applyDepositForBuilder(stateGloas, pubkey, withdrawalCredentials, amount, signature, state.slot);
100
109
  return;
101
110
  }
111
+
112
+ // Keep the shared cache in sync: if this deposit has a valid signature, subsequent
113
+ // deposit requests for the same pubkey in this envelope must see it as a pending validator
114
+ if (
115
+ pendingValidatorPubkeysCache &&
116
+ !isValidator &&
117
+ !isPendingValidator &&
118
+ isValidDepositSignature(state.config, pubkey, withdrawalCredentials, amount, signature)
119
+ ) {
120
+ pendingValidatorPubkeys.add(pubkeyHex);
121
+ }
102
122
  }
103
123
 
104
124
  // Only set deposit_requests_start_index in Electra fork, not Gloas
@@ -116,3 +136,28 @@ export function processDepositRequest(
116
136
  });
117
137
  state.pendingDeposits.push(pendingDeposit);
118
138
  }
139
+
140
+ /**
141
+ * Build a set of pubkeys (hex-encoded) from pending deposits that have valid signatures.
142
+ * This is computed once and passed to each processDepositRequest call to avoid
143
+ * repeatedly iterating state.pendingDeposits.
144
+ *
145
+ * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.3/specs/gloas/beacon-chain.md#new-is_pending_validator
146
+ */
147
+ export function getPendingValidatorPubkeys(config: BeaconConfig, state: CachedBeaconStateGloas): Set<PubkeyHex> {
148
+ const result = new Set<PubkeyHex>();
149
+ for (const pendingDeposit of state.pendingDeposits.getAllReadonly()) {
150
+ if (
151
+ isValidDepositSignature(
152
+ config,
153
+ pendingDeposit.pubkey,
154
+ pendingDeposit.withdrawalCredentials,
155
+ pendingDeposit.amount,
156
+ pendingDeposit.signature
157
+ )
158
+ ) {
159
+ result.add(toPubkeyHex(pendingDeposit.pubkey));
160
+ }
161
+ }
162
+ return result;
163
+ }
@@ -7,7 +7,7 @@ import {CachedBeaconStateGloas} from "../types.js";
7
7
  import {computeTimeAtSlot} from "../util/index.js";
8
8
  import {verifySignatureSet} from "../util/signatureSets.js";
9
9
  import {processConsolidationRequest} from "./processConsolidationRequest.js";
10
- import {processDepositRequest} from "./processDepositRequest.js";
10
+ import {getPendingValidatorPubkeys, processDepositRequest} from "./processDepositRequest.js";
11
11
  import {processWithdrawalRequest} from "./processWithdrawalRequest.js";
12
12
 
13
13
  export type ProcessExecutionPayloadEnvelopeOpts = {
@@ -40,8 +40,13 @@ export function processExecutionPayloadEnvelope(
40
40
 
41
41
  const requests = envelope.executionRequests;
42
42
 
43
- for (const deposit of requests.deposits) {
44
- processDepositRequest(fork, postState, deposit);
43
+ if (requests.deposits.length > 0) {
44
+ // Build cache of pending validator pubkeys once, shared across all deposit requests
45
+ const pendingValidatorPubkeys = getPendingValidatorPubkeys(postState.config, postState);
46
+
47
+ for (const deposit of requests.deposits) {
48
+ processDepositRequest(fork, postState, deposit, pendingValidatorPubkeys);
49
+ }
45
50
  }
46
51
 
47
52
  for (const withdrawal of requests.withdrawals) {
package/src/index.ts CHANGED
@@ -35,14 +35,32 @@ export {
35
35
  isStateValidatorsNodesPopulated,
36
36
  loadCachedBeaconState,
37
37
  } from "./cache/stateCache.js";
38
- export {type SyncCommitteeCache} from "./cache/syncCommitteeCache.js";
38
+ export {type SyncCommitteeCache, SyncCommitteeCacheEmpty} from "./cache/syncCommitteeCache.js";
39
39
  export * from "./constants/index.js";
40
40
  export type {EpochTransitionStep} from "./epoch/index.js";
41
41
  export {type BeaconStateTransitionMetrics, getMetrics} from "./metrics.js";
42
42
  export * from "./rewards/index.js";
43
43
  export * from "./signatureSets/index.js";
44
44
  export * from "./stateTransition.js";
45
- export * from "./stateView/index.js";
45
+ export {BeaconStateView} from "./stateView/beaconStateView.js";
46
+ export {
47
+ type IBeaconStateView,
48
+ type IBeaconStateViewAltair,
49
+ type IBeaconStateViewBellatrix,
50
+ type IBeaconStateViewCapella,
51
+ type IBeaconStateViewDeneb,
52
+ type IBeaconStateViewElectra,
53
+ type IBeaconStateViewFulu,
54
+ type IBeaconStateViewGloas,
55
+ isStatePostAltair,
56
+ isStatePostBellatrix,
57
+ isStatePostCapella,
58
+ isStatePostDeneb,
59
+ isStatePostElectra,
60
+ isStatePostFulu,
61
+ isStatePostGloas,
62
+ } from "./stateView/interface.js";
63
+ export {createBeaconStateView, createBeaconStateViewForHistoricalRegen} from "./stateView/stateViewFactory.js";
46
64
  export type {
47
65
  BeaconStateAllForks,
48
66
  BeaconStateAltair,
@@ -3,7 +3,7 @@ import {BeaconConfig} from "@lodestar/config";
3
3
  import {BUILDER_INDEX_SELF_BUILD, DOMAIN_BEACON_BUILDER} from "@lodestar/params";
4
4
  import {ValidatorIndex, gloas, ssz} from "@lodestar/types";
5
5
  import {PubkeyCache} from "../cache/pubkeyCache.js";
6
- import {IBeaconStateView} from "../stateView/interface.js";
6
+ import {IBeaconStateView, isStatePostGloas} from "../stateView/interface.js";
7
7
  import {computeSigningRoot} from "../util/index.js";
8
8
  import {type SingleSignatureSet, createSingleSignatureSetFromComponents} from "../util/signatureSets.js";
9
9
 
@@ -23,6 +23,10 @@ export function getExecutionPayloadEnvelopeSignatureSet(
23
23
  signedEnvelope: gloas.SignedExecutionPayloadEnvelope,
24
24
  proposerIndex: ValidatorIndex
25
25
  ): SingleSignatureSet {
26
+ if (!isStatePostGloas(state)) {
27
+ throw new Error(`Expected gloas+ state for execution payload envelope signature, got fork=${state.forkName}`);
28
+ }
29
+
26
30
  const envelope = signedEnvelope.message;
27
31
  const pubkey =
28
32
  envelope.builderIndex === BUILDER_INDEX_SELF_BUILD
@@ -3,7 +3,7 @@ import {BeaconConfig} from "@lodestar/config";
3
3
  import {ForkSeq} from "@lodestar/params";
4
4
  import {SignedBeaconBlock, Slot, phase0, ssz} from "@lodestar/types";
5
5
  import {PubkeyCache} from "../cache/pubkeyCache.js";
6
- import {IBeaconStateView} from "../stateView/interface.js";
6
+ import {IBeaconStateView, IBeaconStateViewGloas, isStatePostGloas} from "../stateView/interface.js";
7
7
  import {
8
8
  ISignatureSet,
9
9
  SignatureSetType,
@@ -34,6 +34,9 @@ export function getVoluntaryExitSignatureSet(
34
34
  const fork = config.getForkSeq(state.slot);
35
35
 
36
36
  if (fork >= ForkSeq.gloas && isBuilderVoluntaryExit(signedVoluntaryExit)) {
37
+ if (!isStatePostGloas(state)) {
38
+ throw new Error(`Expected gloas+ state for builder voluntary exit signature, got fork=${state.forkName}`);
39
+ }
37
40
  return getBuilderVoluntaryExitSignatureSet(config, state, signedVoluntaryExit);
38
41
  }
39
42
 
@@ -68,7 +71,7 @@ export function getValidatorVoluntaryExitSignatureSet(
68
71
 
69
72
  export function getBuilderVoluntaryExitSignatureSet(
70
73
  config: BeaconConfig,
71
- state: IBeaconStateView,
74
+ state: IBeaconStateViewGloas,
72
75
  signedVoluntaryExit: phase0.SignedVoluntaryExit
73
76
  ): ISignatureSet {
74
77
  const messageSlot = computeStartSlotAtEpoch(signedVoluntaryExit.message.epoch);
@@ -76,7 +76,7 @@ export enum StateHashTreeRootSource {
76
76
  prepareNextEpoch = "prepare_next_epoch",
77
77
  regenState = "regen_state",
78
78
  computeNewStateRoot = "compute_new_state_root",
79
- computeEnvelopeStateRoot = "compute_envelope_state_root",
79
+ computePayloadEnvelopeStateRoot = "compute_payload_envelope_state_root",
80
80
  }
81
81
 
82
82
  /**
@@ -1,7 +1,7 @@
1
1
  import {CompactMultiProof, ProofType, Tree, createProof} from "@chainsafe/persistent-merkle-tree";
2
2
  import {BitArray, ByteViews} from "@chainsafe/ssz";
3
3
  import {BeaconConfig} from "@lodestar/config";
4
- import {ForkSeq, SLOTS_PER_HISTORICAL_ROOT, isForkPostGloas} from "@lodestar/params";
4
+ import {ForkName, ForkSeq, SLOTS_PER_HISTORICAL_ROOT, isForkPostGloas} from "@lodestar/params";
5
5
  import {
6
6
  BeaconBlock,
7
7
  BeaconState,
@@ -64,9 +64,9 @@ import {canBuilderCoverBid} from "../util/gloas.js";
64
64
  import {loadState} from "../util/loadState/loadState.js";
65
65
  import {getRandaoMix} from "../util/seed.js";
66
66
  import {getLatestWeakSubjectivityCheckpointEpoch} from "../util/weakSubjectivity.js";
67
- import {IBeaconStateView} from "./interface.js";
67
+ import {IBeaconStateView, IBeaconStateViewLatestFork} from "./interface.js";
68
68
 
69
- export class BeaconStateView implements IBeaconStateView {
69
+ export class BeaconStateView implements IBeaconStateViewLatestFork {
70
70
  private readonly config: BeaconConfig;
71
71
  // Cached values extracted from the tree
72
72
  // phase0
@@ -92,6 +92,7 @@ export class BeaconStateView implements IBeaconStateView {
92
92
  // gloas
93
93
  private _executionPayloadAvailability: BitArray | null = null;
94
94
  private _latestExecutionPayloadBid: ExecutionPayloadBid | null = null;
95
+ private _payloadExpectedWithdrawals: capella.Withdrawal[] | null = null;
95
96
 
96
97
  constructor(readonly cachedState: CachedBeaconStateAllForks) {
97
98
  this.config = cachedState.config;
@@ -99,6 +100,10 @@ export class BeaconStateView implements IBeaconStateView {
99
100
 
100
101
  // phase0
101
102
 
103
+ get forkName(): ForkName {
104
+ return this.config.getForkName(this.cachedState.slot);
105
+ }
106
+
102
107
  get slot(): number {
103
108
  return this.cachedState.slot;
104
109
  }
@@ -357,7 +362,7 @@ export class BeaconStateView implements IBeaconStateView {
357
362
 
358
363
  get executionPayloadAvailability(): BitArray {
359
364
  if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.gloas) {
360
- throw new Error("executionPayloadAvailability is not available before GLOAS");
365
+ throw new Error("executionPayloadAvailability is not available before Gloas");
361
366
  }
362
367
 
363
368
  if (this._executionPayloadAvailability === null) {
@@ -371,7 +376,7 @@ export class BeaconStateView implements IBeaconStateView {
371
376
 
372
377
  get latestExecutionPayloadBid(): ExecutionPayloadBid {
373
378
  if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.gloas) {
374
- throw new Error("latestExecutionPayloadBid is not available before GLOAS");
379
+ throw new Error("latestExecutionPayloadBid is not available before Gloas");
375
380
  }
376
381
 
377
382
  if (this._latestExecutionPayloadBid === null) {
@@ -382,9 +387,22 @@ export class BeaconStateView implements IBeaconStateView {
382
387
  return this._latestExecutionPayloadBid;
383
388
  }
384
389
 
390
+ get payloadExpectedWithdrawals(): capella.Withdrawal[] {
391
+ if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.gloas) {
392
+ throw new Error("payloadExpectedWithdrawals is not available before Gloas");
393
+ }
394
+
395
+ if (this._payloadExpectedWithdrawals === null) {
396
+ this._payloadExpectedWithdrawals = (
397
+ this.cachedState as CachedBeaconStateGloas
398
+ ).payloadExpectedWithdrawals.toValue();
399
+ }
400
+ return this._payloadExpectedWithdrawals;
401
+ }
402
+
385
403
  getBuilder(index: BuilderIndex): gloas.Builder {
386
404
  if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.gloas) {
387
- throw new Error("Builders are not supported before GLOAS");
405
+ throw new Error("Builders are not supported before Gloas");
388
406
  }
389
407
 
390
408
  return (this.cachedState as CachedBeaconStateGloas).builders.getReadonly(index);
@@ -392,7 +410,7 @@ export class BeaconStateView implements IBeaconStateView {
392
410
 
393
411
  canBuilderCoverBid(builderIndex: BuilderIndex, bidAmount: number): boolean {
394
412
  if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.gloas) {
395
- throw new Error("Builders are not supported before GLOAS");
413
+ throw new Error("Builders are not supported before Gloas");
396
414
  }
397
415
 
398
416
  return canBuilderCoverBid(this.cachedState as CachedBeaconStateGloas, builderIndex, bidAmount);
@@ -404,7 +422,7 @@ export class BeaconStateView implements IBeaconStateView {
404
422
  */
405
423
  getIndexInPayloadTimelinessCommittee(validatorIndex: ValidatorIndex, slot: Slot): number {
406
424
  if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.gloas) {
407
- throw new Error("PTC committees are not supported before GLOAS");
425
+ throw new Error("PTC committees are not supported before Gloas");
408
426
  }
409
427
 
410
428
  const ptcCommittee = (this.cachedState as CachedBeaconStateGloas).epochCtx.getPayloadTimelinessCommittee(slot);
@@ -1,5 +1,22 @@
1
1
  import {CompactMultiProof} from "@chainsafe/persistent-merkle-tree";
2
2
  import {BitArray, ByteViews} from "@chainsafe/ssz";
3
+ import {
4
+ ForkName,
5
+ ForkPostAltair,
6
+ ForkPostBellatrix,
7
+ ForkPostCapella,
8
+ ForkPostDeneb,
9
+ ForkPostElectra,
10
+ ForkPostFulu,
11
+ ForkPostGloas,
12
+ isForkPostAltair,
13
+ isForkPostBellatrix,
14
+ isForkPostCapella,
15
+ isForkPostDeneb,
16
+ isForkPostElectra,
17
+ isForkPostFulu,
18
+ isForkPostGloas,
19
+ } from "@lodestar/params";
3
20
  import {
4
21
  BeaconBlock,
5
22
  BeaconState,
@@ -41,6 +58,7 @@ export interface IBeaconStateView {
41
58
  // State access
42
59
 
43
60
  // phase0
61
+ forkName: ForkName;
44
62
  slot: Slot;
45
63
  fork: Fork;
46
64
  epoch: Epoch;
@@ -56,50 +74,6 @@ export interface IBeaconStateView {
56
74
  getStateRootAtSlot(slot: Slot): Root;
57
75
  getRandaoMix(epoch: Epoch): Bytes32;
58
76
 
59
- // altair
60
- previousEpochParticipation: Uint8Array;
61
- currentEpochParticipation: Uint8Array;
62
- getPreviousEpochParticipation(validatorIndex: ValidatorIndex): number;
63
- getCurrentEpochParticipation(validatorIndex: ValidatorIndex): number;
64
-
65
- // bellatrix
66
- latestExecutionPayloadHeader: ExecutionPayloadHeader;
67
- /**
68
- * Cross-fork accessor for the execution block hash of the most recently included payload.
69
- * Pre-gloas: returns latestExecutionPayloadHeader.blockHash (bellatrix–fulu).
70
- * Gloas+: returns the dedicated latestBlockHash state field (EIP-7732).
71
- * Throws before bellatrix.
72
- */
73
- latestBlockHash: Bytes32;
74
- /**
75
- * The execution block number of the most recently included payload.
76
- * Named payloadBlockNumber (not latestBlockNumber) to mirror ExecutionPayloadHeader.blockNumber pre-gloas.
77
- * Only available from bellatrix through fulu — not tracked on BeaconState in gloas+ (EIP-7732).
78
- * Throws before bellatrix and from gloas onwards.
79
- */
80
- payloadBlockNumber: number;
81
-
82
- // capella
83
- historicalSummaries: capella.HistoricalSummaries;
84
-
85
- // electra
86
- pendingDeposits: electra.PendingDeposits;
87
- pendingDepositsCount: number;
88
- pendingPartialWithdrawals: electra.PendingPartialWithdrawals;
89
- pendingPartialWithdrawalsCount: number;
90
- pendingConsolidations: electra.PendingConsolidations;
91
- pendingConsolidationsCount: number;
92
-
93
- // fulu
94
- proposerLookahead: fulu.ProposerLookahead;
95
-
96
- // gloas
97
- executionPayloadAvailability: BitArray;
98
- latestExecutionPayloadBid: ExecutionPayloadBid;
99
- getBuilder(index: BuilderIndex): gloas.Builder;
100
- canBuilderCoverBid(builderIndex: BuilderIndex, bidAmount: number): boolean;
101
- getIndexInPayloadTimelinessCommittee(validatorIndex: ValidatorIndex, slot: Slot): number;
102
-
103
77
  // Shuffling and committees
104
78
  getShufflingAtEpoch(epoch: Epoch): EpochShuffling;
105
79
  // Decision roots
@@ -117,15 +91,6 @@ export interface IBeaconStateView {
117
91
  nextProposers: ValidatorIndex[];
118
92
  getBeaconProposer(slot: Slot): ValidatorIndex;
119
93
 
120
- // Sync committees
121
- currentSyncCommittee: altair.SyncCommittee;
122
- nextSyncCommittee: altair.SyncCommittee;
123
- currentSyncCommitteeIndexed: SyncCommitteeCache;
124
- syncProposerReward: number;
125
- getIndexedSyncCommitteeAtEpoch(epoch: Epoch): SyncCommitteeCache;
126
- /** Get indexed sync committee with slot+1 offset for duty lookups */
127
- getIndexedSyncCommittee(slot: Slot): SyncCommitteeCache;
128
-
129
94
  // Validators and balances
130
95
  effectiveBalanceIncrements: EffectiveBalanceIncrements;
131
96
  getEffectiveBalanceIncrementsZeroInactive(): EffectiveBalanceIncrements;
@@ -140,29 +105,10 @@ export interface IBeaconStateView {
140
105
  getAllValidators(): phase0.Validator[];
141
106
  getAllBalances(): number[];
142
107
 
143
- // Merge
144
- isExecutionStateType: boolean;
145
- isMergeTransitionComplete: boolean;
146
- // TODO this should go away (or rather only need block)
147
- isExecutionEnabled(block: BeaconBlock | BlindedBeaconBlock): boolean;
148
-
149
- // Block production
150
- getExpectedWithdrawals(): {
151
- expectedWithdrawals: capella.Withdrawal[];
152
- processedBuilderWithdrawalsCount: number;
153
- processedPartialWithdrawalsCount: number;
154
- processedBuildersSweepCount: number;
155
- processedValidatorSweepCount: number;
156
- };
157
-
158
108
  // API
159
109
  proposerRewards: RewardCache;
160
110
  computeBlockRewards(block: BeaconBlock, proposerRewards?: RewardCache): Promise<rewards.BlockRewards>;
161
111
  computeAttestationsRewards(validatorIds?: (ValidatorIndex | string)[]): Promise<rewards.AttestationsRewards>;
162
- computeSyncCommitteeRewards(
163
- block: BeaconBlock,
164
- validatorIds: (ValidatorIndex | string)[]
165
- ): Promise<rewards.SyncCommitteeRewards>;
166
112
  getLatestWeakSubjectivityCheckpointEpoch(): Epoch;
167
113
 
168
114
  // Validation
@@ -174,7 +120,6 @@ export interface IBeaconStateView {
174
120
 
175
121
  // Proofs
176
122
  getFinalizedRootProof(): Uint8Array[];
177
- getSyncCommitteesWitness(): SyncCommitteeWitness;
178
123
  getSingleProof(gindex: bigint): Uint8Array[];
179
124
  createMultiProof(descriptor: Uint8Array): CompactMultiProof;
180
125
 
@@ -215,8 +160,133 @@ export interface IBeaconStateView {
215
160
  epochTransitionCacheOpts?: EpochTransitionCacheOpts & {dontTransferCache?: boolean},
216
161
  modules?: StateTransitionModules
217
162
  ): IBeaconStateView;
163
+ }
164
+
165
+ /** Altair+ state fields — use isStatePostAltair() guard */
166
+ export interface IBeaconStateViewAltair extends IBeaconStateView {
167
+ forkName: ForkPostAltair;
168
+ previousEpochParticipation: Uint8Array;
169
+ currentEpochParticipation: Uint8Array;
170
+ getPreviousEpochParticipation(validatorIndex: ValidatorIndex): number;
171
+ getCurrentEpochParticipation(validatorIndex: ValidatorIndex): number;
172
+ currentSyncCommittee: altair.SyncCommittee;
173
+ nextSyncCommittee: altair.SyncCommittee;
174
+ currentSyncCommitteeIndexed: SyncCommitteeCache;
175
+ syncProposerReward: number;
176
+ getIndexedSyncCommitteeAtEpoch(epoch: Epoch): SyncCommitteeCache;
177
+ /** Get indexed sync committee with slot+1 offset for duty lookups */
178
+ getIndexedSyncCommittee(slot: Slot): SyncCommitteeCache;
179
+ computeSyncCommitteeRewards(
180
+ block: BeaconBlock,
181
+ validatorIds: (ValidatorIndex | string)[]
182
+ ): Promise<rewards.SyncCommitteeRewards>;
183
+ getSyncCommitteesWitness(): SyncCommitteeWitness;
184
+ }
185
+
186
+ /** Bellatrix+ state fields — use isStatePostBellatrix() guard */
187
+ export interface IBeaconStateViewBellatrix extends IBeaconStateViewAltair {
188
+ forkName: ForkPostBellatrix;
189
+ latestExecutionPayloadHeader: ExecutionPayloadHeader;
190
+ /**
191
+ * Cross-fork accessor for the execution block hash of the most recently included payload.
192
+ * Pre-gloas: returns latestExecutionPayloadHeader.blockHash (bellatrix–fulu).
193
+ * Gloas+: returns the dedicated latestBlockHash state field (EIP-7732).
194
+ * Throws before bellatrix.
195
+ */
196
+ latestBlockHash: Bytes32;
197
+ /**
198
+ * The execution block number of the most recently included payload.
199
+ * Named payloadBlockNumber (not latestBlockNumber) to mirror ExecutionPayloadHeader.blockNumber pre-gloas.
200
+ * Only available from bellatrix through fulu — not tracked on BeaconState in gloas+ (EIP-7732).
201
+ * Throws before bellatrix and from gloas onwards.
202
+ */
203
+ payloadBlockNumber: number;
204
+ isExecutionStateType: boolean;
205
+ isMergeTransitionComplete: boolean;
206
+ isExecutionEnabled(block: BeaconBlock | BlindedBeaconBlock): boolean;
207
+ }
208
+
209
+ /** Capella+ state fields — use isStatePostCapella() guard */
210
+ export interface IBeaconStateViewCapella extends IBeaconStateViewBellatrix {
211
+ forkName: ForkPostCapella;
212
+ historicalSummaries: capella.HistoricalSummaries;
213
+ getExpectedWithdrawals(): {
214
+ expectedWithdrawals: capella.Withdrawal[];
215
+ processedBuilderWithdrawalsCount: number;
216
+ processedPartialWithdrawalsCount: number;
217
+ processedBuildersSweepCount: number;
218
+ processedValidatorSweepCount: number;
219
+ };
220
+ }
221
+
222
+ /** Deneb+ state — no new state-view fields; placeholder for fork completeness and isStatePostDeneb() narrowing */
223
+ export interface IBeaconStateViewDeneb extends IBeaconStateViewCapella {
224
+ forkName: ForkPostDeneb;
225
+ }
226
+
227
+ /** Electra+ state fields — use isStatePostElectra() guard */
228
+ export interface IBeaconStateViewElectra extends IBeaconStateViewDeneb {
229
+ forkName: ForkPostElectra;
230
+ pendingDeposits: electra.PendingDeposits;
231
+ pendingDepositsCount: number;
232
+ pendingPartialWithdrawals: electra.PendingPartialWithdrawals;
233
+ pendingPartialWithdrawalsCount: number;
234
+ pendingConsolidations: electra.PendingConsolidations;
235
+ pendingConsolidationsCount: number;
236
+ }
237
+
238
+ /** Fulu+ state fields — use isStatePostFulu() guard */
239
+ export interface IBeaconStateViewFulu extends IBeaconStateViewElectra {
240
+ forkName: ForkPostFulu;
241
+ proposerLookahead: fulu.ProposerLookahead;
242
+ }
243
+
244
+ /** Gloas+ state fields — use isStatePostGloas() guard */
245
+ export interface IBeaconStateViewGloas extends IBeaconStateViewFulu {
246
+ forkName: ForkPostGloas;
247
+ executionPayloadAvailability: BitArray;
248
+ latestExecutionPayloadBid: ExecutionPayloadBid;
249
+ payloadExpectedWithdrawals: capella.Withdrawal[];
250
+ getBuilder(index: BuilderIndex): gloas.Builder;
251
+ canBuilderCoverBid(builderIndex: BuilderIndex, bidAmount: number): boolean;
252
+ getIndexInPayloadTimelinessCommittee(validatorIndex: ValidatorIndex, slot: Slot): number;
218
253
  processExecutionPayloadEnvelope(
219
254
  signedEnvelope: gloas.SignedExecutionPayloadEnvelope,
220
255
  opts?: ProcessExecutionPayloadEnvelopeOpts
221
256
  ): IBeaconStateView;
222
257
  }
258
+
259
+ /**
260
+ * Type constraint for the concrete BeaconStateView class.
261
+ * Requires all fields from the latest fork interface (IBeaconStateViewGloas) but keeps
262
+ * forkName as ForkName since the class wraps any fork's state.
263
+ * Sub-interfaces retain their narrowed forkName discriminants for caller-side type guards.
264
+ */
265
+ export type IBeaconStateViewLatestFork = Omit<IBeaconStateViewGloas, "forkName"> & {forkName: ForkName};
266
+ export function isStatePostAltair(state: IBeaconStateView): state is IBeaconStateViewAltair {
267
+ return isForkPostAltair(state.forkName);
268
+ }
269
+
270
+ export function isStatePostBellatrix(state: IBeaconStateView): state is IBeaconStateViewBellatrix {
271
+ return isForkPostBellatrix(state.forkName);
272
+ }
273
+
274
+ export function isStatePostCapella(state: IBeaconStateView): state is IBeaconStateViewCapella {
275
+ return isForkPostCapella(state.forkName);
276
+ }
277
+
278
+ export function isStatePostDeneb(state: IBeaconStateView): state is IBeaconStateViewDeneb {
279
+ return isForkPostDeneb(state.forkName);
280
+ }
281
+
282
+ export function isStatePostElectra(state: IBeaconStateView): state is IBeaconStateViewElectra {
283
+ return isForkPostElectra(state.forkName);
284
+ }
285
+
286
+ export function isStatePostFulu(state: IBeaconStateView): state is IBeaconStateViewFulu {
287
+ return isForkPostFulu(state.forkName);
288
+ }
289
+
290
+ export function isStatePostGloas(state: IBeaconStateView): state is IBeaconStateViewGloas {
291
+ return isForkPostGloas(state.forkName);
292
+ }
@@ -1,5 +1,13 @@
1
- import {MIN_ATTESTATION_INCLUSION_DELAY, SLOTS_PER_EPOCH} from "@lodestar/params";
2
- import {AttesterSlashing, Slot, ValidatorIndex, phase0, ssz} from "@lodestar/types";
1
+ import {ForkSeq, MIN_ATTESTATION_INCLUSION_DELAY, SLOTS_PER_EPOCH} from "@lodestar/params";
2
+ import {
3
+ AttesterSlashing,
4
+ IndexedAttestation,
5
+ IndexedAttestationBigint,
6
+ Slot,
7
+ ValidatorIndex,
8
+ phase0,
9
+ ssz,
10
+ } from "@lodestar/types";
3
11
 
4
12
  /**
5
13
  * Check if [[data1]] and [[data2]] are slashable according to Casper FFG rules.
@@ -22,15 +30,36 @@ export function isValidAttestationSlot(attestationSlot: Slot, currentSlot: Slot)
22
30
  );
23
31
  }
24
32
 
25
- export function getAttesterSlashableIndices(attesterSlashing: AttesterSlashing): ValidatorIndex[] {
33
+ /**
34
+ * Compute the intersection of two sorted validator index lists.
35
+ * Both inputs must be sorted in ascending order (per spec).
36
+ */
37
+ export function getIntersectingIndices(indices1: ValidatorIndex[], indices2: ValidatorIndex[]): ValidatorIndex[] {
26
38
  const indices: ValidatorIndex[] = [];
27
- const attSet1 = new Set(attesterSlashing.attestation1.attestingIndices);
28
- const attArr2 = attesterSlashing.attestation2.attestingIndices;
29
- for (let i = 0, len = attArr2.length; i < len; i++) {
30
- const index = attArr2[i];
31
- if (attSet1.has(index)) {
39
+ const alreadyPresent = new Set(indices1);
40
+ for (let i = 0, len = indices2.length; i < len; i++) {
41
+ const index = indices2[i];
42
+ if (alreadyPresent.has(index)) {
32
43
  indices.push(index);
33
44
  }
34
45
  }
35
46
  return indices;
36
47
  }
48
+
49
+ export function getAttesterSlashableIndices(attesterSlashing: AttesterSlashing): ValidatorIndex[] {
50
+ return getIntersectingIndices(
51
+ attesterSlashing.attestation1.attestingIndices,
52
+ attesterSlashing.attestation2.attestingIndices
53
+ );
54
+ }
55
+
56
+ /**
57
+ * Convert IndexedAttestation to IndexedAttestationBigint via SSZ roundtrip.
58
+ * Both types share the same binary layout — only the JS numeric representation differs.
59
+ */
60
+ export function toIndexedAttestationBigint(att: IndexedAttestation, fork: ForkSeq): IndexedAttestationBigint {
61
+ const sszType = fork >= ForkSeq.electra ? ssz.electra.IndexedAttestation : ssz.phase0.IndexedAttestation;
62
+ const sszTypeBigint =
63
+ fork >= ForkSeq.electra ? ssz.electra.IndexedAttestationBigint : ssz.phase0.IndexedAttestationBigint;
64
+ return sszTypeBigint.deserialize(sszType.serialize(att));
65
+ }
package/src/util/gloas.ts CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  import {BuilderIndex, Epoch, ValidatorIndex, gloas} from "@lodestar/types";
12
12
  import {AttestationData} from "@lodestar/types/phase0";
13
13
  import {byteArrayEquals} from "@lodestar/utils";
14
+ import {IBeaconStateViewGloas} from "../stateView/interface.js";
14
15
  import {CachedBeaconStateGloas} from "../types.js";
15
16
  import {getBlockRootAtSlot} from "./blockRoot.js";
16
17
  import {computeEpochAtSlot} from "./epoch.js";
@@ -167,6 +168,6 @@ export function isAttestationSameSlotRootCache(rootCache: RootCache, data: Attes
167
168
  return isMatchingBlockRoot && isCurrentBlockRoot;
168
169
  }
169
170
 
170
- export function isParentBlockFull(state: CachedBeaconStateGloas): boolean {
171
+ export function isParentBlockFull(state: CachedBeaconStateGloas | IBeaconStateViewGloas): boolean {
171
172
  return byteArrayEquals(state.latestExecutionPayloadBid.blockHash, state.latestBlockHash);
172
173
  }