@lodestar/state-transition 1.43.0-dev.bc569affb9 → 1.43.0-dev.c5efeb6c90

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 (120) hide show
  1. package/lib/block/processConsolidationRequest.d.ts.map +1 -1
  2. package/lib/block/processConsolidationRequest.js +2 -1
  3. package/lib/block/processConsolidationRequest.js.map +1 -1
  4. package/lib/block/processDepositRequest.d.ts +3 -11
  5. package/lib/block/processDepositRequest.d.ts.map +1 -1
  6. package/lib/block/processDepositRequest.js +27 -35
  7. package/lib/block/processDepositRequest.js.map +1 -1
  8. package/lib/block/processParentExecutionPayload.d.ts +2 -2
  9. package/lib/block/processParentExecutionPayload.d.ts.map +1 -1
  10. package/lib/block/processParentExecutionPayload.js +7 -6
  11. package/lib/block/processParentExecutionPayload.js.map +1 -1
  12. package/lib/block/processWithdrawals.d.ts.map +1 -1
  13. package/lib/block/processWithdrawals.js +4 -6
  14. package/lib/block/processWithdrawals.js.map +1 -1
  15. package/lib/cache/epochCache.js +3 -3
  16. package/lib/cache/epochCache.js.map +1 -1
  17. package/lib/epoch/processPendingDeposits.d.ts.map +1 -1
  18. package/lib/epoch/processPendingDeposits.js +4 -2
  19. package/lib/epoch/processPendingDeposits.js.map +1 -1
  20. package/lib/lightClient/spec/index.d.ts +22 -0
  21. package/lib/lightClient/spec/index.d.ts.map +1 -0
  22. package/lib/lightClient/spec/index.js +58 -0
  23. package/lib/lightClient/spec/index.js.map +1 -0
  24. package/lib/lightClient/spec/isBetterUpdate.d.ts +23 -0
  25. package/lib/lightClient/spec/isBetterUpdate.d.ts.map +1 -0
  26. package/lib/lightClient/spec/isBetterUpdate.js +66 -0
  27. package/lib/lightClient/spec/isBetterUpdate.js.map +1 -0
  28. package/lib/lightClient/spec/processLightClientUpdate.d.ts +12 -0
  29. package/lib/lightClient/spec/processLightClientUpdate.d.ts.map +1 -0
  30. package/lib/lightClient/spec/processLightClientUpdate.js +80 -0
  31. package/lib/lightClient/spec/processLightClientUpdate.js.map +1 -0
  32. package/lib/lightClient/spec/store.d.ts +45 -0
  33. package/lib/lightClient/spec/store.d.ts.map +1 -0
  34. package/lib/lightClient/spec/store.js +56 -0
  35. package/lib/lightClient/spec/store.js.map +1 -0
  36. package/lib/lightClient/spec/utils.d.ts +47 -0
  37. package/lib/lightClient/spec/utils.d.ts.map +1 -0
  38. package/lib/lightClient/spec/utils.js +197 -0
  39. package/lib/lightClient/spec/utils.js.map +1 -0
  40. package/lib/lightClient/spec/validateLightClientBootstrap.d.ts +4 -0
  41. package/lib/lightClient/spec/validateLightClientBootstrap.d.ts.map +1 -0
  42. package/lib/lightClient/spec/validateLightClientBootstrap.js +22 -0
  43. package/lib/lightClient/spec/validateLightClientBootstrap.js.map +1 -0
  44. package/lib/lightClient/spec/validateLightClientUpdate.d.ts +5 -0
  45. package/lib/lightClient/spec/validateLightClientUpdate.d.ts.map +1 -0
  46. package/lib/lightClient/spec/validateLightClientUpdate.js +88 -0
  47. package/lib/lightClient/spec/validateLightClientUpdate.js.map +1 -0
  48. package/lib/signatureSets/index.d.ts +1 -0
  49. package/lib/signatureSets/index.d.ts.map +1 -1
  50. package/lib/signatureSets/index.js +1 -0
  51. package/lib/signatureSets/index.js.map +1 -1
  52. package/lib/signatureSets/proposerPreferences.d.ts +4 -0
  53. package/lib/signatureSets/proposerPreferences.d.ts.map +1 -0
  54. package/lib/signatureSets/proposerPreferences.js +8 -0
  55. package/lib/signatureSets/proposerPreferences.js.map +1 -0
  56. package/lib/slot/upgradeStateToElectra.d.ts.map +1 -1
  57. package/lib/slot/upgradeStateToElectra.js +2 -2
  58. package/lib/slot/upgradeStateToElectra.js.map +1 -1
  59. package/lib/slot/upgradeStateToGloas.d.ts.map +1 -1
  60. package/lib/slot/upgradeStateToGloas.js +33 -28
  61. package/lib/slot/upgradeStateToGloas.js.map +1 -1
  62. package/lib/stateView/beaconStateView.d.ts +14 -5
  63. package/lib/stateView/beaconStateView.d.ts.map +1 -1
  64. package/lib/stateView/beaconStateView.js +40 -11
  65. package/lib/stateView/beaconStateView.js.map +1 -1
  66. package/lib/stateView/interface.d.ts +7 -5
  67. package/lib/stateView/interface.d.ts.map +1 -1
  68. package/lib/stateView/interface.js.map +1 -1
  69. package/lib/util/epoch.d.ts.map +1 -1
  70. package/lib/util/epoch.js +6 -4
  71. package/lib/util/epoch.js.map +1 -1
  72. package/lib/util/gloas.d.ts +0 -1
  73. package/lib/util/gloas.d.ts.map +1 -1
  74. package/lib/util/gloas.js +0 -4
  75. package/lib/util/gloas.js.map +1 -1
  76. package/lib/util/index.d.ts +1 -0
  77. package/lib/util/index.d.ts.map +1 -1
  78. package/lib/util/index.js +1 -0
  79. package/lib/util/index.js.map +1 -1
  80. package/lib/util/loadState/loadState.js +4 -4
  81. package/lib/util/loadState/loadState.js.map +1 -1
  82. package/lib/util/pendingDepositsLookup.d.ts +40 -0
  83. package/lib/util/pendingDepositsLookup.d.ts.map +1 -0
  84. package/lib/util/pendingDepositsLookup.js +84 -0
  85. package/lib/util/pendingDepositsLookup.js.map +1 -0
  86. package/lib/util/shuffling.d.ts +6 -5
  87. package/lib/util/shuffling.d.ts.map +1 -1
  88. package/lib/util/shuffling.js +13 -15
  89. package/lib/util/shuffling.js.map +1 -1
  90. package/lib/util/validator.d.ts +14 -2
  91. package/lib/util/validator.d.ts.map +1 -1
  92. package/lib/util/validator.js +24 -2
  93. package/lib/util/validator.js.map +1 -1
  94. package/package.json +13 -8
  95. package/src/block/processConsolidationRequest.ts +2 -1
  96. package/src/block/processDepositRequest.ts +29 -47
  97. package/src/block/processParentExecutionPayload.ts +7 -6
  98. package/src/block/processWithdrawals.ts +6 -6
  99. package/src/cache/epochCache.ts +3 -3
  100. package/src/epoch/processPendingDeposits.ts +5 -2
  101. package/src/lightClient/spec/index.ts +101 -0
  102. package/src/lightClient/spec/isBetterUpdate.ts +94 -0
  103. package/src/lightClient/spec/processLightClientUpdate.ts +119 -0
  104. package/src/lightClient/spec/store.ts +106 -0
  105. package/src/lightClient/spec/utils.ts +317 -0
  106. package/src/lightClient/spec/validateLightClientBootstrap.ts +39 -0
  107. package/src/lightClient/spec/validateLightClientUpdate.ts +145 -0
  108. package/src/signatureSets/index.ts +1 -0
  109. package/src/signatureSets/proposerPreferences.ts +12 -0
  110. package/src/slot/upgradeStateToElectra.ts +4 -2
  111. package/src/slot/upgradeStateToGloas.ts +41 -44
  112. package/src/stateView/beaconStateView.ts +43 -12
  113. package/src/stateView/interface.ts +7 -5
  114. package/src/util/epoch.ts +13 -4
  115. package/src/util/gloas.ts +0 -5
  116. package/src/util/index.ts +1 -0
  117. package/src/util/loadState/loadState.ts +4 -4
  118. package/src/util/pendingDepositsLookup.ts +105 -0
  119. package/src/util/shuffling.ts +17 -15
  120. package/src/util/validator.ts +42 -2
