@lodestar/state-transition 1.43.0-dev.5f9285892c → 1.43.0-dev.9c8becae00

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 (44) hide show
  1. package/lib/cache/epochCache.d.ts +3 -1
  2. package/lib/cache/epochCache.d.ts.map +1 -1
  3. package/lib/cache/epochCache.js +31 -13
  4. package/lib/cache/epochCache.js.map +1 -1
  5. package/lib/cache/epochTransitionCache.d.ts +5 -0
  6. package/lib/cache/epochTransitionCache.d.ts.map +1 -1
  7. package/lib/cache/epochTransitionCache.js +1 -0
  8. package/lib/cache/epochTransitionCache.js.map +1 -1
  9. package/lib/epoch/index.d.ts +3 -1
  10. package/lib/epoch/index.d.ts.map +1 -1
  11. package/lib/epoch/index.js +8 -1
  12. package/lib/epoch/index.js.map +1 -1
  13. package/lib/epoch/processPtcWindow.d.ts +11 -0
  14. package/lib/epoch/processPtcWindow.d.ts.map +1 -0
  15. package/lib/epoch/processPtcWindow.js +28 -0
  16. package/lib/epoch/processPtcWindow.js.map +1 -0
  17. package/lib/slot/upgradeStateToGloas.d.ts.map +1 -1
  18. package/lib/slot/upgradeStateToGloas.js +2 -1
  19. package/lib/slot/upgradeStateToGloas.js.map +1 -1
  20. package/lib/stateTransition.d.ts +1 -2
  21. package/lib/stateTransition.d.ts.map +1 -1
  22. package/lib/stateTransition.js +1 -2
  23. package/lib/stateTransition.js.map +1 -1
  24. package/lib/stateView/beaconStateView.d.ts +1 -7
  25. package/lib/stateView/beaconStateView.d.ts.map +1 -1
  26. package/lib/stateView/beaconStateView.js +11 -23
  27. package/lib/stateView/beaconStateView.js.map +1 -1
  28. package/lib/stateView/interface.d.ts +8 -8
  29. package/lib/stateView/interface.d.ts.map +1 -1
  30. package/lib/stateView/interface.js.map +1 -1
  31. package/lib/util/gloas.d.ts +8 -3
  32. package/lib/util/gloas.d.ts.map +1 -1
  33. package/lib/util/gloas.js +26 -1
  34. package/lib/util/gloas.js.map +1 -1
  35. package/package.json +8 -8
  36. package/src/cache/epochCache.ts +32 -30
  37. package/src/cache/epochTransitionCache.ts +7 -0
  38. package/src/epoch/index.ts +9 -0
  39. package/src/epoch/processPtcWindow.ts +38 -0
  40. package/src/slot/upgradeStateToGloas.ts +2 -1
  41. package/src/stateTransition.ts +1 -2
  42. package/src/stateView/beaconStateView.ts +12 -27
  43. package/src/stateView/interface.ts +13 -8
  44. package/src/util/gloas.ts +49 -3
