@lodestar/state-transition 1.41.0-dev.702f7932c2 → 1.41.0-dev.95cf2edc4c

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 (159) hide show
  1. package/lib/block/isValidIndexedAttestation.d.ts +3 -3
  2. package/lib/block/isValidIndexedAttestation.d.ts.map +1 -1
  3. package/lib/block/isValidIndexedAttestation.js +4 -4
  4. package/lib/block/isValidIndexedAttestation.js.map +1 -1
  5. package/lib/block/isValidIndexedPayloadAttestation.js +1 -1
  6. package/lib/block/isValidIndexedPayloadAttestation.js.map +1 -1
  7. package/lib/block/processAttestationPhase0.js +1 -1
  8. package/lib/block/processAttestationPhase0.js.map +1 -1
  9. package/lib/block/processAttestationsAltair.js +1 -1
  10. package/lib/block/processAttestationsAltair.js.map +1 -1
  11. package/lib/block/processAttesterSlashing.d.ts +2 -2
  12. package/lib/block/processAttesterSlashing.d.ts.map +1 -1
  13. package/lib/block/processAttesterSlashing.js +3 -3
  14. package/lib/block/processAttesterSlashing.js.map +1 -1
  15. package/lib/block/processDepositRequest.d.ts +1 -1
  16. package/lib/block/processDepositRequest.d.ts.map +1 -1
  17. package/lib/block/processDepositRequest.js +6 -5
  18. package/lib/block/processDepositRequest.js.map +1 -1
  19. package/lib/block/processExecutionPayloadBid.d.ts.map +1 -1
  20. package/lib/block/processExecutionPayloadBid.js +5 -0
  21. package/lib/block/processExecutionPayloadBid.js.map +1 -1
  22. package/lib/block/processExecutionPayloadEnvelope.js +5 -10
  23. package/lib/block/processExecutionPayloadEnvelope.js.map +1 -1
  24. package/lib/block/processProposerSlashing.d.ts +2 -2
  25. package/lib/block/processProposerSlashing.d.ts.map +1 -1
  26. package/lib/block/processProposerSlashing.js +3 -3
  27. package/lib/block/processProposerSlashing.js.map +1 -1
  28. package/lib/block/processRandao.js +1 -1
  29. package/lib/block/processRandao.js.map +1 -1
  30. package/lib/block/processSyncCommittee.js +1 -1
  31. package/lib/block/processSyncCommittee.js.map +1 -1
  32. package/lib/block/processVoluntaryExit.js +1 -1
  33. package/lib/block/processVoluntaryExit.js.map +1 -1
  34. package/lib/block/processWithdrawalRequest.js +2 -2
  35. package/lib/block/processWithdrawalRequest.js.map +1 -1
  36. package/lib/block/processWithdrawals.d.ts.map +1 -1
  37. package/lib/block/processWithdrawals.js +9 -1
  38. package/lib/block/processWithdrawals.js.map +1 -1
  39. package/lib/cache/epochCache.d.ts +9 -18
  40. package/lib/cache/epochCache.d.ts.map +1 -1
  41. package/lib/cache/epochCache.js +28 -41
  42. package/lib/cache/epochCache.js.map +1 -1
  43. package/lib/cache/pubkeyCache.d.ts +21 -6
  44. package/lib/cache/pubkeyCache.d.ts.map +1 -1
  45. package/lib/cache/pubkeyCache.js +39 -14
  46. package/lib/cache/pubkeyCache.js.map +1 -1
  47. package/lib/cache/stateCache.d.ts +1 -1
  48. package/lib/cache/stateCache.d.ts.map +1 -1
  49. package/lib/cache/stateCache.js +3 -7
  50. package/lib/cache/stateCache.js.map +1 -1
  51. package/lib/cache/syncCommitteeCache.d.ts +3 -2
  52. package/lib/cache/syncCommitteeCache.d.ts.map +1 -1
  53. package/lib/cache/syncCommitteeCache.js +4 -4
  54. package/lib/cache/syncCommitteeCache.js.map +1 -1
  55. package/lib/index.d.ts +3 -1
  56. package/lib/index.d.ts.map +1 -1
  57. package/lib/index.js +2 -1
  58. package/lib/index.js.map +1 -1
  59. package/lib/lightClient/proofs.d.ts +10 -0
  60. package/lib/lightClient/proofs.d.ts.map +1 -0
  61. package/lib/lightClient/proofs.js +63 -0
  62. package/lib/lightClient/proofs.js.map +1 -0
  63. package/lib/lightClient/types.d.ts +34 -0
  64. package/lib/lightClient/types.d.ts.map +1 -0
  65. package/lib/lightClient/types.js +2 -0
  66. package/lib/lightClient/types.js.map +1 -0
  67. package/lib/rewards/attestationsRewards.d.ts +2 -2
  68. package/lib/rewards/attestationsRewards.d.ts.map +1 -1
  69. package/lib/rewards/attestationsRewards.js +4 -4
  70. package/lib/rewards/attestationsRewards.js.map +1 -1
  71. package/lib/rewards/syncCommitteeRewards.d.ts +2 -2
  72. package/lib/rewards/syncCommitteeRewards.d.ts.map +1 -1
  73. package/lib/rewards/syncCommitteeRewards.js +5 -2
  74. package/lib/rewards/syncCommitteeRewards.js.map +1 -1
  75. package/lib/signatureSets/indexedPayloadAttestation.d.ts +3 -4
  76. package/lib/signatureSets/indexedPayloadAttestation.d.ts.map +1 -1
  77. package/lib/signatureSets/indexedPayloadAttestation.js +4 -4
  78. package/lib/signatureSets/indexedPayloadAttestation.js.map +1 -1
  79. package/lib/signatureSets/proposer.d.ts +2 -2
  80. package/lib/signatureSets/proposer.d.ts.map +1 -1
  81. package/lib/signatureSets/proposer.js +2 -2
  82. package/lib/signatureSets/proposer.js.map +1 -1
  83. package/lib/signatureSets/randao.d.ts +2 -2
  84. package/lib/signatureSets/randao.d.ts.map +1 -1
  85. package/lib/signatureSets/randao.js +2 -2
  86. package/lib/signatureSets/randao.js.map +1 -1
  87. package/lib/signatureSets/voluntaryExits.d.ts +2 -2
  88. package/lib/signatureSets/voluntaryExits.d.ts.map +1 -1
  89. package/lib/signatureSets/voluntaryExits.js +2 -2
  90. package/lib/signatureSets/voluntaryExits.js.map +1 -1
  91. package/lib/slot/upgradeStateToGloas.d.ts.map +1 -1
  92. package/lib/slot/upgradeStateToGloas.js +50 -0
  93. package/lib/slot/upgradeStateToGloas.js.map +1 -1
  94. package/lib/stateTransition.d.ts +2 -1
  95. package/lib/stateTransition.d.ts.map +1 -1
  96. package/lib/stateTransition.js +2 -1
  97. package/lib/stateTransition.js.map +1 -1
  98. package/lib/stateView/beaconStateView.d.ts +144 -0
  99. package/lib/stateView/beaconStateView.d.ts.map +1 -0
  100. package/lib/stateView/beaconStateView.js +496 -0
  101. package/lib/stateView/beaconStateView.js.map +1 -0
  102. package/lib/stateView/index.d.ts +3 -0
  103. package/lib/stateView/index.d.ts.map +1 -0
  104. package/lib/stateView/index.js +3 -0
  105. package/lib/stateView/index.js.map +1 -0
  106. package/lib/stateView/interface.d.ts +118 -0
  107. package/lib/stateView/interface.d.ts.map +1 -0
  108. package/lib/stateView/interface.js +2 -0
  109. package/lib/stateView/interface.js.map +1 -0
  110. package/lib/util/gloas.d.ts.map +1 -1
  111. package/lib/util/gloas.js +10 -3
  112. package/lib/util/gloas.js.map +1 -1
  113. package/lib/util/seed.d.ts +20 -4
  114. package/lib/util/seed.d.ts.map +1 -1
  115. package/lib/util/seed.js +80 -8
  116. package/lib/util/seed.js.map +1 -1
  117. package/lib/util/signatureSets.d.ts +7 -7
  118. package/lib/util/signatureSets.d.ts.map +1 -1
  119. package/lib/util/signatureSets.js +18 -12
  120. package/lib/util/signatureSets.js.map +1 -1
  121. package/lib/util/weakSubjectivity.js +1 -1
  122. package/lib/util/weakSubjectivity.js.map +1 -1
  123. package/package.json +7 -7
  124. package/src/block/isValidIndexedAttestation.ts +5 -5
  125. package/src/block/isValidIndexedPayloadAttestation.ts +2 -2
  126. package/src/block/processAttestationPhase0.ts +1 -1
  127. package/src/block/processAttestationsAltair.ts +1 -1
  128. package/src/block/processAttesterSlashing.ts +4 -4
  129. package/src/block/processDepositRequest.ts +8 -5
  130. package/src/block/processExecutionPayloadBid.ts +8 -0
  131. package/src/block/processExecutionPayloadEnvelope.ts +5 -16
  132. package/src/block/processProposerSlashing.ts +4 -4
  133. package/src/block/processRandao.ts +1 -1
  134. package/src/block/processSyncCommittee.ts +1 -1
  135. package/src/block/processVoluntaryExit.ts +1 -1
  136. package/src/block/processWithdrawalRequest.ts +2 -2
  137. package/src/block/processWithdrawals.ts +10 -1
  138. package/src/cache/epochCache.ts +41 -55
  139. package/src/cache/pubkeyCache.ts +62 -21
  140. package/src/cache/stateCache.ts +4 -8
  141. package/src/cache/syncCommitteeCache.ts +4 -5
  142. package/src/index.ts +3 -1
  143. package/src/lightClient/proofs.ts +83 -0
  144. package/src/lightClient/types.ts +33 -0
  145. package/src/rewards/attestationsRewards.ts +5 -5
  146. package/src/rewards/syncCommitteeRewards.ts +6 -5
  147. package/src/signatureSets/indexedPayloadAttestation.ts +4 -6
  148. package/src/signatureSets/proposer.ts +3 -3
  149. package/src/signatureSets/randao.ts +3 -7
  150. package/src/signatureSets/voluntaryExits.ts +3 -3
  151. package/src/slot/upgradeStateToGloas.ts +74 -0
  152. package/src/stateTransition.ts +2 -1
  153. package/src/stateView/beaconStateView.ts +744 -0
  154. package/src/stateView/index.ts +2 -0
  155. package/src/stateView/interface.ts +196 -0
  156. package/src/util/gloas.ts +11 -3
  157. package/src/util/seed.ts +106 -18
  158. package/src/util/signatureSets.ts +23 -17
  159. package/src/util/weakSubjectivity.ts +1 -1
