@lodestar/state-transition 1.43.0-dev.4fb05c546d → 1.43.0-dev.4fe6b362c9

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/processDepositRequest.d.ts +3 -11
  9. package/lib/block/processDepositRequest.d.ts.map +1 -1
  10. package/lib/block/processDepositRequest.js +27 -35
  11. package/lib/block/processDepositRequest.js.map +1 -1
  12. package/lib/block/processParentExecutionPayload.d.ts +20 -0
  13. package/lib/block/processParentExecutionPayload.d.ts.map +1 -0
  14. package/lib/block/processParentExecutionPayload.js +101 -0
  15. package/lib/block/processParentExecutionPayload.js.map +1 -0
  16. package/lib/block/processWithdrawals.d.ts.map +1 -1
  17. package/lib/block/processWithdrawals.js +10 -4
  18. package/lib/block/processWithdrawals.js.map +1 -1
  19. package/lib/cache/epochCache.js +3 -3
  20. package/lib/cache/epochCache.js.map +1 -1
  21. package/lib/epoch/processPendingDeposits.d.ts.map +1 -1
  22. package/lib/epoch/processPendingDeposits.js +4 -2
  23. package/lib/epoch/processPendingDeposits.js.map +1 -1
  24. package/lib/lightClient/spec/index.d.ts +22 -0
  25. package/lib/lightClient/spec/index.d.ts.map +1 -0
  26. package/lib/lightClient/spec/index.js +58 -0
  27. package/lib/lightClient/spec/index.js.map +1 -0
  28. package/lib/lightClient/spec/isBetterUpdate.d.ts +23 -0
  29. package/lib/lightClient/spec/isBetterUpdate.d.ts.map +1 -0
  30. package/lib/lightClient/spec/isBetterUpdate.js +66 -0
  31. package/lib/lightClient/spec/isBetterUpdate.js.map +1 -0
  32. package/lib/lightClient/spec/processLightClientUpdate.d.ts +12 -0
  33. package/lib/lightClient/spec/processLightClientUpdate.d.ts.map +1 -0
  34. package/lib/lightClient/spec/processLightClientUpdate.js +80 -0
  35. package/lib/lightClient/spec/processLightClientUpdate.js.map +1 -0
  36. package/lib/lightClient/spec/store.d.ts +45 -0
  37. package/lib/lightClient/spec/store.d.ts.map +1 -0
  38. package/lib/lightClient/spec/store.js +56 -0
  39. package/lib/lightClient/spec/store.js.map +1 -0
  40. package/lib/lightClient/spec/utils.d.ts +47 -0
  41. package/lib/lightClient/spec/utils.d.ts.map +1 -0
  42. package/lib/lightClient/spec/utils.js +197 -0
  43. package/lib/lightClient/spec/utils.js.map +1 -0
  44. package/lib/lightClient/spec/validateLightClientBootstrap.d.ts +4 -0
  45. package/lib/lightClient/spec/validateLightClientBootstrap.d.ts.map +1 -0
  46. package/lib/lightClient/spec/validateLightClientBootstrap.js +22 -0
  47. package/lib/lightClient/spec/validateLightClientBootstrap.js.map +1 -0
  48. package/lib/lightClient/spec/validateLightClientUpdate.d.ts +5 -0
  49. package/lib/lightClient/spec/validateLightClientUpdate.d.ts.map +1 -0
  50. package/lib/lightClient/spec/validateLightClientUpdate.js +88 -0
  51. package/lib/lightClient/spec/validateLightClientUpdate.js.map +1 -0
  52. package/lib/signatureSets/executionPayloadEnvelope.js +1 -1
  53. package/lib/signatureSets/executionPayloadEnvelope.js.map +1 -1
  54. package/lib/signatureSets/index.d.ts +1 -0
  55. package/lib/signatureSets/index.d.ts.map +1 -1
  56. package/lib/signatureSets/index.js +1 -0
  57. package/lib/signatureSets/index.js.map +1 -1
  58. package/lib/signatureSets/proposerPreferences.d.ts +4 -0
  59. package/lib/signatureSets/proposerPreferences.d.ts.map +1 -0
  60. package/lib/signatureSets/proposerPreferences.js +8 -0
  61. package/lib/signatureSets/proposerPreferences.js.map +1 -0
  62. package/lib/slot/upgradeStateToElectra.d.ts.map +1 -1
  63. package/lib/slot/upgradeStateToElectra.js +2 -2
  64. package/lib/slot/upgradeStateToElectra.js.map +1 -1
  65. package/lib/slot/upgradeStateToGloas.d.ts.map +1 -1
  66. package/lib/slot/upgradeStateToGloas.js +34 -28
  67. package/lib/slot/upgradeStateToGloas.js.map +1 -1
  68. package/lib/stateView/beaconStateView.d.ts +20 -7
  69. package/lib/stateView/beaconStateView.d.ts.map +1 -1
  70. package/lib/stateView/beaconStateView.js +52 -16
  71. package/lib/stateView/beaconStateView.js.map +1 -1
  72. package/lib/stateView/interface.d.ts +14 -4
  73. package/lib/stateView/interface.d.ts.map +1 -1
  74. package/lib/stateView/interface.js.map +1 -1
  75. package/lib/util/computeAnchorCheckpoint.d.ts +1 -1
  76. package/lib/util/computeAnchorCheckpoint.d.ts.map +1 -1
  77. package/lib/util/computeAnchorCheckpoint.js +6 -19
  78. package/lib/util/computeAnchorCheckpoint.js.map +1 -1
  79. package/lib/util/epoch.d.ts.map +1 -1
  80. package/lib/util/epoch.js +6 -4
  81. package/lib/util/epoch.js.map +1 -1
  82. package/lib/util/gloas.d.ts +0 -1
  83. package/lib/util/gloas.d.ts.map +1 -1
  84. package/lib/util/gloas.js +0 -3
  85. package/lib/util/gloas.js.map +1 -1
  86. package/lib/util/index.d.ts +1 -0
  87. package/lib/util/index.d.ts.map +1 -1
  88. package/lib/util/index.js +1 -0
  89. package/lib/util/index.js.map +1 -1
  90. package/lib/util/loadState/loadState.js +4 -4
  91. package/lib/util/loadState/loadState.js.map +1 -1
  92. package/lib/util/pendingDepositsLookup.d.ts +40 -0
  93. package/lib/util/pendingDepositsLookup.d.ts.map +1 -0
  94. package/lib/util/pendingDepositsLookup.js +84 -0
  95. package/lib/util/pendingDepositsLookup.js.map +1 -0
  96. package/lib/util/shuffling.d.ts +6 -5
  97. package/lib/util/shuffling.d.ts.map +1 -1
  98. package/lib/util/shuffling.js +13 -15
  99. package/lib/util/shuffling.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 +13 -8
  105. package/src/block/index.ts +12 -4
  106. package/src/block/processConsolidationRequest.ts +2 -1
  107. package/src/block/processDepositRequest.ts +29 -47
  108. package/src/block/processParentExecutionPayload.ts +117 -0
  109. package/src/block/processWithdrawals.ts +12 -4
  110. package/src/cache/epochCache.ts +3 -3
  111. package/src/epoch/processPendingDeposits.ts +5 -2
  112. package/src/lightClient/spec/index.ts +101 -0
  113. package/src/lightClient/spec/isBetterUpdate.ts +94 -0
  114. package/src/lightClient/spec/processLightClientUpdate.ts +119 -0
  115. package/src/lightClient/spec/store.ts +106 -0
  116. package/src/lightClient/spec/utils.ts +317 -0
  117. package/src/lightClient/spec/validateLightClientBootstrap.ts +39 -0
  118. package/src/lightClient/spec/validateLightClientUpdate.ts +145 -0
  119. package/src/signatureSets/executionPayloadEnvelope.ts +1 -1
  120. package/src/signatureSets/index.ts +1 -0
  121. package/src/signatureSets/proposerPreferences.ts +12 -0
  122. package/src/slot/upgradeStateToElectra.ts +4 -2
  123. package/src/slot/upgradeStateToGloas.ts +44 -44
  124. package/src/stateView/beaconStateView.ts +60 -25
  125. package/src/stateView/interface.ts +16 -7
  126. package/src/util/computeAnchorCheckpoint.ts +6 -19
  127. package/src/util/epoch.ts +13 -4
  128. package/src/util/gloas.ts +0 -4
  129. package/src/util/index.ts +1 -0
  130. package/src/util/loadState/loadState.ts +4 -4
  131. package/src/util/pendingDepositsLookup.ts +105 -0
  132. package/src/util/shuffling.ts +17 -15
  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;