@@ -68,7 +68,7 @@ import {canBuilderCoverBid} from "../util/gloas.js";
68
68
  import {loadState} from "../util/loadState/loadState.js";
69
69
  import {getRandaoMix} from "../util/seed.js";
70
70
  import {getLatestWeakSubjectivityCheckpointEpoch} from "../util/weakSubjectivity.js";
71
- import {IBeaconStateView, IBeaconStateViewLatestFork} from "./interface.js";
71
+ import {IBeaconStateView, IBeaconStateViewGloas, IBeaconStateViewLatestFork, isStatePostGloas} from "./interface.js";
72
72
 
73
73
  export class BeaconStateView implements IBeaconStateViewLatestFork {
74
74
  private readonly config: BeaconConfig;
@@ -406,16 +406,44 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
406
406
  }
407
407
 
408
408
  /**
409
- * Return the index of the validator in the PTC committee for the given slot.
410
- * return -1 if validator is not in the PTC committee for the given slot.
409
+ * Return the PTCs for an epoch
411
410
  */
412
- 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[] {
413
435
  if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.gloas) {
414
436
  throw new Error("PTC committees are not supported before Gloas");
415
437
  }
416
438
 
417
439
  const ptcCommittee = (this.cachedState as CachedBeaconStateGloas).epochCtx.getPayloadTimelinessCommittee(slot);