@@ -158,6 +158,12 @@ export interface EpochTransitionCache {
158
158
  */
159
159
  nextShuffling: EpochShuffling | null;
160
160
 
161
+ /**
162
+ * Pre-computed PTC for epoch N + MIN_SEED_LOOKAHEAD + 1, populated by processPtcWindow (Gloas+).
163
+ * Used by finalProcessEpoch to shift PTC arrays in epoch cache without reading from state.
164
+ */
165
+ nextEpochPayloadTimelinessCommittees: Uint32Array[] | null;
166
+
161
167
  /**
162
168
  * Altair specific, this is total active balances for the next epoch.
163
169
  * This is only used in `afterProcessEpoch` to compute base reward and sync participant reward.
@@ -502,6 +508,7 @@ export function beforeProcessEpoch(
502
508
  indicesToEject,
503
509
  nextShufflingActiveIndices,
504
510
  nextShuffling: null,
511
+ nextEpochPayloadTimelinessCommittees: null,
505
512
  // to be updated in processEffectiveBalanceUpdates
506
513
  nextEpochTotalActiveBalanceByIncrement: 0,
507
514
  isActivePrevEpoch,
@@ -28,6 +28,7 @@ import {processParticipationRecordUpdates} from "./processParticipationRecordUpd
28
28
  import {processPendingConsolidations} from "./processPendingConsolidations.js";
29
29
  import {processPendingDeposits} from "./processPendingDeposits.js";
30
30
  import {processProposerLookahead} from "./processProposerLookahead.js";
31
+ import {processPtcWindow} from "./processPtcWindow.js";
31
32
  import {processRandaoMixesReset} from "./processRandaoMixesReset.js";
32
33
  import {processRegistryUpdates} from "./processRegistryUpdates.js";
33
34
  import {processRewardsAndPenalties} from "./processRewardsAndPenalties.js";
@@ -55,6 +56,7 @@ export {
55
56
  processPendingDeposits,
56
57
  processPendingConsolidations,
57
58
  processProposerLookahead,
59
+ processPtcWindow,
58
60
  processBuilderPendingPayments,
59
61
  };
60
62
 
@@ -81,6 +83,7 @@ export enum EpochTransitionStep {
81
83
  processPendingDeposits = "processPendingDeposits",
82
84
  processPendingConsolidations = "processPendingConsolidations",
83
85
  processProposerLookahead = "processProposerLookahead",
86
+ processPtcWindow = "processPtcWindow",
84
87
  processBuilderPendingPayments = "processBuilderPendingPayments",
85
88
  }
86
89
 
@@ -211,4 +214,10 @@ export function processEpoch(
211
214
  processProposerLookahead(fork, state as CachedBeaconStateFulu, cache);
212
215
  timer?.();
213
216
  }
217
+
218
+ if (fork >= ForkSeq.gloas) {
219
+ const timer = metrics?.epochTransitionStepTime.startTimer({step: EpochTransitionStep.processPtcWindow});
220
+ processPtcWindow(state as CachedBeaconStateGloas, cache);
221
+ timer?.();
222
+ }
214
223
  }
@@ -0,0 +1,38 @@
1
+ import {MIN_SEED_LOOKAHEAD} from "@lodestar/params";
2
+ import {ssz} from "@lodestar/types";
3
+ import {CachedBeaconStateGloas, EpochTransitionCache} from "../types.js";
4
+ import {computeEpochShuffling} from "../util/epochShuffling.js";
5
+ import {computePayloadTimelinessCommitteesForEpoch} from "../util/seed.js";
6
+
7
+ /**
8
+ * Update the `ptc_window` field in the beacon state by shifting out the oldest epoch's
9
+ * PTC entries and appending newly computed entries for the next lookahead epoch.
10
+ * Stashes the computed PTCs in the transition cache for finalProcessEpoch to shift
11
+ * into the epoch cache without reading from state.
12
+ *
13
+ * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.4/specs/gloas/beacon-chain.md#new-process_ptc_window
14
+ */
15
+ export function processPtcWindow(state: CachedBeaconStateGloas, cache: EpochTransitionCache): void {
16
+ const nextEpoch = state.epochCtx.epoch + MIN_SEED_LOOKAHEAD + 1;
17
+ const nextEpochShuffling =
18
+ cache.nextShuffling ?? computeEpochShuffling(state, cache.nextShufflingActiveIndices, nextEpoch);
19
+ cache.nextShuffling = nextEpochShuffling;
20
+
21
+ const newNextPayloadTimelinessCommittees = computePayloadTimelinessCommitteesForEpoch(
22
+ state,
23
+ nextEpoch,
24
+ nextEpochShuffling.committees,
25
+ state.epochCtx.effectiveBalanceIncrements
26
+ );
27
+
28
+ // Stash for finalProcessEpoch to shift into epoch cache
29
+ cache.nextEpochPayloadTimelinessCommittees = newNextPayloadTimelinessCommittees;
30
+
31
+ // Write shifted window to state: current(N) + next(N+1) + newlyComputed(N+2)
32
+ // From the perspective of upcoming epoch N+1, this is previous + current + next
33
+ state.ptcWindow = ssz.gloas.PtcWindow.toViewDU([
34
+ ...state.epochCtx.payloadTimelinessCommittees,
35
+ ...state.epochCtx.nextPayloadTimelinessCommittees,
36
+ ...newNextPayloadTimelinessCommittees,
37
+ ]);
38
+ }
@@ -5,7 +5,7 @@ import {isValidDepositSignature} from "../block/processDeposit.js";
5
5
  import {applyDepositForBuilder} from "../block/processDepositRequest.js";
6
6
  import {getCachedBeaconState} from "../cache/stateCache.js";
7
7
  import {CachedBeaconStateFulu, CachedBeaconStateGloas} from "../types.js";
8
- import {isBuilderWithdrawalCredential} from "../util/gloas.js";
8
+ import {initializePtcWindow, isBuilderWithdrawalCredential} from "../util/gloas.js";
9
9
  import {isValidatorKnown} from "../util/index.js";
10
10
 
11
11
  /**
@@ -61,6 +61,7 @@ export function upgradeStateToGloas(stateFulu: CachedBeaconStateFulu): CachedBea
61
61
  stateGloasView.pendingPartialWithdrawals = stateGloasCloned.pendingPartialWithdrawals;
62
62
  stateGloasView.pendingConsolidations = stateGloasCloned.pendingConsolidations;
63
63
  stateGloasView.proposerLookahead = stateGloasCloned.proposerLookahead;
64
+ stateGloasView.ptcWindow = ssz.gloas.PtcWindow.toViewDU(initializePtcWindow(stateFulu));
64
65
 
65
66
  for (let i = 0; i < SLOTS_PER_HISTORICAL_ROOT; i++) {
66
67
  stateGloasView.executionPayloadAvailability.set(i, true);
@@ -76,7 +76,6 @@ export enum StateHashTreeRootSource {
76
76
  prepareNextEpoch = "prepare_next_epoch",
77
77
  regenState = "regen_state",
78
78
  computeNewStateRoot = "compute_new_state_root",
79
- computePayloadEnvelopeStateRoot = "compute_payload_envelope_state_root",
80
79
  }
81
80
 
82
81
  /**
@@ -283,7 +282,7 @@ function processSlotsWithTransientCache(
283
282
  {
284
283
  const timer = metrics?.epochTransitionStepTime.startTimer({step: EpochTransitionStep.finalProcessEpoch});
285
284
  // last step to prepare epoch data that depends on the upgraded state, for example proposerLookahead of BeaconStateFulu
286
- postState.epochCtx.finalProcessEpoch(postState);
285
+ postState.epochCtx.finalProcessEpoch(postState, epochTransitionCache);
287
286
  timer?.();
288
287
  }
289
288
 
@@ -84,8 +84,6 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
84
84
  private _currentEpochParticipation: Uint8Array | null = null;
85
85
  // bellatrix
86
86
  private _latestExecutionPayloadHeader: ExecutionPayloadHeader | null = null;
87
- // Caches the cross-fork latestBlockHash value
88
- private _latestBlockHash: Bytes32 | null = null;
89
87
  // capella
90
88
  private _historicalSummaries: capella.HistoricalSummaries | null = null;
91
89
  // electra
@@ -218,9 +216,13 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
218
216
  // bellatrix
219
217
 
220
218
  get latestExecutionPayloadHeader(): ExecutionPayloadHeader {
221
- if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.bellatrix) {
219
+ const forkSeq = this.config.getForkSeq(this.cachedState.slot);
220
+ if (forkSeq < ForkSeq.bellatrix) {
222
221
  throw new Error("latestExecutionPayloadHeader is not available before Bellatrix");
223
222
  }
223
+ if (forkSeq >= ForkSeq.gloas) {
224
+ throw new Error("latestExecutionPayloadHeader is not available after Gloas");
225
+ }
224
226
 
225
227
  if (this._latestExecutionPayloadHeader === null) {
226
228
  this._latestExecutionPayloadHeader = (
@@ -231,30 +233,6 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
231
233
  return this._latestExecutionPayloadHeader;
232
234
  }
233
235
 
234
- /**
235
- * Cross-fork accessor for the execution block hash of the most recently included payload.
236
- * Pre-gloas: reads from latestExecutionPayloadHeader.blockHash.
237
- * Gloas+: reads the dedicated latestBlockHash field (EIP-7732).
238
- */
239
- get latestBlockHash(): Bytes32 {
240
- const forkSeq = this.config.getForkSeq(this.cachedState.slot);
241
- if (forkSeq < ForkSeq.bellatrix) {
242
- throw new Error("latestBlockHash is not available before Bellatrix");
243
- }
244
-
245
- if (this._latestBlockHash === null) {
246
- if (forkSeq >= ForkSeq.gloas) {
247
- this._latestBlockHash = (this.cachedState as CachedBeaconStateGloas).latestBlockHash;
248
- } else {
249
- this._latestBlockHash = (
250
- this.cachedState as CachedBeaconStateExecutions
251
- ).latestExecutionPayloadHeader.blockHash;
252
- }
253
- }
254
-
255
- return this._latestBlockHash;
256
- }
257
-
258
236
  /**
259
237
  * The execution block number of the most recently included payload.
260
238
  * Named payloadBlockNumber (not latestBlockNumber) to mirror ExecutionPayloadHeader.blockNumber pre-gloas.
@@ -365,6 +343,13 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
365
343
 
366
344
  // gloas
367
345
 
346
+ get latestBlockHash(): Bytes32 {
347
+ if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.gloas) {
348
+ throw new Error("latestBlockHash is not available before Gloas");
349
+ }
350
+ return (this.cachedState as CachedBeaconStateGloas).latestBlockHash;
351
+ }
352
+
368
353
  get executionPayloadAvailability(): BitArray {
369
354
  if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.gloas) {
370
355
  throw new Error("executionPayloadAvailability is not available before Gloas");
@@ -187,13 +187,6 @@ export interface IBeaconStateViewAltair extends IBeaconStateView {
187
187
  export interface IBeaconStateViewBellatrix extends IBeaconStateViewAltair {
188
188
  forkName: ForkPostBellatrix;
189
189
  latestExecutionPayloadHeader: ExecutionPayloadHeader;
190
- /**
191
- * Cross-fork accessor for the execution block hash of the most recently included payload.
192
- * Pre-gloas: returns latestExecutionPayloadHeader.blockHash (bellatrix–fulu).
193
- * Gloas+: returns the dedicated latestBlockHash state field (EIP-7732).
194
- * Throws before bellatrix.
195
- */
196
- latestBlockHash: Bytes32;
197
190
  /**
198
191
  * The execution block number of the most recently included payload.
199
192
  * Named payloadBlockNumber (not latestBlockNumber) to mirror ExecutionPayloadHeader.blockNumber pre-gloas.
@@ -244,6 +237,11 @@ export interface IBeaconStateViewFulu extends IBeaconStateViewElectra {
244
237
  /** Gloas+ state fields — use isStatePostGloas() guard */
245
238
  export interface IBeaconStateViewGloas extends IBeaconStateViewFulu {
246
239
  forkName: ForkPostGloas;
240
+ /** Removed from BeaconState in gloas. Use `latestBlockHash` instead. */
241
+ latestExecutionPayloadHeader: never;
242
+ /** Removed from BeaconState in gloas. */
243
+ payloadBlockNumber: never;
244
+ latestBlockHash: Bytes32;
247
245
  executionPayloadAvailability: BitArray;
248
246
  latestExecutionPayloadBid: ExecutionPayloadBid;
249
247
  payloadExpectedWithdrawals: capella.Withdrawal[];
@@ -262,7 +260,14 @@ export interface IBeaconStateViewGloas extends IBeaconStateViewFulu {
262
260
  * forkName as ForkName since the class wraps any fork's state.
263
261
  * Sub-interfaces retain their narrowed forkName discriminants for caller-side type guards.
264
262
  */
265
- export type IBeaconStateViewLatestFork = Omit<IBeaconStateViewGloas, "forkName"> & {forkName: ForkName};
263
+ export type IBeaconStateViewLatestFork = Omit<
264
+ IBeaconStateViewGloas,
265
+ "forkName" | "latestExecutionPayloadHeader" | "payloadBlockNumber"
266
+ > & {
267
+ forkName: ForkName;
268
+ latestExecutionPayloadHeader: ExecutionPayloadHeader;
269
+ payloadBlockNumber: number;
270
+ };
266
271
  export function isStatePostAltair(state: IBeaconStateView): state is IBeaconStateViewAltair {
267
272
  return isForkPostAltair(state.forkName);
268
273
  }
package/src/util/gloas.ts CHANGED
@@ -6,16 +6,20 @@ import {
6
6
  EFFECTIVE_BALANCE_INCREMENT,
7
7
  FAR_FUTURE_EPOCH,
8
8
  MIN_DEPOSIT_AMOUNT,
9
+ MIN_SEED_LOOKAHEAD,
10
+ PTC_SIZE,
9
11
  SLOTS_PER_EPOCH,
10
12
  } from "@lodestar/params";
11
13
  import {BuilderIndex, Epoch, ValidatorIndex, gloas} from "@lodestar/types";
12
14
  import {AttestationData} from "@lodestar/types/phase0";
13
15
  import {byteArrayEquals} from "@lodestar/utils";
14
- import {IBeaconStateViewGloas} from "../stateView/interface.js";
15
- import {CachedBeaconStateGloas} from "../types.js";
16
+ import {CachedBeaconStateFulu, CachedBeaconStateGloas} from "../types.js";
16
17
  import {getBlockRootAtSlot} from "./blockRoot.js";
17
18
  import {computeEpochAtSlot} from "./epoch.js";
19
+ import {computeEpochShuffling} from "./epochShuffling.js";
18
20
  import {RootCache} from "./rootCache.js";
21
+ import {computePayloadTimelinessCommitteesForEpoch} from "./seed.js";
22
+ import {getActiveValidatorIndices} from "./validator.js";
19
23
 
20
24
  export function isBuilderWithdrawalCredential(withdrawalCredentials: Uint8Array): boolean {
21
25
  return withdrawalCredentials[0] === BUILDER_WITHDRAWAL_PREFIX;
@@ -168,6 +172,48 @@ export function isAttestationSameSlotRootCache(rootCache: RootCache, data: Attes
168
172
  return isMatchingBlockRoot && isCurrentBlockRoot;
169
173
  }
170
174
 
171
- export function isParentBlockFull(state: CachedBeaconStateGloas | IBeaconStateViewGloas): boolean {
175
+ export function isParentBlockFull(state: CachedBeaconStateGloas): boolean {
172
176
  return byteArrayEquals(state.latestExecutionPayloadBid.blockHash, state.latestBlockHash);
173
177
  }
178
+
179
+ export function initializePtcWindow(state: CachedBeaconStateFulu): Uint32Array[] {
180
+ const ptcWindow: Uint32Array[] = Array.from({length: SLOTS_PER_EPOCH}, () => new Uint32Array(PTC_SIZE));
181
+ const currentEpoch = state.epochCtx.epoch;
182
+
183
+ for (let epochOffset = 0; epochOffset <= MIN_SEED_LOOKAHEAD; epochOffset++) {
184
+ const epoch = currentEpoch + epochOffset;
185
+ const shuffling =
186
+ state.epochCtx.getShufflingAtEpochOrNull(epoch) ??
187
+ computeEpochShuffling(state, getActiveValidatorIndices(state, epoch), epoch);
188
+
189
+ ptcWindow.push(
190
+ ...computePayloadTimelinessCommitteesForEpoch(
191
+ state,
192
+ epoch,
193
+ shuffling.committees,
194
+ state.epochCtx.effectiveBalanceIncrements
195
+ )
196
+ );
197
+ }
198
+
199
+ return ptcWindow;
200
+ }
201
+
202
+ export function getPtcWindowEpochCacheData(state: CachedBeaconStateGloas): {
203
+ previousPayloadTimelinessCommittees: Uint32Array[];
204
+ payloadTimelinessCommittees: Uint32Array[];
205
+ nextPayloadTimelinessCommittees: Uint32Array[];
206
+ } {
207
+ const toUint32Arrays = (views: ReturnType<typeof state.ptcWindow.getReadonlyByRange>) =>
208
+ views.map((v) => Uint32Array.from(v.getAll()));
209
+
210
+ const previousPtcWindow = state.ptcWindow.getReadonlyByRange(0, SLOTS_PER_EPOCH);
211
+ const currentPtcWindow = state.ptcWindow.getReadonlyByRange(SLOTS_PER_EPOCH, SLOTS_PER_EPOCH);
212
+ const nextPtcWindow = state.ptcWindow.getReadonlyByRange(2 * SLOTS_PER_EPOCH, SLOTS_PER_EPOCH);
213
+
214
+ return {
215
+ previousPayloadTimelinessCommittees: toUint32Arrays(previousPtcWindow),
216
+ payloadTimelinessCommittees: toUint32Arrays(currentPtcWindow),
217
+ nextPayloadTimelinessCommittees: toUint32Arrays(nextPtcWindow),
218
+ };
219
+ }