@@ -1,12 +1,12 @@
1
1
  import {SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
2
2
  import {ssz} from "@lodestar/types";
3
- import {toHex} from "@lodestar/utils";
4
- import {isValidDepositSignature} from "../block/processDeposit.js";
3
+ import {toPubkeyHex} from "@lodestar/utils";
5
4
  import {applyDepositForBuilder} from "../block/processDepositRequest.js";
6
5
  import {getCachedBeaconState} from "../cache/stateCache.js";
7
6
  import {CachedBeaconStateFulu, CachedBeaconStateGloas} from "../types.js";
8
7
  import {initializePtcWindow, isBuilderWithdrawalCredential} from "../util/gloas.js";
9
8
  import {isValidatorKnown} from "../util/index.js";
9
+ import {PendingDepositsLookup} from "../util/pendingDepositsLookup.js";
10
10
 
11
11
  /**
12
12
  * Upgrade a state from Fulu to Gloas.
@@ -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;
@@ -86,63 +89,60 @@ export function upgradeStateToGloas(stateFulu: CachedBeaconStateFulu): CachedBea
86
89
  * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.2/specs/gloas/fork.md#new-onboard_builders_from_pending_deposits
87
90
  */
88
91
  function onboardBuildersFromPendingDeposits(state: CachedBeaconStateGloas): void {
89
- // Track pubkeys of new validators to keep their deposits pending
90
- const validatorPubkeys = new Set<string>();
91
-
92
92
  // Track pubkeys of new builders added when applying deposits
93
93
  const builderPubkeys = new Set<string>();
94
94
 
95
- const remainingPendingDeposits = ssz.electra.PendingDeposits.defaultViewDU();
95
+ const pendingDeposits = ssz.electra.PendingDeposits.defaultViewDU();
96
+ const pendingDepositsLookup = PendingDepositsLookup.buildEmpty();
97
+
96
98
  for (let i = 0; i < state.pendingDeposits.length; i++) {
97
99
  const deposit = state.pendingDeposits.getReadonly(i);
98
100
 
99
101
  const validatorIndex = state.epochCtx.getValidatorIndex(deposit.pubkey);
100
- const pubkeyHex = toHex(deposit.pubkey);
102
+ const pubkeyHex = toPubkeyHex(deposit.pubkey);
101
103
 
102
- // Deposits for existing validators stay in pending queue
103
- if (isValidatorKnown(state, validatorIndex) || validatorPubkeys.has(pubkeyHex)) {
104
- remainingPendingDeposits.push(deposit);
104
+ // Deposits for existing validators stay in the pending queue
105
+ if (isValidatorKnown(state, validatorIndex)) {
106
+ pendingDeposits.push(deposit);
107
+ pendingDepositsLookup.add(deposit, pubkeyHex);
105
108
  continue;
106
109
  }
107
110
 
108
- // If the pubkey is associated with a builder that was created in a previous iteration
109
- // or it is a builder deposit, try to apply the deposit to the new/existing builder
110
- const isExistingBuilder = builderPubkeys.has(pubkeyHex);
111
- const hasBuilderCredentials = isBuilderWithdrawalCredential(deposit.withdrawalCredentials);
112
- if (isExistingBuilder || hasBuilderCredentials) {
113
- const buildersLenBefore = state.builders.length;
114
- applyDepositForBuilder(
115
- state,
116
- deposit.pubkey,
117
- deposit.withdrawalCredentials,
118
- deposit.amount,
119
- deposit.signature,
120
- deposit.slot
121
- );
122
- // Track newly added builders for subsequent iterations
123
- if (!isExistingBuilder && state.builders.length > buildersLenBefore) {
124
- builderPubkeys.add(pubkeyHex);
111
+ // `applyDepositForBuilder` can mutate the state and add a builder to the registry, so
112
+ // the set of builder pubkeys must be recomputed each iteration. `builderPubkeys` stands
113
+ // in for the spec's `[b.pubkey for b in state.builders]`: `state.builders` starts empty
114
+ // at the fork, so every builder is one added in a previous iteration of this loop.
115
+ if (!builderPubkeys.has(pubkeyHex)) {
116
+ // Deposits for non-builders stay in the pending queue. If there is a valid pending
117
+ // deposit for a new validator with this pubkey, keep this deposit in the pending
118
+ // queue to be applied to that validator later.
119
+ if (!isBuilderWithdrawalCredential(deposit.withdrawalCredentials)) {
120
+ pendingDeposits.push(deposit);
121
+ pendingDepositsLookup.add(deposit, pubkeyHex);
122
+ continue;
123
+ }
124
+ if (pendingDepositsLookup.hasPendingValidator(state.config, pubkeyHex)) {
125
+ pendingDeposits.push(deposit);
126
+ pendingDepositsLookup.add(deposit, pubkeyHex);
127
+ continue;
125
128
  }
126
- continue;
127
129
  }
128
130
 
129
- // If there is a pending deposit for a new validator that has a valid signature, track the
130
- // pubkey so that subsequent builder deposits for the same pubkey stay in pending (applied to
131
- // the validator later) rather than creating a builder. Deposits with invalid signatures are
132
- // dropped here since they would fail in apply_pending_deposit anyway.
133
- if (
134
- isValidDepositSignature(
135
- state.config,
136
- deposit.pubkey,
137
- deposit.withdrawalCredentials,
138
- deposit.amount,
139
- deposit.signature
140
- )
141
- ) {
142
- validatorPubkeys.add(pubkeyHex);
143
- remainingPendingDeposits.push(deposit);
131
+ const buildersLenBefore = state.builders.length;
132
+ // TODO GLOAS: handle 20k 1ETH deposits on time
133
+ // there is a note in the spec https://github.com/ethereum/consensus-specs/pull/5227
134
+ applyDepositForBuilder(
135
+ state,
136
+ deposit.pubkey,
137
+ deposit.withdrawalCredentials,
138
+ deposit.amount,
139
+ deposit.signature,
140
+ deposit.slot
141
+ );
142
+ if (state.builders.length > buildersLenBefore) {
143
+ builderPubkeys.add(pubkeyHex);
144
144
  }
145
145
  }
146
146
 
147
- state.pendingDeposits = remainingPendingDeposits;
147
+ state.pendingDeposits = pendingDeposits;
148
148
  }
@@ -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;
@@ -407,16 +406,44 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
407
406
  }
408
407
 
409
408
  /**
410
- * Return the index of the validator in the PTC committee for the given slot.
411
- * return -1 if validator is not in the PTC committee for the given slot.
409
+ * Return the PTCs for an epoch
412
410
  */
413
- 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[] {
414
435
  if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.gloas) {
415
436
  throw new Error("PTC committees are not supported before Gloas");
416
437
  }
417
438
 
418
439
  const ptcCommittee = (this.cachedState as CachedBeaconStateGloas).epochCtx.getPayloadTimelinessCommittee(slot);
419
- 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;
420
447
  }
421
448
 
422
449
  // Shuffling and committees
@@ -704,7 +731,11 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
704
731
 
705
732
  // Serialization
706
733
 
707
- loadOtherState(stateBytes: Uint8Array, seedValidatorsBytes?: Uint8Array): IBeaconStateView {
734
+ loadOtherState(
735
+ stateBytes: Uint8Array,
736
+ seedValidatorsBytes?: Uint8Array,
737
+ opts?: {preloadValidatorsAndBalances?: boolean}
738
+ ): IBeaconStateView {
708
739
  const {state} = loadState(this.config, this.cachedState, stateBytes, seedValidatorsBytes);
709
740
 
710
741
  const cachedState = createCachedBeaconState(
@@ -719,9 +750,10 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
719
750
  }
720
751
  );
721
752
 
722
- // load all cache in order for consumers (usually regen.getState()) to process blocks faster
723
- cachedState.validators.getAllReadonlyValues();
724
- cachedState.balances.getAll();
753
+ if (opts?.preloadValidatorsAndBalances) {
754
+ cachedState.validators.getAllReadonlyValues();
755
+ cachedState.balances.getAll();
756
+ }
725
757
 
726
758
  return new BeaconStateView(cachedState);
727
759
  }
@@ -779,19 +811,22 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
779
811
  return new BeaconStateView(newState);
780
812
  }
781
813
 
782
- processExecutionPayloadEnvelope(
783
- signedEnvelope: gloas.SignedExecutionPayloadEnvelope,
784
- opts?: ProcessExecutionPayloadEnvelopeOpts
785
- ): BeaconStateView {
786
- const fork = this.config.getForkName(this.cachedState.slot);
787
- if (!isForkPostGloas(fork)) {
788
- 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");
789
820
  }
790
- const postPayloadState = processExecutionPayloadEnvelope(
791
- this.cachedState as CachedBeaconStateGloas,
792
- signedEnvelope,
793
- opts
794
- );
795
- 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;
796
831
  }
797
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;
@@ -247,11 +252,15 @@ export interface IBeaconStateViewGloas extends IBeaconStateViewFulu {
247
252
  payloadExpectedWithdrawals: capella.Withdrawal[];
248
253
  getBuilder(index: BuilderIndex): gloas.Builder;
249
254
  canBuilderCoverBid(builderIndex: BuilderIndex, bidAmount: number): boolean;
250
- getIndexInPayloadTimelinessCommittee(validatorIndex: ValidatorIndex, slot: Slot): number;
251
- processExecutionPayloadEnvelope(
252
- signedEnvelope: gloas.SignedExecutionPayloadEnvelope,
253
- opts?: ProcessExecutionPayloadEnvelopeOpts
254
- ): 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;
255
264
  }
256
265
 
257
266
  /**
@@ -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 =
package/src/util/gloas.ts CHANGED
@@ -172,10 +172,6 @@ export function isAttestationSameSlotRootCache(rootCache: RootCache, data: Attes
172
172
  return isMatchingBlockRoot && isCurrentBlockRoot;
173
173
  }
174
174
 
175
- export function isParentBlockFull(state: CachedBeaconStateGloas): boolean {
176
- return byteArrayEquals(state.latestExecutionPayloadBid.blockHash, state.latestBlockHash);
177
- }
178
-
179
175
  export function initializePtcWindow(state: CachedBeaconStateFulu): Uint32Array[] {
180
176
  const ptcWindow: Uint32Array[] = Array.from({length: SLOTS_PER_EPOCH}, () => new Uint32Array(PTC_SIZE));
181
177
  const currentEpoch = state.epochCtx.epoch;
package/src/util/index.ts CHANGED
@@ -18,6 +18,7 @@ export * from "./genesis.js";
18
18
  export * from "./gloas.js";
19
19
  export * from "./interop.js";
20
20
  export * from "./loadState/index.js";
21
+ export * from "./pendingDepositsLookup.js";
21
22
  export * from "./rootCache.js";
22
23
  export * from "./seed.js";
23
24
  export * from "./shuffling.js";
@@ -110,8 +110,8 @@ function loadInactivityScores(
110
110
  seedState: BeaconStateAltair,
111
111
  inactivityScoresBytes: Uint8Array
112
112
  ): void {
113
- // migratedState starts with the same inactivityScores to seed state
114
- migratedState.inactivityScores = seedState.inactivityScores.clone();
113
+ // true = do not transfer cache
114
+ migratedState.inactivityScores = seedState.inactivityScores.clone(true);
115
115
  const oldValidator = migratedState.inactivityScores.length;
116
116
  // UintNum64 = 8 bytes
117
117
  const newValidator = inactivityScoresBytes.length / 8;
@@ -187,8 +187,8 @@ function loadValidators(
187
187
  const newValidatorCount = Math.floor(newValidatorsBytes.length / VALIDATOR_BYTES_SIZE);
188
188
  const isMoreValidator = newValidatorCount >= seedValidatorCount;
189
189
  const minValidatorCount = Math.min(seedValidatorCount, newValidatorCount);
190
- // migrated state starts with the same validators to seed state
191
- migratedState.validators = seedState.validators.clone();
190
+ // true = do not transfer cache
191
+ migratedState.validators = seedState.validators.clone(true);
192
192
  // 80% of validators serialization time comes from memory allocation
193
193
  // seedStateValidatorsBytes is an optimization at beacon-node side to avoid memory allocation here
194
194
  const seedValidatorsBytes = seedStateValidatorsBytes ?? seedState.validators.serialize();