418
- 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;
419
447
  }
420
448
 
421
449
  // Shuffling and committees
@@ -786,16 +814,19 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
786
814
  /**
787
815
  * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.5/specs/gloas/validator.md#executionpayload
788
816
  */
789
- getExpectedWithdrawalsForFullParent(executionRequests: electra.ExecutionRequests): capella.Withdrawal[] {
790
- const fork = this.config.getForkSeq(this.cachedState.slot);
791
- if (fork < ForkSeq.gloas) {
792
- throw new Error("getExpectedWithdrawalsForFullParent is not available before Gloas");
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");
793
820
  }
794
- // Make a copy of the state to avoid mutability issues
795
821
  const stateCopy = this.cachedState.clone(true) as CachedBeaconStateGloas;
796
- // Apply parent payload before computing withdrawals
822
+
797
823
  applyParentExecutionPayload(stateCopy, executionRequests);
798
824
 
799
- return getExpectedWithdrawals(fork, stateCopy).expectedWithdrawals;
825
+ const stateView = new BeaconStateView(stateCopy);
826
+ if (!isStatePostGloas(stateView)) {
827
+ throw new Error("Expected gloas state after clone");
828
+ }
829
+
830
+ return stateView;
800
831
  }
801
832
  }
@@ -252,13 +252,15 @@ export interface IBeaconStateViewGloas extends IBeaconStateViewFulu {
252
252
  payloadExpectedWithdrawals: capella.Withdrawal[];
253
253
  getBuilder(index: BuilderIndex): gloas.Builder;
254
254
  canBuilderCoverBid(builderIndex: BuilderIndex, bidAmount: number): boolean;
255
- getIndexInPayloadTimelinessCommittee(validatorIndex: ValidatorIndex, slot: Slot): number;
255
+ getEpochPTCs(epoch: Epoch): Uint32Array[];
256
+ getIndicesInPayloadTimelinessCommittee(validatorIndex: ValidatorIndex, slot: Slot): number[];
256
257
  /**
257
- * Compute expected withdrawals as if the parent was FULL.
258
- * Clones the state, applies parent payload effects, then computes withdrawals.
259
- * Used by prepare_execution_payload when building on FULL parent.
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.
260
262
  */
261
- getExpectedWithdrawalsForFullParent(executionRequests: electra.ExecutionRequests): capella.Withdrawal[];
263
+ withParentPayloadApplied(executionRequests: electra.ExecutionRequests): IBeaconStateViewGloas;
262
264
  }
