@lodestar/state-transition 1.43.0-dev.433e692fd9 → 1.43.0-dev.4451fec75a

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 (138) hide show
  1. package/lib/block/index.d.ts +2 -2
  2. package/lib/block/index.d.ts.map +1 -1
  3. package/lib/block/index.js +11 -4
  4. package/lib/block/index.js.map +1 -1
  5. package/lib/block/processConsolidationRequest.d.ts.map +1 -1
  6. package/lib/block/processConsolidationRequest.js +2 -1
  7. package/lib/block/processConsolidationRequest.js.map +1 -1
  8. package/lib/block/processParentExecutionPayload.d.ts +20 -0
  9. package/lib/block/processParentExecutionPayload.d.ts.map +1 -0
  10. package/lib/block/processParentExecutionPayload.js +100 -0
  11. package/lib/block/processParentExecutionPayload.js.map +1 -0
  12. package/lib/block/processWithdrawals.d.ts.map +1 -1
  13. package/lib/block/processWithdrawals.js +10 -4
  14. package/lib/block/processWithdrawals.js.map +1 -1
  15. package/lib/cache/epochCache.d.ts +3 -1
  16. package/lib/cache/epochCache.d.ts.map +1 -1
  17. package/lib/cache/epochCache.js +33 -15
  18. package/lib/cache/epochCache.js.map +1 -1
  19. package/lib/cache/epochTransitionCache.d.ts +5 -0
  20. package/lib/cache/epochTransitionCache.d.ts.map +1 -1
  21. package/lib/cache/epochTransitionCache.js +1 -0
  22. package/lib/cache/epochTransitionCache.js.map +1 -1
  23. package/lib/epoch/index.d.ts +3 -1
  24. package/lib/epoch/index.d.ts.map +1 -1
  25. package/lib/epoch/index.js +8 -1
  26. package/lib/epoch/index.js.map +1 -1
  27. package/lib/epoch/processPendingDeposits.d.ts.map +1 -1
  28. package/lib/epoch/processPendingDeposits.js +4 -2
  29. package/lib/epoch/processPendingDeposits.js.map +1 -1
  30. package/lib/epoch/processPtcWindow.d.ts +11 -0
  31. package/lib/epoch/processPtcWindow.d.ts.map +1 -0
  32. package/lib/epoch/processPtcWindow.js +28 -0
  33. package/lib/epoch/processPtcWindow.js.map +1 -0
  34. package/lib/lightClient/spec/index.d.ts +22 -0
  35. package/lib/lightClient/spec/index.d.ts.map +1 -0
  36. package/lib/lightClient/spec/index.js +58 -0
  37. package/lib/lightClient/spec/index.js.map +1 -0
  38. package/lib/lightClient/spec/isBetterUpdate.d.ts +23 -0
  39. package/lib/lightClient/spec/isBetterUpdate.d.ts.map +1 -0
  40. package/lib/lightClient/spec/isBetterUpdate.js +66 -0
  41. package/lib/lightClient/spec/isBetterUpdate.js.map +1 -0
  42. package/lib/lightClient/spec/processLightClientUpdate.d.ts +12 -0
  43. package/lib/lightClient/spec/processLightClientUpdate.d.ts.map +1 -0
  44. package/lib/lightClient/spec/processLightClientUpdate.js +80 -0
  45. package/lib/lightClient/spec/processLightClientUpdate.js.map +1 -0
  46. package/lib/lightClient/spec/store.d.ts +45 -0
  47. package/lib/lightClient/spec/store.d.ts.map +1 -0
  48. package/lib/lightClient/spec/store.js +56 -0
  49. package/lib/lightClient/spec/store.js.map +1 -0
  50. package/lib/lightClient/spec/utils.d.ts +47 -0
  51. package/lib/lightClient/spec/utils.d.ts.map +1 -0
  52. package/lib/lightClient/spec/utils.js +197 -0
  53. package/lib/lightClient/spec/utils.js.map +1 -0
  54. package/lib/lightClient/spec/validateLightClientBootstrap.d.ts +4 -0
  55. package/lib/lightClient/spec/validateLightClientBootstrap.d.ts.map +1 -0
  56. package/lib/lightClient/spec/validateLightClientBootstrap.js +22 -0
  57. package/lib/lightClient/spec/validateLightClientBootstrap.js.map +1 -0
  58. package/lib/lightClient/spec/validateLightClientUpdate.d.ts +5 -0
  59. package/lib/lightClient/spec/validateLightClientUpdate.d.ts.map +1 -0
  60. package/lib/lightClient/spec/validateLightClientUpdate.js +88 -0
  61. package/lib/lightClient/spec/validateLightClientUpdate.js.map +1 -0
  62. package/lib/signatureSets/executionPayloadEnvelope.js +1 -1
  63. package/lib/signatureSets/executionPayloadEnvelope.js.map +1 -1
  64. package/lib/signatureSets/index.d.ts +1 -0
  65. package/lib/signatureSets/index.d.ts.map +1 -1
  66. package/lib/signatureSets/index.js +1 -0
  67. package/lib/signatureSets/index.js.map +1 -1
  68. package/lib/signatureSets/proposerPreferences.d.ts +4 -0
  69. package/lib/signatureSets/proposerPreferences.d.ts.map +1 -0
  70. package/lib/signatureSets/proposerPreferences.js +8 -0
  71. package/lib/signatureSets/proposerPreferences.js.map +1 -0
  72. package/lib/slot/upgradeStateToElectra.d.ts.map +1 -1
  73. package/lib/slot/upgradeStateToElectra.js +2 -2
  74. package/lib/slot/upgradeStateToElectra.js.map +1 -1
  75. package/lib/slot/upgradeStateToGloas.d.ts.map +1 -1
  76. package/lib/slot/upgradeStateToGloas.js +3 -1
  77. package/lib/slot/upgradeStateToGloas.js.map +1 -1
  78. package/lib/stateTransition.js +1 -1
  79. package/lib/stateTransition.js.map +1 -1
  80. package/lib/stateView/beaconStateView.d.ts +21 -14
  81. package/lib/stateView/beaconStateView.d.ts.map +1 -1
  82. package/lib/stateView/beaconStateView.js +63 -39
  83. package/lib/stateView/beaconStateView.js.map +1 -1
  84. package/lib/stateView/interface.d.ts +22 -12
  85. package/lib/stateView/interface.d.ts.map +1 -1
  86. package/lib/stateView/interface.js.map +1 -1
  87. package/lib/util/computeAnchorCheckpoint.d.ts +1 -1
  88. package/lib/util/computeAnchorCheckpoint.d.ts.map +1 -1
  89. package/lib/util/computeAnchorCheckpoint.js +6 -19
  90. package/lib/util/computeAnchorCheckpoint.js.map +1 -1
  91. package/lib/util/epoch.d.ts.map +1 -1
  92. package/lib/util/epoch.js +6 -4
  93. package/lib/util/epoch.js.map +1 -1
  94. package/lib/util/gloas.d.ts +7 -3
  95. package/lib/util/gloas.d.ts.map +1 -1
  96. package/lib/util/gloas.js +25 -3
  97. package/lib/util/gloas.js.map +1 -1
  98. package/lib/util/loadState/loadState.js +4 -4
  99. package/lib/util/loadState/loadState.js.map +1 -1
  100. package/lib/util/validator.d.ts +14 -2
  101. package/lib/util/validator.d.ts.map +1 -1
  102. package/lib/util/validator.js +24 -2
  103. package/lib/util/validator.js.map +1 -1
  104. package/package.json +14 -9
  105. package/src/block/index.ts +12 -4
  106. package/src/block/processConsolidationRequest.ts +2 -1
  107. package/src/block/processParentExecutionPayload.ts +116 -0
  108. package/src/block/processWithdrawals.ts +12 -4
  109. package/src/cache/epochCache.ts +35 -33
  110. package/src/cache/epochTransitionCache.ts +7 -0
  111. package/src/epoch/index.ts +9 -0
  112. package/src/epoch/processPendingDeposits.ts +5 -2
  113. package/src/epoch/processPtcWindow.ts +38 -0
  114. package/src/lightClient/spec/index.ts +101 -0
  115. package/src/lightClient/spec/isBetterUpdate.ts +94 -0
  116. package/src/lightClient/spec/processLightClientUpdate.ts +119 -0
  117. package/src/lightClient/spec/store.ts +106 -0
  118. package/src/lightClient/spec/utils.ts +317 -0
  119. package/src/lightClient/spec/validateLightClientBootstrap.ts +39 -0
  120. package/src/lightClient/spec/validateLightClientUpdate.ts +145 -0
  121. package/src/signatureSets/executionPayloadEnvelope.ts +1 -1
  122. package/src/signatureSets/index.ts +1 -0
  123. package/src/signatureSets/proposerPreferences.ts +12 -0
  124. package/src/slot/upgradeStateToElectra.ts +4 -2
  125. package/src/slot/upgradeStateToGloas.ts +5 -1
  126. package/src/stateTransition.ts +1 -1
  127. package/src/stateView/beaconStateView.ts +72 -52
  128. package/src/stateView/interface.ts +29 -15
  129. package/src/util/computeAnchorCheckpoint.ts +6 -19
  130. package/src/util/epoch.ts +13 -4
  131. package/src/util/gloas.ts +46 -4
  132. package/src/util/loadState/loadState.ts +4 -4
  133. package/src/util/validator.ts +42 -2
  134. package/lib/block/processExecutionPayloadEnvelope.d.ts +0 -9
  135. package/lib/block/processExecutionPayloadEnvelope.d.ts.map +0 -1
  136. package/lib/block/processExecutionPayloadEnvelope.js +0 -106
  137. package/lib/block/processExecutionPayloadEnvelope.js.map +0 -1
  138. package/src/block/processExecutionPayloadEnvelope.ts +0 -175