@@ -14,7 +14,8 @@ export function applyDepositForBuilder(
14
14
  pubkey: BLSPubkey,
15
15
  withdrawalCredentials: Bytes32,
16
16
  amount: UintNum64,
17
- signature: Bytes32
17
+ signature: Bytes32,
18
+ slot: UintNum64
18
19
  ): void {
19
20
  const builderIndex = findBuilderIndexByPubkey(state, pubkey);
20
21
 
@@ -25,7 +26,7 @@ export function applyDepositForBuilder(
25
26
  } else {
26
27
  // New builder - verify signature and add to registry
27
28
  if (isValidDepositSignature(state.config, pubkey, withdrawalCredentials, amount, signature)) {
28
- addBuilderToRegistry(state, pubkey, withdrawalCredentials, amount);
29
+ addBuilderToRegistry(state, pubkey, withdrawalCredentials, amount, slot);
29
30
  }
30
31
  }
31
32
  }
@@ -38,9 +39,11 @@ function addBuilderToRegistry(
38
39
  state: CachedBeaconStateGloas,
39
40
  pubkey: BLSPubkey,
40
41
  withdrawalCredentials: Bytes32,
41
- amount: UintNum64
42
+ amount: UintNum64,
43
+ slot: UintNum64
42
44
  ): void {
43
45
  const currentEpoch = computeEpochAtSlot(state.slot);
46
+ const depositEpoch = computeEpochAtSlot(slot);
44
47
 
45
48
  // Try to find a reusable slot from an exited builder with zero balance
46
49
  let builderIndex = state.builders.length;
@@ -58,7 +61,7 @@ function addBuilderToRegistry(
58
61
  version: withdrawalCredentials[0],
59
62
  executionAddress: withdrawalCredentials.subarray(12),
60
63
  balance: amount,
61
- depositEpoch: currentEpoch,
64
+ depositEpoch: depositEpoch,
62
65
  withdrawableEpoch: FAR_FUTURE_EPOCH,
63
66
  });