263
265
 
264
266
  /**
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,11 +172,6 @@ export function isAttestationSameSlotRootCache(rootCache: RootCache, data: Attes
172
172
  return isMatchingBlockRoot && isCurrentBlockRoot;
173
173
  }
174
174
 
175
- // TODO GLOAS: This function no longer exists in v1.7.0-alpha.5 specs. Remove it when appropriate to do so
176
- export function isParentBlockFull(state: CachedBeaconStateGloas): boolean {
177
- return byteArrayEquals(state.latestExecutionPayloadBid.blockHash, state.latestBlockHash);
178
- }
179
-
180
175
  export function initializePtcWindow(state: CachedBeaconStateFulu): Uint32Array[] {
181
176
  const ptcWindow: Uint32Array[] = Array.from({length: SLOTS_PER_EPOCH}, () => new Uint32Array(PTC_SIZE));
182
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();
@@ -0,0 +1,105 @@
1
+ import {BeaconConfig} from "@lodestar/config";
2
+ import {PubkeyHex, electra} from "@lodestar/types";
3
+ import {toPubkeyHex} from "@lodestar/utils";
4
+ import {isValidDepositSignature} from "../block/processDeposit.js";
5
+ import {CachedBeaconStateGloas} from "../types.js";
6
+
7
+ type PendingDepositsValidation = {
8
+ hasValidSignature: boolean;
9
+ validatedCount: number;
10
+ };
11
+
12
+ /**
13
+ * Mutable lookup for the pending-deposit sequence used by builder-routing logic.
14
+ *
15
+ * This is to implement the spec's `is_pending_validator(pending_deposits, pubkey)` lazily:
16
+ * deposits are grouped by pubkey without verifying signatures, and BLS verification is
17
+ * deferred until a builder deposit needs to know whether the same pubkey already has a
18
+ * valid pending validator deposit.
19
+ *
20
+ * Call `add()` whenever a deposit is appended to the represented sequence. A cached `true`
21
+ * result short-circuits all subsequent checks for that pubkey; a cached `false` records
22
+ * how many deposits were already verified, so appending a new deposit only verifies the
23
+ * newly-appended tail rather than re-running BLS on previously-invalid entries.
24
+ */
25
+ export class PendingDepositsLookup {
26
+ private constructor(
27
+ private readonly depositsByPubkey: Map<PubkeyHex, electra.PendingDeposit[]>,
28
+ private readonly validationCache: Map<PubkeyHex, PendingDepositsValidation>
29
+ ) {}
30
+
31
+ /** Build an empty lookup for a sequence that will be populated incrementally. */
32
+ static buildEmpty(): PendingDepositsLookup {
33
+ return new PendingDepositsLookup(new Map(), new Map());
34
+ }
35
+
36
+ /**
37
+ * Build a pubkey -> pending-deposits lookup from `state.pendingDeposits`.
38
+ * No BLS work is done here; signature verification happens lazily in `hasPendingValidator`.
39
+ */
40
+ static build(state: CachedBeaconStateGloas): PendingDepositsLookup {
41
+ const lookup = PendingDepositsLookup.buildEmpty();
42
+ for (const pendingDeposit of state.pendingDeposits.getAllReadonly()) {
43
+ lookup.add(pendingDeposit);
44
+ }
45
+ return lookup;
46
+ }
47
+
48
+ /**
49
+ * Append a pending deposit to the represented sequence.
50
+ * Pass `pubkeyHex` if the caller has already computed it.
51
+ */
52
+ add(pendingDeposit: electra.PendingDeposit, pubkeyHex?: PubkeyHex): void {
53
+ const key = pubkeyHex ?? toPubkeyHex(pendingDeposit.pubkey);
54
+ const existing = this.depositsByPubkey.get(key);
55
+ if (existing) {
56
+ existing.push(pendingDeposit);
57
+ } else {
58
+ this.depositsByPubkey.set(key, [pendingDeposit]);
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Returns true if any pending deposit for `pubkeyHex` has a valid BLS deposit signature.
64
+ * Memoizes the result in `validationCache` so repeated checks for the same pubkey
65
+ * within a block only verify deposits that have not already been checked.
66
+ */
67
+ hasPendingValidator(config: BeaconConfig, pubkeyHex: PubkeyHex): boolean {
68
+ const validation = this.validationCache.get(pubkeyHex);
69
+ if (validation?.hasValidSignature === true) {
70
+ return true;
71
+ }
72
+
73
+ const deposits = this.depositsByPubkey.get(pubkeyHex);
74
+ if (deposits === undefined) {
75
+ return false;
76
+ }
77
+
78
+ // hasValidSignature is false or undefined; resume from the last validatedCount so
79
+ // previously-checked invalid deposits are not re-verified.
80
+ const startIndex = validation?.validatedCount ?? 0;
81
+ if (startIndex === deposits.length) {
82
+ // Nothing new to check; the cached false result still holds.
83
+ return false;
84
+ }
85
+
86
+ for (let i = startIndex; i < deposits.length; i++) {
87
+ const deposit = deposits[i];
88
+ if (
89
+ isValidDepositSignature(
90
+ config,
91
+ deposit.pubkey,
92
+ deposit.withdrawalCredentials,
93
+ deposit.amount,
94
+ deposit.signature
95
+ )
96
+ ) {
97
+ this.validationCache.set(pubkeyHex, {hasValidSignature: true, validatedCount: i + 1});
98
+ return true;
99
+ }
100
+ }
101
+
102
+ this.validationCache.set(pubkeyHex, {hasValidSignature: false, validatedCount: deposits.length});
103
+ return false;
104
+ }
105
+ }
@@ -17,29 +17,31 @@ import {computeStartSlotAtEpoch} from "./epoch.js";
17
17
  import {EpochShuffling} from "./epochShuffling.js";
18
18
 
19
19
  /**
20
- * Returns the block root which decided the proposer shuffling for the current epoch. This root
21
- * can be used to key this proposer shuffling.
20
+ * Block root that decided the proposer shuffling for `proposalEpoch` (keys that shuffling).
21
+ * Computed for the requested epoch, not `state.epoch`, so it is correct when `state` is one
22
+ * epoch off the requested epoch (e.g. serving next-epoch duties from the current state).
22
23
  *
23
- * Returns `null` on the one-off scenario where the genesis block decides its own shuffling.
24
- * It should be set to the latest block applied to this `state` or the genesis block root.
24
+ * Returns `null` when the genesis block decides its own shuffling (caller falls back to the
25
+ * genesis block root).
25
26
  */
26
- export function proposerShufflingDecisionRoot(fork: ForkName, state: IBeaconStateView): Root | null {
27
- const decisionSlot = proposerShufflingDecisionSlot(fork, state);
27
+ export function proposerShufflingDecisionRoot(
28
+ fork: ForkName,
29
+ state: IBeaconStateView,
30
+ proposalEpoch: Epoch
31
+ ): Root | null {
32
+ const decisionSlot = proposerShufflingDecisionSlot(fork, proposalEpoch);
28
33
  if (state.slot === decisionSlot) {
29
34
  return null;
30
35
  }
31
36
  return state.getBlockRootAtSlot(decisionSlot);
32
37
  }
33
38
 
34
- /**
35
- * Returns the slot at which the proposer shuffling was decided. The block root at this slot
36
- * can be used to key the proposer shuffling for the current epoch.
37
- */
38
- function proposerShufflingDecisionSlot(fork: ForkName, state: IBeaconStateView): Slot {
39
- // After fulu, the decision slot is in previous epoch due to deterministic proposer lookahead
40
- const epoch = isForkPostFulu(fork) ? state.epoch - 1 : state.epoch;
41
- const startSlot = computeStartSlotAtEpoch(epoch);
42
- return Math.max(startSlot - 1, 0);
39
+ /** Slot whose block root keys the proposer shuffling for `proposalEpoch`. */
40
+ function proposerShufflingDecisionSlot(fork: ForkName, proposalEpoch: Epoch): Slot {
41
+ // Post-Fulu the shuffling is decided one epoch earlier (deterministic proposer lookahead,
42
+ // MIN_SEED_LOOKAHEAD = 1); pre-Fulu it is the last block before `proposalEpoch`.
43
+ const decisionEpoch = isForkPostFulu(fork) ? proposalEpoch - 1 : proposalEpoch;
44
+ return Math.max(computeStartSlotAtEpoch(decisionEpoch) - 1, 0);
43
45
  }
44
46
 
45
47
  /**
@@ -44,7 +44,12 @@ export function getActiveValidatorIndices(state: BeaconStateAllForks, epoch: Epo
44
44
  return new Uint32Array(indices);
45
45
  }
46
46
 
47
- export function getActivationChurnLimit(config: ChainForkConfig, fork: ForkSeq, activeValidatorCount: number): number {
47
+ // Deneb fork upgrade only
48
+ export function getValidatorActivationChurnLimit(
49
+ config: ChainForkConfig,
50
+ fork: ForkSeq,
51
+ activeValidatorCount: number
52
+ ): number {
48
53
  if (fork >= ForkSeq.deneb) {
49
54
  return Math.min(config.MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT, getChurnLimit(config, activeValidatorCount));
50
55
  }
@@ -84,7 +89,42 @@ export function getActivationExitChurnLimit(epochCtx: EpochCache): number {
84
89
  return Math.min(epochCtx.config.MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT, getBalanceChurnLimitFromCache(epochCtx));
85
90
  }
86
91
 
87
- export function getConsolidationChurnLimit(epochCtx: EpochCache): number {
92
+ /**
93
+ * https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.6/specs/gloas/beacon-chain.md#new-get_activation_churn_limit
94
+ */
95
+ export function getActivationChurnLimit(epochCtx: EpochCache): number {
96
+ const churn = getBalanceChurnLimit(
97
+ epochCtx.totalActiveBalanceIncrements,
98
+ epochCtx.config.CHURN_LIMIT_QUOTIENT_GLOAS,
99
+ epochCtx.config.MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA
100
+ );
101
+ return Math.min(epochCtx.config.MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT_GLOAS, churn);
102
+ }
103
+
104
+ /**
105
+ * https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.6/specs/gloas/beacon-chain.md#new-get_exit_churn_limit
106
+ */
107
+ export function getExitChurnLimit(epochCtx: EpochCache): number {
108
+ return getBalanceChurnLimit(
109
+ epochCtx.totalActiveBalanceIncrements,
110
+ epochCtx.config.CHURN_LIMIT_QUOTIENT_GLOAS,
111
+ epochCtx.config.MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA
112
+ );
113
+ }
114
+
115
+ /**
116
+ * Spec (electra): get_consolidation_churn_limit (uses combined balance churn minus activation+exit churn)
117
+ * Spec (gloas): get_consolidation_churn_limit (independent quotient, no MIN floor)
118
+ */
119
+ export function getConsolidationChurnLimit(fork: ForkSeq, epochCtx: EpochCache): number {
120
+ if (fork >= ForkSeq.gloas) {
121
+ // No MIN floor — pass 0 so getBalanceChurnLimit's max(churn, min) is a no-op.
122
+ return getBalanceChurnLimit(
123
+ epochCtx.totalActiveBalanceIncrements,
124
+ epochCtx.config.CONSOLIDATION_CHURN_LIMIT_QUOTIENT,
125
+ 0
126
+ );
127
+ }
88
128
  return getBalanceChurnLimitFromCache(epochCtx) - getActivationExitChurnLimit(epochCtx);
89
129
  }
90
130