@@ -0,0 +1,145 @@
1
+ import {PublicKey, Signature, fastAggregateVerify} from "@chainsafe/blst";
2
+ import {ChainForkConfig} from "@lodestar/config";
3
+ import {
4
+ DOMAIN_SYNC_COMMITTEE,
5
+ FINALIZED_ROOT_DEPTH,
6
+ FINALIZED_ROOT_DEPTH_ELECTRA,
7
+ FINALIZED_ROOT_INDEX,
8
+ FINALIZED_ROOT_INDEX_ELECTRA,
9
+ GENESIS_SLOT,
10
+ MIN_SYNC_COMMITTEE_PARTICIPANTS,
11
+ NEXT_SYNC_COMMITTEE_DEPTH,
12
+ NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA,
13
+ NEXT_SYNC_COMMITTEE_INDEX,
14
+ NEXT_SYNC_COMMITTEE_INDEX_ELECTRA,
15
+ } from "@lodestar/params";
16
+ import {LightClientUpdate, Root, isElectraLightClientUpdate, ssz} from "@lodestar/types";
17
+ import type {ILightClientStore, SyncCommitteeFast} from "./store.js";
18
+ import {
19
+ ZERO_HASH,
20
+ getParticipantPubkeys,
21
+ isFinalityUpdate,
22
+ isSyncCommitteeUpdate,
23
+ isValidLightClientHeader,
24
+ isValidMerkleBranch,
25
+ isZeroedHeader,
26
+ isZeroedSyncCommittee,
27
+ sumBits,
28
+ } from "./utils.js";
29
+
30
+ export function validateLightClientUpdate(
31
+ config: ChainForkConfig,
32
+ store: ILightClientStore,
33
+ update: LightClientUpdate,
34
+ syncCommittee: SyncCommitteeFast
35
+ ): void {
36
+ // Verify sync committee has sufficient participants
37
+ if (sumBits(update.syncAggregate.syncCommitteeBits) < MIN_SYNC_COMMITTEE_PARTICIPANTS) {
38
+ throw Error("Sync committee has not sufficient participants");
39
+ }
40
+
41
+ if (!isValidLightClientHeader(config, update.attestedHeader)) {
42
+ throw Error("Attested Header is not Valid Light Client Header");
43
+ }
44
+
45
+ // Sanity check that slots are in correct order
46
+ if (update.signatureSlot <= update.attestedHeader.beacon.slot) {
47
+ throw Error(
48
+ `signature slot ${update.signatureSlot} must be after attested header slot ${update.attestedHeader.beacon.slot}`
49
+ );
50
+ }
51
+ if (update.attestedHeader.beacon.slot < update.finalizedHeader.beacon.slot) {
52
+ throw Error(
53
+ `attested header slot ${update.signatureSlot} must be after finalized header slot ${update.finalizedHeader.beacon.slot}`
54
+ );
55
+ }
56
+
57
+ // Verify that the `finality_branch`, if present, confirms `finalized_header`
58
+ // to match the finalized checkpoint root saved in the state of `attested_header`.
59
+ // Note that the genesis finalized checkpoint root is represented as a zero hash.
60
+ if (!isFinalityUpdate(update)) {
61
+ if (!isZeroedHeader(update.finalizedHeader.beacon)) {
62
+ throw Error("finalizedHeader must be zero for non-finality update");
63
+ }
64
+ } else {
65
+ let finalizedRoot: Root;
66
+
67
+ if (update.finalizedHeader.beacon.slot === GENESIS_SLOT) {
68
+ if (!isZeroedHeader(update.finalizedHeader.beacon)) {
69
+ throw Error("finalizedHeader must be zero for not finality update");
70
+ }
71
+ finalizedRoot = ZERO_HASH;
72
+ } else {
73
+ if (!isValidLightClientHeader(config, update.finalizedHeader)) {
74
+ throw Error("Finalized Header is not valid Light Client Header");
75
+ }
76
+
77
+ finalizedRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(update.finalizedHeader.beacon);
78
+ }
79
+
80
+ if (
81
+ !isValidMerkleBranch(
82
+ finalizedRoot,
83
+ update.finalityBranch,
84
+ isElectraLightClientUpdate(update) ? FINALIZED_ROOT_DEPTH_ELECTRA : FINALIZED_ROOT_DEPTH,
85
+ isElectraLightClientUpdate(update) ? FINALIZED_ROOT_INDEX_ELECTRA : FINALIZED_ROOT_INDEX,
86
+ update.attestedHeader.beacon.stateRoot
87
+ )
88
+ ) {
89
+ throw Error("Invalid finality header merkle branch");
90
+ }
91
+ }
92
+
93
+ // Verify that the `next_sync_committee`, if present, actually is the next sync committee saved in the
94
+ // state of the `attested_header`
95
+ if (!isSyncCommitteeUpdate(update)) {
96
+ if (!isZeroedSyncCommittee(update.nextSyncCommittee)) {
97
+ throw Error("nextSyncCommittee must be zero for non sync committee update");
98
+ }
99
+ } else {
100
+ if (
101
+ !isValidMerkleBranch(
102
+ ssz.altair.SyncCommittee.hashTreeRoot(update.nextSyncCommittee),
103
+ update.nextSyncCommitteeBranch,
104
+ isElectraLightClientUpdate(update) ? NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA : NEXT_SYNC_COMMITTEE_DEPTH,
105
+ isElectraLightClientUpdate(update) ? NEXT_SYNC_COMMITTEE_INDEX_ELECTRA : NEXT_SYNC_COMMITTEE_INDEX,
106
+ update.attestedHeader.beacon.stateRoot
107
+ )
108
+ ) {
109
+ throw Error("Invalid next sync committee merkle branch");
110
+ }
111
+ }
112
+
113
+ // Verify sync committee aggregate signature
114
+
115
+ const participantPubkeys = getParticipantPubkeys(syncCommittee.pubkeys, update.syncAggregate.syncCommitteeBits);
116
+
117
+ const signingRoot = ssz.phase0.SigningData.hashTreeRoot({
118
+ objectRoot: ssz.phase0.BeaconBlockHeader.hashTreeRoot(update.attestedHeader.beacon),
119
+ domain: store.config.getDomain(update.signatureSlot - 1, DOMAIN_SYNC_COMMITTEE),
120
+ });
121
+
122
+ if (!isValidBlsAggregate(participantPubkeys, signingRoot, update.syncAggregate.syncCommitteeSignature)) {
123
+ throw Error("Invalid aggregate signature");
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Same as BLS.verifyAggregate but with detailed error messages
129
+ */
130
+ function isValidBlsAggregate(publicKeys: PublicKey[], message: Uint8Array, signature: Uint8Array): boolean {
131
+ let sig: Signature;
132
+ try {
133
+ sig = Signature.fromBytes(signature, true);
134
+ } catch (e) {
135
+ (e as Error).message = `Error deserializing signature: ${(e as Error).message}`;
136
+ throw e;
137
+ }
138
+
139
+ try {
140
+ return fastAggregateVerify(message, publicKeys, sig);
141
+ } catch (e) {
142
+ (e as Error).message = `Error verifying signature: ${(e as Error).message}`;
143
+ throw e;
144
+ }
145
+ }
@@ -11,7 +11,7 @@ export function getExecutionPayloadEnvelopeSigningRoot(
11
11
  config: BeaconConfig,
12
12
  envelope: gloas.ExecutionPayloadEnvelope
13
13
  ): Uint8Array {
14
- const domain = config.getDomain(envelope.slot, DOMAIN_BEACON_BUILDER);
14
+ const domain = config.getDomain(envelope.payload.slotNumber, DOMAIN_BEACON_BUILDER);
15
15
 
16
16
  return computeSigningRoot(ssz.gloas.ExecutionPayloadEnvelope, envelope, domain);
17
17
  }
@@ -20,6 +20,7 @@ export * from "./executionPayloadEnvelope.js";
20
20
  export * from "./indexedAttestation.js";
21
21
  export * from "./indexedPayloadAttestation.js";
22
22
  export * from "./proposer.js";
23
+ export * from "./proposerPreferences.js";
23
24
  export * from "./proposerSlashings.js";
24
25
  export * from "./randao.js";
25
26
  export * from "./voluntaryExits.js";
@@ -0,0 +1,12 @@
1
+ import {BeaconConfig} from "@lodestar/config";
2
+ import {DOMAIN_PROPOSER_PREFERENCES} from "@lodestar/params";
3
+ import {gloas, ssz} from "@lodestar/types";
4
+ import {computeSigningRoot} from "../util/index.js";
5
+
6
+ export function getProposerPreferencesSigningRoot(
7
+ config: BeaconConfig,
8
+ preferences: gloas.ProposerPreferences
9
+ ): Uint8Array {
10
+ const domain = config.getDomain(preferences.proposalSlot, DOMAIN_PROPOSER_PREFERENCES);
11
+ return computeSigningRoot(ssz.gloas.ProposerPreferences, preferences, domain);
12
+ }
@@ -1,4 +1,4 @@
1
- import {FAR_FUTURE_EPOCH, GENESIS_SLOT, UNSET_DEPOSIT_REQUESTS_START_INDEX} from "@lodestar/params";
1
+ import {FAR_FUTURE_EPOCH, ForkSeq, GENESIS_SLOT, UNSET_DEPOSIT_REQUESTS_START_INDEX} from "@lodestar/params";
2
2
  import {ValidatorIndex, ssz} from "@lodestar/types";
3
3
  import {CachedBeaconStateElectra, getCachedBeaconState} from "../cache/stateCache.js";
4
4
  import {G2_POINT_AT_INFINITY} from "../constants/constants.js";
@@ -78,7 +78,9 @@ export function upgradeStateToElectra(stateDeneb: CachedBeaconStateDeneb): Cache
78
78
  stateElectraView.commit();
79
79
  const tmpElectraState = getCachedBeaconState(stateElectraView, stateDeneb);
80
80
  stateElectraView.exitBalanceToConsume = BigInt(getActivationExitChurnLimit(tmpElectraState.epochCtx));
81
- stateElectraView.consolidationBalanceToConsume = BigInt(getConsolidationChurnLimit(tmpElectraState.epochCtx));
81
+ stateElectraView.consolidationBalanceToConsume = BigInt(
82
+ getConsolidationChurnLimit(ForkSeq.electra, tmpElectraState.epochCtx)
83
+ );
82
84
 
83
85
  preActivation.sort((i0, i1) => {
84
86
  const res = validatorsArr[i0].activationEligibilityEpoch - validatorsArr[i1].activationEligibilityEpoch;
@@ -5,7 +5,7 @@ import {isValidDepositSignature} from "../block/processDeposit.js";
5
5
  import {applyDepositForBuilder} from "../block/processDepositRequest.js";
6
6
  import {getCachedBeaconState} from "../cache/stateCache.js";
7
7
  import {CachedBeaconStateFulu, CachedBeaconStateGloas} from "../types.js";
8
- import {isBuilderWithdrawalCredential} from "../util/gloas.js";
8
+ import {initializePtcWindow, isBuilderWithdrawalCredential} from "../util/gloas.js";
9
9
  import {isValidatorKnown} from "../util/index.js";
10
10
 
11
11
  /**
@@ -48,6 +48,9 @@ export function upgradeStateToGloas(stateFulu: CachedBeaconStateFulu): CachedBea
48
48
  stateGloasView.currentSyncCommittee = stateGloasCloned.currentSyncCommittee;
49
49
  stateGloasView.nextSyncCommittee = stateGloasCloned.nextSyncCommittee;
50
50
  stateGloasView.latestExecutionPayloadBid.blockHash = stateFulu.latestExecutionPayloadHeader.blockHash;
51
+ stateGloasView.latestExecutionPayloadBid.executionRequestsRoot = ssz.electra.ExecutionRequests.hashTreeRoot(
52
+ ssz.electra.ExecutionRequests.defaultValue()
53
+ );
51
54
  stateGloasView.nextWithdrawalIndex = stateGloasCloned.nextWithdrawalIndex;
52
55
  stateGloasView.nextWithdrawalValidatorIndex = stateGloasCloned.nextWithdrawalValidatorIndex;
53
56
  stateGloasView.historicalSummaries = stateGloasCloned.historicalSummaries;
@@ -61,6 +64,7 @@ export function upgradeStateToGloas(stateFulu: CachedBeaconStateFulu): CachedBea
61
64
  stateGloasView.pendingPartialWithdrawals = stateGloasCloned.pendingPartialWithdrawals;
62
65
  stateGloasView.pendingConsolidations = stateGloasCloned.pendingConsolidations;
63
66
  stateGloasView.proposerLookahead = stateGloasCloned.proposerLookahead;
67
+ stateGloasView.ptcWindow = ssz.gloas.PtcWindow.toViewDU(initializePtcWindow(stateFulu));
64
68
 
65
69
  for (let i = 0; i < SLOTS_PER_HISTORICAL_ROOT; i++) {
66
70
  stateGloasView.executionPayloadAvailability.set(i, true);
@@ -282,7 +282,7 @@ function processSlotsWithTransientCache(
282
282
  {
283
283
  const timer = metrics?.epochTransitionStepTime.startTimer({step: EpochTransitionStep.finalProcessEpoch});
284
284
  // last step to prepare epoch data that depends on the upgraded state, for example proposerLookahead of BeaconStateFulu
285
- postState.epochCtx.finalProcessEpoch(postState);
285
+ postState.epochCtx.finalProcessEpoch(postState, epochTransitionCache);
286
286
  timer?.();
287
287
  }
288
288
 
@@ -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 {ForkName, ForkSeq, SLOTS_PER_HISTORICAL_ROOT, isForkPostGloas} from "@lodestar/params";
4
+ import {ForkName, ForkSeq, SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
5
5
  import {
6
6
  BeaconBlock,
7
7
  BeaconState,
@@ -28,8 +28,7 @@ import {
28
28
  rewards,
29
29
  } from "@lodestar/types";
30
30
  import {Checkpoint, Fork} from "@lodestar/types/phase0";
31
- import {processExecutionPayloadEnvelope} from "../block/index.js";
32
- import {ProcessExecutionPayloadEnvelopeOpts} from "../block/processExecutionPayloadEnvelope.js";
31
+ import {applyParentExecutionPayload} from "../block/processParentExecutionPayload.js";
33
32
  import {VoluntaryExitValidity, getVoluntaryExitValidity} from "../block/processVoluntaryExit.js";
34
33
  import {getExpectedWithdrawals} from "../block/processWithdrawals.js";
35
34
  import {EffectiveBalanceIncrements} from "../cache/effectiveBalanceIncrements.js";
@@ -69,7 +68,7 @@ import {canBuilderCoverBid} from "../util/gloas.js";
69
68
  import {loadState} from "../util/loadState/loadState.js";
70
69
  import {getRandaoMix} from "../util/seed.js";
71
70
  import {getLatestWeakSubjectivityCheckpointEpoch} from "../util/weakSubjectivity.js";
72
- import {IBeaconStateView, IBeaconStateViewLatestFork} from "./interface.js";
71
+ import {IBeaconStateView, IBeaconStateViewGloas, IBeaconStateViewLatestFork, isStatePostGloas} from "./interface.js";
73
72
 
74
73
  export class BeaconStateView implements IBeaconStateViewLatestFork {
75
74
  private readonly config: BeaconConfig;
@@ -84,8 +83,6 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
84
83
  private _currentEpochParticipation: Uint8Array | null = null;
85
84
  // bellatrix
86
85
  private _latestExecutionPayloadHeader: ExecutionPayloadHeader | null = null;
87
- // Caches the cross-fork latestBlockHash value
88
- private _latestBlockHash: Bytes32 | null = null;
89
86
  // capella
90
87
  private _historicalSummaries: capella.HistoricalSummaries | null = null;
91
88
  // electra
@@ -218,9 +215,13 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
218
215
  // bellatrix
219
216
 
220
217
  get latestExecutionPayloadHeader(): ExecutionPayloadHeader {
221
- if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.bellatrix) {
218
+ const forkSeq = this.config.getForkSeq(this.cachedState.slot);
219
+ if (forkSeq < ForkSeq.bellatrix) {
222
220
  throw new Error("latestExecutionPayloadHeader is not available before Bellatrix");
223
221
  }
222
+ if (forkSeq >= ForkSeq.gloas) {
223
+ throw new Error("latestExecutionPayloadHeader is not available after Gloas");
224
+ }
224
225
 
225
226
  if (this._latestExecutionPayloadHeader === null) {
226
227
  this._latestExecutionPayloadHeader = (
@@ -231,30 +232,6 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
231
232
  return this._latestExecutionPayloadHeader;
232
233
  }
233
234
 
234
- /**
235
- * Cross-fork accessor for the execution block hash of the most recently included payload.
236
- * Pre-gloas: reads from latestExecutionPayloadHeader.blockHash.
237
- * Gloas+: reads the dedicated latestBlockHash field (EIP-7732).
238
- */
239
- get latestBlockHash(): Bytes32 {
240
- const forkSeq = this.config.getForkSeq(this.cachedState.slot);
241
- if (forkSeq < ForkSeq.bellatrix) {
242
- throw new Error("latestBlockHash is not available before Bellatrix");
243
- }
244
-
245
- if (this._latestBlockHash === null) {
246
- if (forkSeq >= ForkSeq.gloas) {
247
- this._latestBlockHash = (this.cachedState as CachedBeaconStateGloas).latestBlockHash;
248
- } else {
249
- this._latestBlockHash = (
250
- this.cachedState as CachedBeaconStateExecutions
251
- ).latestExecutionPayloadHeader.blockHash;
252
- }
253
- }
254
-
255
- return this._latestBlockHash;
256
- }
257
-
258
235
  /**
259
236
  * The execution block number of the most recently included payload.
260
237
  * Named payloadBlockNumber (not latestBlockNumber) to mirror ExecutionPayloadHeader.blockNumber pre-gloas.
@@ -365,6 +342,13 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
365
342
 
366
343
  // gloas
367
344
 
345
+ get latestBlockHash(): Bytes32 {
346
+ if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.gloas) {
347
+ throw new Error("latestBlockHash is not available before Gloas");
348
+ }
349
+ return (this.cachedState as CachedBeaconStateGloas).latestBlockHash;
350
+ }
351
+
368
352
  get executionPayloadAvailability(): BitArray {
369
353
  if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.gloas) {
370
354
  throw new Error("executionPayloadAvailability is not available before Gloas");
@@ -422,16 +406,44 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
422
406
  }
423
407
 
424
408
  /**
425
- * Return the index of the validator in the PTC committee for the given slot.
426
- * return -1 if validator is not in the PTC committee for the given slot.
409
+ * Return the PTCs for an epoch
427
410
  */
428
- getIndexInPayloadTimelinessCommittee(validatorIndex: ValidatorIndex, slot: Slot): number {
411
+ getEpochPTCs(epoch: Epoch): Uint32Array[] {
412
+ if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.gloas) {
413
+ throw new Error("PTC committees are not supported before Gloas");
414
+ }
415
+
416
+ const epochCtx = (this.cachedState as CachedBeaconStateGloas).epochCtx;
417
+ if (epoch === epochCtx.epoch) {
418
+ return epochCtx.payloadTimelinessCommittees;
419
+ }
420
+ if (epoch === epochCtx.nextEpoch) {
421
+ return epochCtx.nextPayloadTimelinessCommittees;
422
+ }
423
+ throw new Error(`PTC committees are not available for epoch=${epoch}`);
424
+ }
425
+ /**
426
+ * Return all positions of the validator in the PTC committee for the given slot.
427
+ *
428
+ * `compute_ptc` samples by effective balance and may place the same validator at multiple
429
+ * positions, so a validator can have more than one index. Returns an empty array if the
430
+ * validator is not in the PTC for the given slot.
431
+ *
432
+ * Spec: gloas/fork-choice.md#new-on_payload_attestation_message
433
+ */
434
+ getIndicesInPayloadTimelinessCommittee(validatorIndex: ValidatorIndex, slot: Slot): number[] {
429
435
  if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.gloas) {
430
436
  throw new Error("PTC committees are not supported before Gloas");
431
437
  }
432
438
 
433
439
  const ptcCommittee = (this.cachedState as CachedBeaconStateGloas).epochCtx.getPayloadTimelinessCommittee(slot);
434
- return ptcCommittee.indexOf(validatorIndex);
440
+ const indices: number[] = [];
441
+ for (let i = 0; i < ptcCommittee.length; i++) {
442
+ if (ptcCommittee[i] === validatorIndex) {
443
+ indices.push(i);
444
+ }
445
+ }
446
+ return indices;
435
447
  }
436
448
 
437
449
  // Shuffling and committees
@@ -719,7 +731,11 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
719
731
 
720
732
  // Serialization
721
733
 
722
- loadOtherState(stateBytes: Uint8Array, seedValidatorsBytes?: Uint8Array): IBeaconStateView {
734
+ loadOtherState(
735
+ stateBytes: Uint8Array,
736
+ seedValidatorsBytes?: Uint8Array,
737
+ opts?: {preloadValidatorsAndBalances?: boolean}
738
+ ): IBeaconStateView {
723
739
  const {state} = loadState(this.config, this.cachedState, stateBytes, seedValidatorsBytes);
724
740
 
725
741
  const cachedState = createCachedBeaconState(
@@ -734,9 +750,10 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
734
750
  }
735
751
  );
736
752
 
737
- // load all cache in order for consumers (usually regen.getState()) to process blocks faster
738
- cachedState.validators.getAllReadonlyValues();
739
- cachedState.balances.getAll();
753
+ if (opts?.preloadValidatorsAndBalances) {
754
+ cachedState.validators.getAllReadonlyValues();
755
+ cachedState.balances.getAll();
756
+ }
740
757
 
741
758
  return new BeaconStateView(cachedState);
742
759
  }
@@ -794,19 +811,22 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
794
811
  return new BeaconStateView(newState);
795
812
  }
796
813
 
797
- processExecutionPayloadEnvelope(
798
- signedEnvelope: gloas.SignedExecutionPayloadEnvelope,
799
- opts?: ProcessExecutionPayloadEnvelopeOpts
800
- ): BeaconStateView {
801
- const fork = this.config.getForkName(this.cachedState.slot);
802
- if (!isForkPostGloas(fork)) {
803
- throw Error(`processExecutionPayloadEnvelope is only available for gloas+ forks, got fork=${fork}`);
814
+ /**
815
+ * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.5/specs/gloas/validator.md#executionpayload
816
+ */
817
+ withParentPayloadApplied(executionRequests: electra.ExecutionRequests): IBeaconStateViewGloas {
818
+ if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.gloas) {
819
+ throw new Error("withParentPayloadApplied is not available before Gloas");
804
820
  }
805
- const postPayloadState = processExecutionPayloadEnvelope(
806
- this.cachedState as CachedBeaconStateGloas,
807
- signedEnvelope,
808
- opts
809
- );
810
- return new BeaconStateView(postPayloadState);
821
+ const stateCopy = this.cachedState.clone(true) as CachedBeaconStateGloas;
822
+
823
+ applyParentExecutionPayload(stateCopy, executionRequests);
824
+
825
+ const stateView = new BeaconStateView(stateCopy);
826
+ if (!isStatePostGloas(stateView)) {
827
+ throw new Error("Expected gloas state after clone");
828
+ }
829
+
830
+ return stateView;
811
831
  }
812
832
  }
@@ -41,7 +41,6 @@ import {
41
41
  rewards,
42
42
  } from "@lodestar/types";
43
43
  import {Checkpoint, Fork} from "@lodestar/types/phase0";
44
- import {ProcessExecutionPayloadEnvelopeOpts} from "../block/processExecutionPayloadEnvelope.js";
45
44
  import {VoluntaryExitValidity} from "../block/processVoluntaryExit.js";
46
45
  import {EffectiveBalanceIncrements} from "../cache/effectiveBalanceIncrements.js";
47
46
  import {EpochTransitionCacheOpts} from "../cache/epochTransitionCache.js";
@@ -138,7 +137,13 @@ export interface IBeaconStateView {
138
137
  isStateValidatorsNodesPopulated(): boolean;
139
138
 
140
139
  // Serialization
141
- loadOtherState(stateBytes: Uint8Array, seedValidatorsBytes?: Uint8Array): IBeaconStateView;
140
+ /** Set `preloadValidatorsAndBalances` only when the whole state will be consumed
141
+ * immediately (e.g. CP reload before block replay). */
142
+ loadOtherState(
143
+ stateBytes: Uint8Array,
144
+ seedValidatorsBytes?: Uint8Array,
145
+ opts?: {preloadValidatorsAndBalances?: boolean}
146
+ ): IBeaconStateView;
142
147
  toValue(): BeaconState;
143
148
  serialize(): Uint8Array;
144
149
  serializedSize(): number;
@@ -187,13 +192,6 @@ export interface IBeaconStateViewAltair extends IBeaconStateView {
187
192
  export interface IBeaconStateViewBellatrix extends IBeaconStateViewAltair {
188
193
  forkName: ForkPostBellatrix;
189
194
  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
195
  /**
198
196
  * The execution block number of the most recently included payload.
199
197
  * Named payloadBlockNumber (not latestBlockNumber) to mirror ExecutionPayloadHeader.blockNumber pre-gloas.
@@ -244,16 +242,25 @@ export interface IBeaconStateViewFulu extends IBeaconStateViewElectra {
244
242
  /** Gloas+ state fields — use isStatePostGloas() guard */
245
243
  export interface IBeaconStateViewGloas extends IBeaconStateViewFulu {
246
244
  forkName: ForkPostGloas;
245
+ /** Removed from BeaconState in gloas. Use `latestBlockHash` instead. */
246
+ latestExecutionPayloadHeader: never;
247
+ /** Removed from BeaconState in gloas. */
248
+ payloadBlockNumber: never;
249
+ latestBlockHash: Bytes32;
247
250
  executionPayloadAvailability: BitArray;
248
251
  latestExecutionPayloadBid: ExecutionPayloadBid;
249
252
  payloadExpectedWithdrawals: capella.Withdrawal[];
250
253
  getBuilder(index: BuilderIndex): gloas.Builder;
251
254
  canBuilderCoverBid(builderIndex: BuilderIndex, bidAmount: number): boolean;
252
- getIndexInPayloadTimelinessCommittee(validatorIndex: ValidatorIndex, slot: Slot): number;
253
- processExecutionPayloadEnvelope(
254
- signedEnvelope: gloas.SignedExecutionPayloadEnvelope,
255
- opts?: ProcessExecutionPayloadEnvelopeOpts
256
- ): IBeaconStateView;
255
+ getEpochPTCs(epoch: Epoch): Uint32Array[];
256
+ getIndicesInPayloadTimelinessCommittee(validatorIndex: ValidatorIndex, slot: Slot): number[];
257
+ /**
258
+ * Clone the state and apply parent execution payload effects.
259
+ * Used during block production and prepareNextSlot so that withdrawals and
260
+ * operation selection (e.g. voluntary exits) see the same post-apply state that the block
261
+ * processor will see at import.
262
+ */
263
+ withParentPayloadApplied(executionRequests: electra.ExecutionRequests): IBeaconStateViewGloas;
257
264
  }
258
265
 
259
266
  /**
@@ -262,7 +269,14 @@ export interface IBeaconStateViewGloas extends IBeaconStateViewFulu {
262
269
  * forkName as ForkName since the class wraps any fork's state.
263
270
  * Sub-interfaces retain their narrowed forkName discriminants for caller-side type guards.
264
271
  */
265
- export type IBeaconStateViewLatestFork = Omit<IBeaconStateViewGloas, "forkName"> & {forkName: ForkName};
272
+ export type IBeaconStateViewLatestFork = Omit<
273
+ IBeaconStateViewGloas,
274
+ "forkName" | "latestExecutionPayloadHeader" | "payloadBlockNumber"
275
+ > & {
276
+ forkName: ForkName;
277
+ latestExecutionPayloadHeader: ExecutionPayloadHeader;
278
+ payloadBlockNumber: number;
279
+ };
266
280
  export function isStatePostAltair(state: IBeaconStateView): state is IBeaconStateViewAltair {
267
281
  return isForkPostAltair(state.forkName);
268
282
  }
@@ -1,34 +1,21 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
- import {GENESIS_SLOT, ZERO_HASH} from "@lodestar/params";
2
+ import {ZERO_HASH} from "@lodestar/params";
3
3
  import {phase0, ssz} from "@lodestar/types";
4
4
  import {BeaconStateAllForks} from "../types.js";
5
- import {blockToHeader} from "./blockRoot.js";
6
5
  import {computeCheckpointEpochAtStateSlot} from "./epoch.js";
7
6
 
8
7
  export function computeAnchorCheckpoint(
9
- config: ChainForkConfig,
8
+ _config: ChainForkConfig,
10
9
  anchorState: BeaconStateAllForks
11
10
  ): {checkpoint: phase0.Checkpoint; blockHeader: phase0.BeaconBlockHeader} {
12
- let blockHeader: phase0.BeaconBlockHeader;
13
- let root: Uint8Array;
14
- const blockTypes = config.getForkTypes(anchorState.latestBlockHeader.slot);
15
-
16
- if (anchorState.latestBlockHeader.slot === GENESIS_SLOT) {
17
- const block = blockTypes.BeaconBlock.defaultValue();
18
- block.stateRoot = anchorState.hashTreeRoot();
19
- blockHeader = blockToHeader(config, block);
20
- root = ssz.phase0.BeaconBlockHeader.hashTreeRoot(blockHeader);
21
- } else {
22
- blockHeader = ssz.phase0.BeaconBlockHeader.clone(anchorState.latestBlockHeader);
23
- if (ssz.Root.equals(blockHeader.stateRoot, ZERO_HASH)) {
24
- blockHeader.stateRoot = anchorState.hashTreeRoot();
25
- }
26
- root = ssz.phase0.BeaconBlockHeader.hashTreeRoot(blockHeader);
11
+ const blockHeader = ssz.phase0.BeaconBlockHeader.clone(anchorState.latestBlockHeader);
12
+ if (ssz.Root.equals(blockHeader.stateRoot, ZERO_HASH)) {
13
+ blockHeader.stateRoot = anchorState.hashTreeRoot();
27
14
  }
28
15
 
29
16
  return {
30
17
  checkpoint: {
31
- root,
18
+ root: ssz.phase0.BeaconBlockHeader.hashTreeRoot(blockHeader),
32
19
  // the checkpoint epoch = computeEpochAtSlot(anchorState.slot) + 1 if slot is not at epoch boundary
33
20
  // this is similar to a process_slots() call
34
21
  epoch: computeCheckpointEpochAtStateSlot(anchorState.slot),
package/src/util/epoch.ts CHANGED
@@ -1,7 +1,13 @@
1
- import {EPOCHS_PER_SYNC_COMMITTEE_PERIOD, GENESIS_EPOCH, MAX_SEED_LOOKAHEAD, SLOTS_PER_EPOCH} from "@lodestar/params";
1
+ import {
2
+ EPOCHS_PER_SYNC_COMMITTEE_PERIOD,
3
+ ForkSeq,
4
+ GENESIS_EPOCH,
5
+ MAX_SEED_LOOKAHEAD,
6
+ SLOTS_PER_EPOCH,
7
+ } from "@lodestar/params";
2
8
  import {BeaconState, Epoch, Gwei, Slot, SyncPeriod} from "@lodestar/types";
3
9
  import {CachedBeaconStateElectra, CachedBeaconStateGloas} from "../types.js";
4
- import {getActivationExitChurnLimit, getConsolidationChurnLimit} from "./validator.js";
10
+ import {getActivationExitChurnLimit, getConsolidationChurnLimit, getExitChurnLimit} from "./validator.js";
5
11
 
6
12
  /**
7
13
  * Return the epoch number at the given slot.
@@ -45,8 +51,10 @@ export function computeExitEpochAndUpdateChurn(
45
51
  state: CachedBeaconStateElectra | CachedBeaconStateGloas,
46
52
  exitBalance: Gwei
47
53
  ): number {
54
+ const fork = state.config.getForkSeq(state.slot);
48
55
  let earliestExitEpoch = Math.max(state.earliestExitEpoch, computeActivationExitEpoch(state.epochCtx.epoch));
49
- const perEpochChurn = getActivationExitChurnLimit(state.epochCtx);
56
+ const perEpochChurn =
57
+ fork >= ForkSeq.gloas ? getExitChurnLimit(state.epochCtx) : getActivationExitChurnLimit(state.epochCtx);
50
58
 
51
59
  // New epoch for exits.
52
60
  let exitBalanceToConsume =
@@ -71,11 +79,12 @@ export function computeConsolidationEpochAndUpdateChurn(
71
79
  state: CachedBeaconStateElectra | CachedBeaconStateGloas,
72
80
  consolidationBalance: Gwei
73
81
  ): number {
82
+ const fork = state.config.getForkSeq(state.slot);
74
83
  let earliestConsolidationEpoch = Math.max(
75
84
  state.earliestConsolidationEpoch,
76
85
  computeActivationExitEpoch(state.epochCtx.epoch)
77
86
  );
78
- const perEpochConsolidationChurn = getConsolidationChurnLimit(state.epochCtx);
87
+ const perEpochConsolidationChurn = getConsolidationChurnLimit(fork, state.epochCtx);
79
88
 
80
89
  // New epoch for consolidations
81
90
  let consolidationBalanceToConsume =