64
67
 
@@ -93,7 +96,7 @@ export function processDepositRequest(
93
96
  // Route to builder if it's an existing builder OR has builder prefix and is not a validator
94
97
  if (isBuilder || (isBuilderPrefix && !isValidator)) {
95
98
  // Apply builder deposits immediately
96
- applyDepositForBuilder(stateGloas, pubkey, withdrawalCredentials, amount, signature);
99
+ applyDepositForBuilder(stateGloas, pubkey, withdrawalCredentials, amount, signature, state.slot);
97
100
  return;
98
101
  }
99
102
  }
@@ -63,6 +63,14 @@ export function processExecutionPayloadBid(state: CachedBeaconStateGloas, block:
63
63
  throw Error(`Prev randao ${toHex(bid.prevRandao)} of bid does not match state's randao mix ${toHex(stateRandao)}`);
64
64
  }
65
65
 
66
+ // Verify commitments are under limit
67
+ const maxBlobsPerBlock = state.config.getMaxBlobsPerBlock(state.epochCtx.epoch);
68
+ if (bid.blobKzgCommitments.length > maxBlobsPerBlock) {
69
+ throw Error(
70
+ `Kzg commitments exceed limit commitments.length=${bid.blobKzgCommitments.length} limit=${maxBlobsPerBlock}`
71
+ );
72
+ }
73
+
66
74
  if (amount > 0) {
67
75
  const pendingPaymentView = ssz.gloas.BuilderPendingPayment.toViewDU({
68
76
  weight: 0,
@@ -96,13 +96,6 @@ function validateExecutionPayloadEnvelope(
96
96
  );
97
97
  }
98
98
 
99
- const envelopeKzgRoot = ssz.deneb.BlobKzgCommitments.hashTreeRoot(envelope.blobKzgCommitments);
100
- if (!byteArrayEquals(committedBid.blobKzgCommitmentsRoot, envelopeKzgRoot)) {
101
- throw new Error(
102
- `Kzg commitment root mismatch between envelope and committed bid envelope=${toRootHex(envelopeKzgRoot)} committedBid=${toRootHex(committedBid.blobKzgCommitmentsRoot)}`
103
- );
104
- }
105
-
106
99
  if (!byteArrayEquals(committedBid.prevRandao, payload.prevRandao)) {
107
100
  throw new Error(
108
101
  `Prev randao mismatch between committed bid and payload committedBid=${toHex(committedBid.prevRandao)} payload=${toHex(payload.prevRandao)}`
@@ -146,14 +139,6 @@ function validateExecutionPayloadEnvelope(
146
139
  );
147
140
  }
148
141
 
149
- // Verify commitments are under limit
150
- const maxBlobsPerBlock = state.config.getMaxBlobsPerBlock(state.epochCtx.epoch);
151
- if (envelope.blobKzgCommitments.length > maxBlobsPerBlock) {
152
- throw new Error(
153
- `Kzg commitments exceed limit commitment.length=${envelope.blobKzgCommitments.length} limit=${maxBlobsPerBlock}`
154
- );
155
- }
156
-
157
142
  // Skipped: Verify the execution payload is valid
158
143
  }
159
144
 
@@ -171,7 +156,11 @@ function verifyExecutionPayloadEnvelopeSignature(
171
156
 
172
157
  if (builderIndex === BUILDER_INDEX_SELF_BUILD) {
173
158
  const validatorIndex = state.latestBlockHeader.proposerIndex;
174
- publicKey = state.epochCtx.index2pubkey[validatorIndex];
159
+ const proposerPubkey = state.epochCtx.pubkeyCache.get(validatorIndex);
160
+ if (!proposerPubkey) {
161
+ return false;
162
+ }
163
+ publicKey = proposerPubkey;
175
164
  } else {
176
165
  publicKey = PublicKey.fromBytes(state.builders.getReadonly(builderIndex).pubkey);
177
166
  }
@@ -2,7 +2,7 @@ import {BeaconConfig} from "@lodestar/config";
2
2
  import {ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params";
3
3
  import {Slot, phase0, ssz} from "@lodestar/types";
4
4
  import {Validator} from "@lodestar/types/phase0";
5
- import {Index2PubkeyCache} from "../cache/pubkeyCache.js";
5
+ import {PubkeyCache} from "../cache/pubkeyCache.js";
6
6
  import {getProposerSlashingSignatureSets} from "../signatureSets/index.js";
7
7
  import {CachedBeaconStateAllForks, CachedBeaconStateGloas} from "../types.js";
8
8
  import {computeEpochAtSlot, isSlashableValidator} from "../util/index.js";
@@ -24,7 +24,7 @@ export function processProposerSlashing(
24
24
  const proposer = state.validators.getReadonly(proposerSlashing.signedHeader1.message.proposerIndex);
25
25
  assertValidProposerSlashing(
26
26
  state.config,
27
- state.epochCtx.index2pubkey,
27
+ state.epochCtx.pubkeyCache,
28
28
  state.slot,
29
29
  proposerSlashing,
30
30
  proposer,
@@ -57,7 +57,7 @@ export function processProposerSlashing(
57
57
 
58
58
  export function assertValidProposerSlashing(
59
59
  config: BeaconConfig,
60
- index2pubkey: Index2PubkeyCache,
60
+ pubkeyCache: PubkeyCache,
61
61
  stateSlot: Slot,
62
62
  proposerSlashing: phase0.ProposerSlashing,
63
63
  proposer: Validator,
@@ -94,7 +94,7 @@ export function assertValidProposerSlashing(
94
94
  if (verifySignatures) {
95
95
  const signatureSets = getProposerSlashingSignatureSets(config, stateSlot, proposerSlashing);
96
96
  for (let i = 0; i < signatureSets.length; i++) {
97
- if (!verifySignatureSet(signatureSets[i], index2pubkey)) {
97
+ if (!verifySignatureSet(signatureSets[i], pubkeyCache)) {
98
98
  throw new Error(`ProposerSlashing header${i + 1} signature invalid`);
99
99
  }
100
100
  }
@@ -17,7 +17,7 @@ export function processRandao(state: CachedBeaconStateAllForks, block: BeaconBlo
17
17
  const randaoReveal = block.body.randaoReveal;
18
18
 
19
19
  // verify RANDAO reveal
20
- if (verifySignature && !verifyRandaoSignature(config, epochCtx.index2pubkey, block)) {
20
+ if (verifySignature && !verifyRandaoSignature(config, epochCtx.pubkeyCache, block)) {
21
21
  throw new Error("RANDAO reveal is an invalid signature");
22
22
  }
23
23
 
@@ -32,7 +32,7 @@ export function processSyncAggregate(
32
32
  participantIndices
33
33
  );
34
34
  // When there's no participation we consider the signature valid and just ignore it
35
- if (signatureSet !== null && !verifySignatureSet(signatureSet, state.epochCtx.index2pubkey)) {
35
+ if (signatureSet !== null && !verifySignatureSet(signatureSet, state.epochCtx.pubkeyCache)) {
36
36
  throw Error("Sync committee signature invalid");
37
37
  }
38
38
  }
@@ -131,7 +131,7 @@ export function getVoluntaryExitValidity(
131
131
 
132
132
  if (
133
133
  verifySignature &&
134
- !verifyVoluntaryExitSignature(state.config, epochCtx.index2pubkey, state.slot, signedVoluntaryExit)
134
+ !verifyVoluntaryExitSignature(state.config, epochCtx.pubkeyCache, state.slot, signedVoluntaryExit)
135
135
  ) {
136
136
  return VoluntaryExitValidity.invalidSignature;
137
137
  }
@@ -21,7 +21,7 @@ export function processWithdrawalRequest(
21
21
  const amount = Number(withdrawalRequest.amount);
22
22
  const {pendingPartialWithdrawals, validators, epochCtx} = state;
23
23
  // no need to use unfinalized pubkey cache from 6110 as validator won't be active anyway
24
- const {pubkey2index, config} = epochCtx;
24
+ const {pubkeyCache, config} = epochCtx;
25
25
  const isFullExitRequest = amount === FULL_EXIT_REQUEST_AMOUNT;
26
26
 
27
27
  // If partial withdrawal queue is full, only full exits are processed
@@ -31,7 +31,7 @@ export function processWithdrawalRequest(
31
31
 
32
32
  // bail out if validator is not in beacon state
33
33
  // note that we don't need to check for 6110 unfinalized vals as they won't be eligible for withdraw/exit anyway
34
- const validatorIndex = pubkey2index.get(withdrawalRequest.validatorPubkey);
34
+ const validatorIndex = pubkeyCache.getIndex(withdrawalRequest.validatorPubkey);
35
35
  if (validatorIndex === null) {
36
36
  return;
37
37
  }
@@ -249,6 +249,10 @@ function getPendingPartialWithdrawals(
249
249
  numPriorWithdrawal + MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP,
250
250
  MAX_WITHDRAWALS_PER_PAYLOAD - 1
251
251
  );
252
+ // There must be at least one space reserved for validator sweep withdrawals
253
+ if (numPriorWithdrawal > partialWithdrawalBound) {
254
+ throw Error(`Prior withdrawals exceed limit: ${numPriorWithdrawal} > ${partialWithdrawalBound}`);
255
+ }
252
256
 
253
257
  // MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP = 8, PENDING_PARTIAL_WITHDRAWALS_LIMIT: 134217728 so we should only call getAllReadonly() if it makes sense
254
258
  // pendingPartialWithdrawals comes from EIP-7002 smart contract where it takes fee so it's more likely than not validator is in correct condition to withdraw
@@ -309,6 +313,11 @@ function getValidatorsSweepWithdrawals(
309
313
  numPriorWithdrawal: number,
310
314
  validatorBalanceAfterWithdrawals: Map<ValidatorIndex, number>
311
315
  ): {sweepWithdrawals: capella.Withdrawal[]; processedCount: number} {
316
+ // There must be at least one space reserved for validator sweep withdrawals
317
+ if (numPriorWithdrawal >= MAX_WITHDRAWALS_PER_PAYLOAD) {
318
+ throw Error(`Prior withdrawals exceed limit: ${numPriorWithdrawal} >= ${MAX_WITHDRAWALS_PER_PAYLOAD}`);
319
+ }
320
+
312
321
  const sweepWithdrawals: capella.Withdrawal[] = [];
313
322
  const epoch = state.epochCtx.epoch;
314
323
  const {validators, balances, nextWithdrawalValidatorIndex} = state;
@@ -319,7 +328,7 @@ function getValidatorsSweepWithdrawals(
319
328
  // Just run a bounded loop max iterating over all withdrawals
320
329
  // however breaks out once we have MAX_WITHDRAWALS_PER_PAYLOAD
321
330
  for (let n = 0; n < validatorsLimit; n++) {
322
- if (sweepWithdrawals.length + numPriorWithdrawal === MAX_WITHDRAWALS_PER_PAYLOAD) {
331
+ if (sweepWithdrawals.length + numPriorWithdrawal >= MAX_WITHDRAWALS_PER_PAYLOAD) {
323
332
  break;
324
333
  }
325
334
 
@@ -1,5 +1,4 @@
1
1
  import {PublicKey} from "@chainsafe/blst";
2
- import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map";
3
2
  import {BeaconConfig, ChainConfig, createBeaconConfig} from "@lodestar/config";
4
3
  import {
5
4
  ATTESTATION_SUBNET_COUNT,
@@ -36,6 +35,7 @@ import {
36
35
  import {
37
36
  computeActivationExitEpoch,
38
37
  computeEpochAtSlot,
38
+ computePayloadTimelinessCommitteesForEpoch,
39
39
  computeProposers,
40
40
  computeSyncPeriodAtEpoch,
41
41
  getActivationChurnLimit,
@@ -43,7 +43,6 @@ import {
43
43
  getSeed,
44
44
  isActiveValidator,
45
45
  isAggregatorFromCommitteeLength,
46
- naiveGetPayloadTimlinessCommitteeIndices,
47
46
  } from "../util/index.js";
48
47
  import {
49
48
  AttesterDuty,
@@ -56,7 +55,7 @@ import {computeBaseRewardPerIncrement, computeSyncParticipantReward} from "../ut
56
55
  import {sumTargetUnslashedBalanceIncrements} from "../util/targetUnslashedBalance.js";
57
56
  import {EffectiveBalanceIncrements, getEffectiveBalanceIncrementsWithLen} from "./effectiveBalanceIncrements.js";
58
57
  import {EpochTransitionCache} from "./epochTransitionCache.js";
59
- import {Index2PubkeyCache, syncPubkeys} from "./pubkeyCache.js";
58
+ import {PubkeyCache, createPubkeyCache, syncPubkeys} from "./pubkeyCache.js";
60
59
  import {CachedBeaconStateAllForks, CachedBeaconStateFulu} from "./stateCache.js";
61
60
  import {
62
61
  SyncCommitteeCache,
@@ -64,15 +63,14 @@ import {
64
63
  computeSyncCommitteeCache,
65
64
  getSyncCommitteeCache,
66
65
  } from "./syncCommitteeCache.js";
67
- import {BeaconStateAllForks, BeaconStateAltair, BeaconStateGloas, ShufflingGetter} from "./types.js";
66
+ import {BeaconStateAllForks, BeaconStateAltair, ShufflingGetter} from "./types.js";
68
67
 
69
68
  /** `= PROPOSER_WEIGHT / (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT)` */
70
69
  export const PROPOSER_WEIGHT_FACTOR = PROPOSER_WEIGHT / (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT);
71
70
 
72
71
  export type EpochCacheImmutableData = {
73
72
  config: BeaconConfig;
74
- pubkey2index: PubkeyIndexMap;
75
- index2pubkey: Index2PubkeyCache;
73
+ pubkeyCache: PubkeyCache;
76
74
  };
77
75
 
78
76
  export type EpochCacheOpts = {
@@ -111,15 +109,9 @@ export class EpochCache {
111
109
  /**
112
110
  * Unique globally shared pubkey registry. There should only exist one for the entire application.
113
111
  *
114
- * $VALIDATOR_COUNT x 192 char String -> Number Map
112
+ * Couples both index→pubkey and pubkey→index lookups, keeping them in sync atomically.
115
113
  */
116
- pubkey2index: PubkeyIndexMap;
117
- /**
118
- * Unique globally shared pubkey registry. There should only exist one for the entire application.
119
- *
120
- * $VALIDATOR_COUNT x BLST deserialized pubkey (Jacobian coordinates)
121
- */
122
- index2pubkey: Index2PubkeyCache;
114
+ pubkeyCache: PubkeyCache;
123
115
  /**
124
116
  * Indexes of the block proposers for the current epoch.
125
117
  * For pre-fulu, this is computed and cached from the current shuffling.
@@ -235,8 +227,8 @@ export class EpochCache {
235
227
  nextSyncCommitteeIndexed: SyncCommitteeCache;
236
228
 
237
229
  // TODO GLOAS: See if we need to cached PTC for prev/next epoch
238
- // PTC for current epoch
239
- payloadTimelinessCommittee: ValidatorIndex[][];
230
+ // PTC for current epoch, computed eagerly at epoch transition
231
+ payloadTimelinessCommittees: Uint32Array[];
240
232
 
241
233
  // TODO: Helper stats
242
234
  syncPeriod: SyncPeriod;
@@ -249,8 +241,7 @@ export class EpochCache {
249
241
 
250
242
  constructor(data: {
251
243
  config: BeaconConfig;
252
- pubkey2index: PubkeyIndexMap;
253
- index2pubkey: Index2PubkeyCache;
244
+ pubkeyCache: PubkeyCache;
254
245
  proposers: number[];
255
246
  proposersPrevEpoch: number[] | null;
256
247
  proposersNextEpoch: ProposersDeferred;
@@ -275,13 +266,12 @@ export class EpochCache {
275
266
  previousTargetUnslashedBalanceIncrements: number;
276
267
  currentSyncCommitteeIndexed: SyncCommitteeCache;
277
268
  nextSyncCommitteeIndexed: SyncCommitteeCache;
278
- payloadTimelinessCommittee: ValidatorIndex[][];
269
+ payloadTimelinessCommittees: Uint32Array[];
279
270
  epoch: Epoch;
280
271
  syncPeriod: SyncPeriod;
281
272
  }) {
282
273
  this.config = data.config;
283
- this.pubkey2index = data.pubkey2index;
284
- this.index2pubkey = data.index2pubkey;
274
+ this.pubkeyCache = data.pubkeyCache;
285
275
  this.proposers = data.proposers;
286
276
  this.proposersPrevEpoch = data.proposersPrevEpoch;
287
277
  this.proposersNextEpoch = data.proposersNextEpoch;
@@ -306,7 +296,7 @@ export class EpochCache {
306
296
  this.previousTargetUnslashedBalanceIncrements = data.previousTargetUnslashedBalanceIncrements;
307
297
  this.currentSyncCommitteeIndexed = data.currentSyncCommitteeIndexed;
308
298
  this.nextSyncCommitteeIndexed = data.nextSyncCommitteeIndexed;
309
- this.payloadTimelinessCommittee = data.payloadTimelinessCommittee;
299
+ this.payloadTimelinessCommittees = data.payloadTimelinessCommittees;
310
300
  this.epoch = data.epoch;
311
301
  this.syncPeriod = data.syncPeriod;
312
302
  }
@@ -319,7 +309,7 @@ export class EpochCache {
319
309
  */
320
310
  static createFromState(
321
311
  state: BeaconStateAllForks,
322
- {config, pubkey2index, index2pubkey}: EpochCacheImmutableData,
312
+ {config, pubkeyCache}: EpochCacheImmutableData,
323
313
  opts?: EpochCacheOpts
324
314
  ): EpochCache {
325
315
  const currentEpoch = computeEpochAtSlot(state.slot);
@@ -335,9 +325,9 @@ export class EpochCache {
335
325
  const validatorCount = validators.length;
336
326
 
337
327
  // syncPubkeys here to ensure EpochCacheImmutableData is popualted before computing the rest of caches
338
- // - computeSyncCommitteeCache() needs a fully populated pubkey2index cache
328
+ // - computeSyncCommitteeCache() needs a fully populated pubkeyCache
339
329
  if (!opts?.skipSyncPubkeys) {
340
- syncPubkeys(validators, pubkey2index, index2pubkey);
330
+ syncPubkeys(pubkeyCache, validators);
341
331
  }
342
332
 
343
333
  const effectiveBalanceIncrements = getEffectiveBalanceIncrementsWithLen(validatorCount);
@@ -450,21 +440,21 @@ export class EpochCache {
450
440
  // Allow to skip populating sync committee for initializeBeaconStateFromEth1()
451
441
  if (afterAltairFork && !opts?.skipSyncCommitteeCache) {
452
442
  const altairState = state as BeaconStateAltair;
453
- currentSyncCommitteeIndexed = computeSyncCommitteeCache(altairState.currentSyncCommittee, pubkey2index);
454
- nextSyncCommitteeIndexed = computeSyncCommitteeCache(altairState.nextSyncCommittee, pubkey2index);
443
+ currentSyncCommitteeIndexed = computeSyncCommitteeCache(altairState.currentSyncCommittee, pubkeyCache);
444
+ nextSyncCommitteeIndexed = computeSyncCommitteeCache(altairState.nextSyncCommittee, pubkeyCache);
455
445
  } else {
456
446
  currentSyncCommitteeIndexed = new SyncCommitteeCacheEmpty();
457
447
  nextSyncCommitteeIndexed = new SyncCommitteeCacheEmpty();
458
448
  }
459
449
 
460
- // Compute PTC for this epoch
461
- let payloadTimelinessCommittee: ValidatorIndex[][] = [];
450
+ // Compute PTC eagerly for all slots in the epoch
451
+ let payloadTimelinessCommittees: Uint32Array[] = [];
462
452
  if (currentEpoch >= config.GLOAS_FORK_EPOCH) {
463
- payloadTimelinessCommittee = naiveGetPayloadTimlinessCommitteeIndices(
464
- state as BeaconStateGloas,
465
- currentShuffling,
466
- effectiveBalanceIncrements,
467
- currentEpoch
453
+ payloadTimelinessCommittees = computePayloadTimelinessCommitteesForEpoch(
454
+ state,
455
+ currentEpoch,
456
+ currentShuffling.committees,
457
+ effectiveBalanceIncrements
468
458
  );
469
459
  }
470
460
 
@@ -514,8 +504,7 @@ export class EpochCache {
514
504
 
515
505
  return new EpochCache({
516
506
  config,
517
- pubkey2index,
518
- index2pubkey,
507
+ pubkeyCache,
519
508
  proposers,
520
509
  // On first epoch, set to null to prevent unnecessary work since this is only used for metrics
521
510
  proposersPrevEpoch: null,
@@ -541,7 +530,7 @@ export class EpochCache {
541
530
  currentTargetUnslashedBalanceIncrements,
542
531
  currentSyncCommitteeIndexed,
543
532
  nextSyncCommitteeIndexed,
544
- payloadTimelinessCommittee: payloadTimelinessCommittee,
533
+ payloadTimelinessCommittees,
545
534
  epoch: currentEpoch,
546
535
  syncPeriod: computeSyncPeriodAtEpoch(currentEpoch),
547
536
  });
@@ -557,8 +546,7 @@ export class EpochCache {
557
546
  return new EpochCache({
558
547
  config: this.config,
559
548
  // Common append-only structures shared with all states, no need to clone
560
- pubkey2index: this.pubkey2index,
561
- index2pubkey: this.index2pubkey,
549
+ pubkeyCache: this.pubkeyCache,
562
550
  // Immutable data
563
551
  proposers: this.proposers,
564
552
  proposersPrevEpoch: this.proposersPrevEpoch,
@@ -587,7 +575,7 @@ export class EpochCache {
587
575
  currentTargetUnslashedBalanceIncrements: this.currentTargetUnslashedBalanceIncrements,
588
576
  currentSyncCommitteeIndexed: this.currentSyncCommitteeIndexed,
589
577
  nextSyncCommitteeIndexed: this.nextSyncCommitteeIndexed,
590
- payloadTimelinessCommittee: this.payloadTimelinessCommittee,
578
+ payloadTimelinessCommittees: this.payloadTimelinessCommittees,
591
579
  epoch: this.epoch,
592
580
  syncPeriod: this.syncPeriod,
593
581
  });
@@ -698,11 +686,11 @@ export class EpochCache {
698
686
 
699
687
  this.proposersPrevEpoch = this.proposers;
700
688
  if (upcomingEpoch >= this.config.GLOAS_FORK_EPOCH) {
701
- this.payloadTimelinessCommittee = naiveGetPayloadTimlinessCommitteeIndices(
702
- state as BeaconStateGloas,
703
- this.currentShuffling,
704
- this.effectiveBalanceIncrements,
705
- upcomingEpoch
689
+ this.payloadTimelinessCommittees = computePayloadTimelinessCommitteesForEpoch(
690
+ state,
691
+ upcomingEpoch,
692
+ this.currentShuffling.committees,
693
+ this.effectiveBalanceIncrements
706
694
  );
707
695
  }
708
696
  if (upcomingEpoch >= this.config.FULU_FORK_EPOCH) {
@@ -882,16 +870,15 @@ export class EpochCache {
882
870
  * Return pubkey given the validator index.
883
871
  */
884
872
  getPubkey(index: ValidatorIndex): PublicKey | undefined {
885
- return this.index2pubkey[index];
873
+ return this.pubkeyCache.get(index);
886
874
  }
887
875
 
888
876
  getValidatorIndex(pubkey: Uint8Array): ValidatorIndex | null {
889
- return this.pubkey2index.get(pubkey);
877
+ return this.pubkeyCache.getIndex(pubkey);
890
878
  }
891
879
 
892
880
  addPubkey(index: ValidatorIndex, pubkey: Uint8Array): void {
893
- this.pubkey2index.set(pubkey, index);
894
- this.index2pubkey[index] = PublicKey.fromBytes(pubkey); // Optimize for aggregation
881
+ this.pubkeyCache.set(index, pubkey);
895
882
  }
896
883
 
897
884
  getShufflingAtSlot(slot: Slot): EpochShuffling {
@@ -1022,18 +1009,18 @@ export class EpochCache {
1022
1009
  return this.epoch >= this.config.ELECTRA_FORK_EPOCH;
1023
1010
  }
1024
1011
 
1025
- getPayloadTimelinessCommittee(slot: Slot): ValidatorIndex[] {
1012
+ getPayloadTimelinessCommittee(slot: Slot): Uint32Array {
1026
1013
  const epoch = computeEpochAtSlot(slot);
1027
1014
 
1028
1015
  if (epoch < this.config.GLOAS_FORK_EPOCH) {
1029
1016
  throw new Error("Payload Timeliness Committee is not available before gloas fork");
1030
1017
  }
1031
1018
 
1032
- if (epoch === this.epoch) {
1033
- return this.payloadTimelinessCommittee[slot % SLOTS_PER_EPOCH];
1019
+ if (epoch !== this.epoch) {
1020
+ throw new Error(`Payload Timeliness Committee is not available for slot=${slot}`);
1034
1021
  }
1035
1022
 
1036
- throw new Error(`Payload Timeliness Committee is not available for slot=${slot}`);
1023
+ return this.payloadTimelinessCommittees[slot % SLOTS_PER_EPOCH];
1037
1024
  }
1038
1025
 
1039
1026
  getIndexedPayloadAttestation(
@@ -1099,7 +1086,6 @@ export function createEmptyEpochCacheImmutableData(
1099
1086
  return {
1100
1087
  config: createBeaconConfig(chainConfig, state.genesisValidatorsRoot),
1101
1088
  // This is a test state, there's no need to have a global shared cache of keys
1102
- pubkey2index: new PubkeyIndexMap(),
1103
- index2pubkey: [],
1089
+ pubkeyCache: createPubkeyCache(),
1104
1090
  };
1105
1091
  }
@@ -1,33 +1,74 @@
1
1
  import {PublicKey} from "@chainsafe/blst";
2
2
  import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map";
3
- import {phase0} from "@lodestar/types";
3
+ import {ValidatorIndex, phase0} from "@lodestar/types";
4
4
 
5
- export type Index2PubkeyCache = PublicKey[];
5
+ /**
6
+ * Unified pubkey cache coupling index→pubkey and pubkey→index lookups.
7
+ * Both directions are kept in sync atomically via `set()`.
8
+ */
9
+ export interface PubkeyCache {
10
+ /** Get deserialized PublicKey by validator index */
11
+ get(index: ValidatorIndex): PublicKey | undefined;
12
+ /** Get deserialized PublicKey by validator index or throw if not found */
13
+ getOrThrow(index: ValidatorIndex): PublicKey;
14
+ /** Get validator index by pubkey bytes */
15
+ getIndex(pubkey: Uint8Array): ValidatorIndex | null;
16
+ /** Set both directions atomically. Takes raw pubkey bytes — deserialization is handled internally. */
17
+ set(index: ValidatorIndex, pubkey: Uint8Array): void;
18
+ /** Number of entries */
19
+ readonly size: number;
20
+ }
21
+
22
+ class StandardPubkeyCache implements PubkeyCache {
23
+ private readonly pubkey2index: PubkeyIndexMap;
24
+ private readonly index2pubkey: (PublicKey | undefined)[];
25
+
26
+ constructor(pubkey2index?: PubkeyIndexMap, index2pubkey?: (PublicKey | undefined)[]) {
27
+ this.pubkey2index = pubkey2index ?? new PubkeyIndexMap();
28
+ this.index2pubkey = index2pubkey ?? [];
29
+ }
30
+
31
+ get size(): number {
32
+ return this.pubkey2index.size;
33
+ }
34
+
35
+ get(index: ValidatorIndex): PublicKey | undefined {
36
+ return this.index2pubkey[index];
37
+ }
38
+
39
+ getOrThrow(index: ValidatorIndex): PublicKey {
40
+ const pubkey = this.get(index);
41
+ if (!pubkey) throw Error(`Missing pubkey for validator index ${index}`);
42
+ return pubkey;
43
+ }
44
+
45
+ getIndex(pubkey: Uint8Array): ValidatorIndex | null {
46
+ return this.pubkey2index.get(pubkey);
47
+ }
48
+
49
+ set(index: ValidatorIndex, pubkey: Uint8Array): void {
50
+ this.pubkey2index.set(pubkey, index);
51
+ // Pubkeys must be checked for group + inf. This must be done only once when the validator deposit is processed.
52
+ // Afterwards any public key in the state is considered validated.
53
+ // > Do not do any validation here
54
+ this.index2pubkey[index] = PublicKey.fromBytes(pubkey); // Optimize for aggregation
55
+ }
56
+ }
57
+
58
+ export function createPubkeyCache(): PubkeyCache {
59
+ return new StandardPubkeyCache();
60
+ }
6
61
 
7
62
  /**
8
63
  * Checks the pubkey indices against a state and adds missing pubkeys
9
64
  *
10
- * Mutates `pubkey2index` and `index2pubkey`
65
+ * Mutates `pubkeyCache`
11
66
  *
12
- * If pubkey caches are empty: SLOW CODE - 🐢
67
+ * If pubkey cache is empty: SLOW CODE - 🐢
13
68
  */
14
- export function syncPubkeys(
15
- validators: phase0.Validator[],
16
- pubkey2index: PubkeyIndexMap,
17
- index2pubkey: Index2PubkeyCache
18
- ): void {
19
- if (pubkey2index.size !== index2pubkey.length) {
20
- throw new Error(`Pubkey indices have fallen out of sync: ${pubkey2index.size} != ${index2pubkey.length}`);
21
- }
22
-
69
+ export function syncPubkeys(pubkeyCache: PubkeyCache, validators: phase0.Validator[]): void {
23
70
  const newCount = validators.length;
24
- index2pubkey.length = newCount;
25
- for (let i = pubkey2index.size; i < newCount; i++) {
26
- const pubkey = validators[i].pubkey;
27
- pubkey2index.set(pubkey, i);
28
- // Pubkeys must be checked for group + inf. This must be done only once when the validator deposit is processed.
29
- // Afterwards any public key is the state consider validated.
30
- // > Do not do any validation here
31
- index2pubkey[i] = PublicKey.fromBytes(pubkey); // Optimize for aggregation
71
+ for (let i = pubkeyCache.size; i < newCount; i++) {
72
+ pubkeyCache.set(i, validators[i].pubkey);
32
73
  }
33
74
  }
@@ -1,4 +1,3 @@
1
- import {PublicKey} from "@chainsafe/blst";
2
1
  import {BeaconConfig} from "@lodestar/config";
3
2
  import {loadState} from "../util/loadState/loadState.js";
4
3
  import {EpochCache, EpochCacheImmutableData, EpochCacheOpts} from "./epochCache.js";
@@ -168,7 +167,7 @@ export function createCachedBeaconState<T extends BeaconStateAllForks>(
168
167
  * Check loadState() api for more details
169
168
  * // TODO: rename to loadUnfinalizedCachedBeaconState() due to ELECTRA
170
169
  */
171
- export function loadCachedBeaconState<T extends BeaconStateAllForks & BeaconStateCache>(
170
+ export function loadCachedBeaconState<T extends CachedBeaconStateAllForks>(
172
171
  cachedSeedState: T,
173
172
  stateBytes: Uint8Array,
174
173
  opts?: EpochCacheOpts,
@@ -180,22 +179,19 @@ export function loadCachedBeaconState<T extends BeaconStateAllForks & BeaconStat
180
179
  stateBytes,
181
180
  seedValidatorsBytes
182
181
  );
183
- const {pubkey2index, index2pubkey} = cachedSeedState.epochCtx;
182
+ const {pubkeyCache} = cachedSeedState.epochCtx;
184
183
  // Get the validators sub tree once for all the loop
185
184
  const validators = migratedState.validators;
186
185
  for (const validatorIndex of modifiedValidators) {
187
186
  const validator = validators.getReadonly(validatorIndex);
188
- const pubkey = validator.pubkey;
189
- pubkey2index.set(pubkey, validatorIndex);
190
- index2pubkey[validatorIndex] = PublicKey.fromBytes(pubkey);
187
+ pubkeyCache.set(validatorIndex, validator.pubkey);
191
188
  }
192
189
 
193
190
  return createCachedBeaconState(
194
191
  migratedState,
195
192
  {
196
193
  config: cachedSeedState.config,
197
- pubkey2index,
198
- index2pubkey,
194
+ pubkeyCache,
199
195
  },
200
196
  {...(opts ?? {}), ...{skipSyncPubkeys: true}}
201
197
  ) as T;
@@ -1,4 +1,3 @@
1
- import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map";
2
1
  import {CompositeViewDU} from "@chainsafe/ssz";
3
2
  import {ValidatorIndex, ssz} from "@lodestar/types";
4
3
  import {toPubkeyHex} from "@lodestar/utils";
@@ -39,9 +38,9 @@ export function getSyncCommitteeCache(validatorIndices: Uint32Array): SyncCommit
39
38
 
40
39
  export function computeSyncCommitteeCache(
41
40
  syncCommittee: CompositeViewDU<typeof ssz.altair.SyncCommittee>,
42
- pubkey2index: PubkeyIndexMap
41
+ pubkeyCache: {getIndex(pubkey: Uint8Array): number | null}
43
42
  ): SyncCommitteeCache {
44
- const validatorIndices = computeSyncCommitteeValidatorIndices(syncCommittee, pubkey2index);
43
+ const validatorIndices = computeSyncCommitteeValidatorIndices(syncCommittee, pubkeyCache);
45
44
  const validatorIndexMap = computeValidatorSyncCommitteeIndexMap(validatorIndices);
46
45
  return {
47
46
  validatorIndices,
@@ -79,12 +78,12 @@ export function computeValidatorSyncCommitteeIndexMap(
79
78
  */
80
79
  function computeSyncCommitteeValidatorIndices(
81
80
  syncCommittee: CompositeViewDU<typeof ssz.altair.SyncCommittee>,
82
- pubkey2index: PubkeyIndexMap
81
+ pubkeyCache: {getIndex(pubkey: Uint8Array): number | null}
83
82
  ): Uint32Array {
84
83
  const pubkeys = syncCommittee.pubkeys.getAllReadonly();
85
84
  const validatorIndices = new Uint32Array(pubkeys.length);
86
85
  for (const [i, pubkey] of pubkeys.entries()) {
87
- const validatorIndex = pubkey2index.get(pubkey);
86
+ const validatorIndex = pubkeyCache.getIndex(pubkey);
88
87
  if (validatorIndex === null) {
89
88
  throw Error(`SyncCommittee pubkey is unknown ${toPubkeyHex(pubkey)}`);
90
89
  }