@lodestar/state-transition 1.45.0-dev.b620872107 → 1.45.0-dev.b98df0df3d

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 (101) hide show
  1. package/lib/block/processAttestationsAltair.d.ts +4 -1
  2. package/lib/block/processAttestationsAltair.d.ts.map +1 -1
  3. package/lib/block/processAttestationsAltair.js +7 -5
  4. package/lib/block/processAttestationsAltair.js.map +1 -1
  5. package/lib/block/processBuilderDepositRequest.d.ts.map +1 -1
  6. package/lib/block/processBuilderDepositRequest.js +13 -7
  7. package/lib/block/processBuilderDepositRequest.js.map +1 -1
  8. package/lib/block/processOperations.d.ts.map +1 -1
  9. package/lib/block/processOperations.js +17 -1
  10. package/lib/block/processOperations.js.map +1 -1
  11. package/lib/block/processParentExecutionPayload.d.ts.map +1 -1
  12. package/lib/block/processParentExecutionPayload.js +15 -1
  13. package/lib/block/processParentExecutionPayload.js.map +1 -1
  14. package/lib/block/processWithdrawals.js +1 -1
  15. package/lib/block/processWithdrawals.js.map +1 -1
  16. package/lib/cache/stateCache.d.ts.map +1 -1
  17. package/lib/cache/stateCache.js +2 -4
  18. package/lib/cache/stateCache.js.map +1 -1
  19. package/lib/epoch/processParticipationFlagUpdates.d.ts +2 -2
  20. package/lib/epoch/processParticipationFlagUpdates.d.ts.map +1 -1
  21. package/lib/epoch/processParticipationFlagUpdates.js +13 -0
  22. package/lib/epoch/processParticipationFlagUpdates.js.map +1 -1
  23. package/lib/epoch/processRewardsAndPenalties.d.ts.map +1 -1
  24. package/lib/epoch/processRewardsAndPenalties.js +1 -2
  25. package/lib/epoch/processRewardsAndPenalties.js.map +1 -1
  26. package/lib/lightClient/proofs.d.ts.map +1 -1
  27. package/lib/lightClient/proofs.js +28 -2
  28. package/lib/lightClient/proofs.js.map +1 -1
  29. package/lib/lightClient/spec/index.d.ts.map +1 -1
  30. package/lib/lightClient/spec/index.js +7 -4
  31. package/lib/lightClient/spec/index.js.map +1 -1
  32. package/lib/lightClient/spec/utils.d.ts +7 -1
  33. package/lib/lightClient/spec/utils.d.ts.map +1 -1
  34. package/lib/lightClient/spec/utils.js +123 -14
  35. package/lib/lightClient/spec/utils.js.map +1 -1
  36. package/lib/lightClient/spec/validateLightClientBootstrap.d.ts.map +1 -1
  37. package/lib/lightClient/spec/validateLightClientBootstrap.js +2 -7
  38. package/lib/lightClient/spec/validateLightClientBootstrap.js.map +1 -1
  39. package/lib/lightClient/spec/validateLightClientUpdate.d.ts.map +1 -1
  40. package/lib/lightClient/spec/validateLightClientUpdate.js +6 -5
  41. package/lib/lightClient/spec/validateLightClientUpdate.js.map +1 -1
  42. package/lib/lightClient/types.d.ts +3 -1
  43. package/lib/lightClient/types.d.ts.map +1 -1
  44. package/lib/metrics.js +2 -4
  45. package/lib/metrics.js.map +1 -1
  46. package/lib/slot/upgradeStateToAltair.js +1 -1
  47. package/lib/slot/upgradeStateToAltair.js.map +1 -1
  48. package/lib/slot/upgradeStateToGloas.d.ts.map +1 -1
  49. package/lib/slot/upgradeStateToGloas.js +48 -9
  50. package/lib/slot/upgradeStateToGloas.js.map +1 -1
  51. package/lib/stateView/beaconStateView.d.ts +0 -1
  52. package/lib/stateView/beaconStateView.d.ts.map +1 -1
  53. package/lib/stateView/beaconStateView.js +0 -8
  54. package/lib/stateView/beaconStateView.js.map +1 -1
  55. package/lib/stateView/interface.d.ts +0 -1
  56. package/lib/stateView/interface.d.ts.map +1 -1
  57. package/lib/stateView/interface.js.map +1 -1
  58. package/lib/stateView/nativeBeaconStateView.d.ts +0 -2
  59. package/lib/stateView/nativeBeaconStateView.d.ts.map +1 -1
  60. package/lib/stateView/nativeBeaconStateView.js +0 -9
  61. package/lib/stateView/nativeBeaconStateView.js.map +1 -1
  62. package/lib/util/execution.d.ts.map +1 -1
  63. package/lib/util/execution.js +7 -2
  64. package/lib/util/execution.js.map +1 -1
  65. package/lib/util/gloas.d.ts +3 -1
  66. package/lib/util/gloas.d.ts.map +1 -1
  67. package/lib/util/gloas.js +8 -11
  68. package/lib/util/gloas.js.map +1 -1
  69. package/lib/util/index.d.ts +1 -0
  70. package/lib/util/index.d.ts.map +1 -1
  71. package/lib/util/index.js +1 -0
  72. package/lib/util/index.js.map +1 -1
  73. package/lib/util/ssz.d.ts +9 -0
  74. package/lib/util/ssz.d.ts.map +1 -0
  75. package/lib/util/ssz.js +39 -0
  76. package/lib/util/ssz.js.map +1 -0
  77. package/package.json +11 -11
  78. package/src/block/processAttestationsAltair.ts +9 -6
  79. package/src/block/processBuilderDepositRequest.ts +21 -8
  80. package/src/block/processOperations.ts +28 -1
  81. package/src/block/processParentExecutionPayload.ts +26 -1
  82. package/src/block/processWithdrawals.ts +1 -1
  83. package/src/cache/stateCache.ts +2 -4
  84. package/src/epoch/processParticipationFlagUpdates.ts +20 -2
  85. package/src/epoch/processRewardsAndPenalties.ts +1 -2
  86. package/src/lightClient/proofs.ts +36 -1
  87. package/src/lightClient/spec/index.ts +8 -4
  88. package/src/lightClient/spec/utils.ts +193 -25
  89. package/src/lightClient/spec/validateLightClientBootstrap.ts +3 -10
  90. package/src/lightClient/spec/validateLightClientUpdate.ts +11 -21
  91. package/src/lightClient/types.ts +3 -1
  92. package/src/metrics.ts +2 -4
  93. package/src/slot/upgradeStateToAltair.ts +1 -1
  94. package/src/slot/upgradeStateToGloas.ts +84 -9
  95. package/src/stateView/beaconStateView.ts +0 -8
  96. package/src/stateView/interface.ts +0 -1
  97. package/src/stateView/nativeBeaconStateView.ts +0 -10
  98. package/src/util/execution.ts +9 -4
  99. package/src/util/gloas.ts +9 -14
  100. package/src/util/index.ts +1 -0
  101. package/src/util/ssz.ts +42 -0
@@ -1,16 +1,25 @@
1
1
  import {PublicKey} from "@chainsafe/blst";
2
+ import {Tree} from "@chainsafe/persistent-merkle-tree";
2
3
  import {BitArray} from "@chainsafe/ssz";
3
4
  import {ChainForkConfig} from "@lodestar/config";
4
5
  import {
5
- BLOCK_BODY_EXECUTION_PAYLOAD_DEPTH as EXECUTION_PAYLOAD_DEPTH,
6
- BLOCK_BODY_EXECUTION_PAYLOAD_INDEX as EXECUTION_PAYLOAD_INDEX,
7
- FINALIZED_ROOT_DEPTH,
8
- FINALIZED_ROOT_DEPTH_ELECTRA,
6
+ BLOCK_BODY_EXECUTION_PAYLOAD_GINDEX,
7
+ CURRENT_SYNC_COMMITTEE_GINDEX,
8
+ CURRENT_SYNC_COMMITTEE_GINDEX_ELECTRA,
9
+ CURRENT_SYNC_COMMITTEE_GINDEX_GLOAS,
10
+ EXECUTION_BLOCK_HASH_GINDEX,
11
+ EXECUTION_BLOCK_HASH_GINDEX_DENEB,
12
+ EXECUTION_BLOCK_HASH_GINDEX_GLOAS,
13
+ FINALIZED_ROOT_GINDEX,
14
+ FINALIZED_ROOT_GINDEX_ELECTRA,
15
+ FINALIZED_ROOT_GINDEX_GLOAS,
9
16
  ForkName,
10
17
  ForkSeq,
11
- NEXT_SYNC_COMMITTEE_DEPTH,
12
- NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA,
18
+ NEXT_SYNC_COMMITTEE_GINDEX,
19
+ NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA,
20
+ NEXT_SYNC_COMMITTEE_GINDEX_GLOAS,
13
21
  isForkPostElectra,
22
+ isForkPostGloas,
14
23
  } from "@lodestar/params";
15
24
  import {
16
25
  BeaconBlockHeader,
@@ -74,17 +83,11 @@ export function getSafetyThreshold(maxActiveParticipants: number): number {
74
83
  }
75
84
 
76
85
  export function getZeroSyncCommitteeBranch(fork: ForkName): Uint8Array[] {
77
- const nextSyncCommitteeDepth = isForkPostElectra(fork)
78
- ? NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA
79
- : NEXT_SYNC_COMMITTEE_DEPTH;
80
-
81
- return Array.from({length: nextSyncCommitteeDepth}, () => ZERO_HASH);
86
+ return Array.from({length: getGindexDepth(nextSyncCommitteeGindexAtFork(fork))}, () => ZERO_HASH);
82
87
  }
83
88
 
84
89
  export function getZeroFinalityBranch(fork: ForkName): Uint8Array[] {
85
- const finalizedRootDepth = isForkPostElectra(fork) ? FINALIZED_ROOT_DEPTH_ELECTRA : FINALIZED_ROOT_DEPTH;
86
-
87
- return Array.from({length: finalizedRootDepth}, () => ZERO_HASH);
90
+ return Array.from({length: getGindexDepth(finalizedRootGindexAtFork(fork))}, () => ZERO_HASH);
88
91
  }
89
92
 
90
93
  export function isSyncCommitteeUpdate(update: LightClientUpdate): boolean {
@@ -129,12 +132,73 @@ export function isValidMerkleBranch(
129
132
  return verifyMerkleBranch(leaf, branch, depth, index, root);
130
133
  }
131
134
 
132
- export function normalizeMerkleBranch(branch: Uint8Array[], depth: number): Uint8Array[] {
135
+ export function isValidNormalizedMerkleBranch(
136
+ leaf: Uint8Array,
137
+ branch: Uint8Array[],
138
+ gindex: number,
139
+ root: Uint8Array
140
+ ): boolean {
141
+ const depth = getGindexDepth(gindex);
142
+ const index = getGindexIndex(gindex);
143
+ const numExtraDepth = branch.length - depth;
144
+ if (numExtraDepth < 0) {
145
+ return false;
146
+ }
147
+
148
+ for (let i = 0; i < numExtraDepth; i++) {
149
+ if (!byteArrayEquals(branch[i], ZERO_HASH)) {
150
+ return false;
151
+ }
152
+ }
153
+
154
+ return isValidMerkleBranch(leaf, branch.slice(numExtraDepth), depth, index, root);
155
+ }
156
+
157
+ export function normalizeMerkleBranch(branch: Uint8Array[], gindex: number): Uint8Array[] {
158
+ const depth = getGindexDepth(gindex);
133
159
  const numExtraDepth = depth - branch.length;
134
160
 
135
161
  return [...Array.from({length: numExtraDepth}, () => ZERO_HASH), ...branch];
136
162
  }
137
163
 
164
+ export function currentSyncCommitteeGindexAtFork(fork: ForkName): number {
165
+ if (isForkPostGloas(fork)) {
166
+ return CURRENT_SYNC_COMMITTEE_GINDEX_GLOAS;
167
+ }
168
+ if (isForkPostElectra(fork)) {
169
+ return CURRENT_SYNC_COMMITTEE_GINDEX_ELECTRA;
170
+ }
171
+ return CURRENT_SYNC_COMMITTEE_GINDEX;
172
+ }
173
+
174
+ export function finalizedRootGindexAtFork(fork: ForkName): number {
175
+ if (isForkPostGloas(fork)) {
176
+ return FINALIZED_ROOT_GINDEX_GLOAS;
177
+ }
178
+ if (isForkPostElectra(fork)) {
179
+ return FINALIZED_ROOT_GINDEX_ELECTRA;
180
+ }
181
+ return FINALIZED_ROOT_GINDEX;
182
+ }
183
+
184
+ export function nextSyncCommitteeGindexAtFork(fork: ForkName): number {
185
+ if (isForkPostGloas(fork)) {
186
+ return NEXT_SYNC_COMMITTEE_GINDEX_GLOAS;
187
+ }
188
+ if (isForkPostElectra(fork)) {
189
+ return NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA;
190
+ }
191
+ return NEXT_SYNC_COMMITTEE_GINDEX;
192
+ }
193
+
194
+ export function getGindexDepth(gindex: number): number {
195
+ return Math.floor(Math.log2(gindex));
196
+ }
197
+
198
+ export function getGindexIndex(gindex: number): number {
199
+ return gindex - 2 ** getGindexDepth(gindex);
200
+ }
201
+
138
202
  export function upgradeLightClientHeader(
139
203
  config: ChainForkConfig,
140
204
  targetFork: ForkName,
@@ -147,7 +211,7 @@ export function upgradeLightClientHeader(
147
211
 
148
212
  // We are modifying the same header object, may be we could create a copy, but its
149
213
  // not required as of now
150
- const upgradedHeader = header;
214
+ let upgradedHeader = header;
151
215
  const startUpgradeFromFork = Object.values(ForkName)[ForkSeq[headerFork] + 1];
152
216
 
153
217
  switch (startUpgradeFromFork) {
@@ -198,7 +262,11 @@ export function upgradeLightClientHeader(
198
262
  if (ForkSeq[targetFork] <= ForkSeq.fulu) break;
199
263
 
200
264
  case ForkName.gloas:
201
- // No changes to LightClientHeader in Gloas
265
+ if (isGloasLightClientHeader(upgradedHeader)) {
266
+ break;
267
+ }
268
+
269
+ upgradedHeader = upgradeLightClientHeaderToGloas(config, upgradedHeader as LightClientHeader<ForkName.electra>);
202
270
 
203
271
  // Break if no further upgrades is required else fall through
204
272
  if (ForkSeq[targetFork] <= ForkSeq.gloas) break;
@@ -209,6 +277,40 @@ export function upgradeLightClientHeader(
209
277
  export function isValidLightClientHeader(config: ChainForkConfig, header: LightClientHeader): boolean {
210
278
  const epoch = computeEpochAtSlot(header.beacon.slot);
211
279
 
280
+ if (isGloasLightClientHeader(header)) {
281
+ if (epoch >= config.GLOAS_FORK_EPOCH) {
282
+ return isValidNormalizedMerkleBranch(
283
+ header.executionBlockHash,
284
+ header.executionBranch,
285
+ EXECUTION_BLOCK_HASH_GINDEX_GLOAS,
286
+ header.beacon.bodyRoot
287
+ );
288
+ }
289
+
290
+ if (epoch >= config.DENEB_FORK_EPOCH) {
291
+ return isValidNormalizedMerkleBranch(
292
+ header.executionBlockHash,
293
+ header.executionBranch,
294
+ EXECUTION_BLOCK_HASH_GINDEX_DENEB,
295
+ header.beacon.bodyRoot
296
+ );
297
+ }
298
+
299
+ if (epoch >= config.CAPELLA_FORK_EPOCH) {
300
+ return isValidNormalizedMerkleBranch(
301
+ header.executionBlockHash,
302
+ header.executionBranch,
303
+ EXECUTION_BLOCK_HASH_GINDEX,
304
+ header.beacon.bodyRoot
305
+ );
306
+ }
307
+
308
+ return (
309
+ byteArrayEquals(header.executionBlockHash, ZERO_HASH) &&
310
+ header.executionBranch.every((node) => byteArrayEquals(node, ZERO_HASH))
311
+ );
312
+ }
313
+
212
314
  if (epoch < config.CAPELLA_FORK_EPOCH) {
213
315
  return (
214
316
  ((header as LightClientHeader<ForkName.capella>).execution === undefined ||
@@ -239,8 +341,8 @@ export function isValidLightClientHeader(config: ChainForkConfig, header: LightC
239
341
  .getPostBellatrixForkTypes(header.beacon.slot)
240
342
  .ExecutionPayloadHeader.hashTreeRoot((header as LightClientHeader<ForkName.capella>).execution),
241
343
  (header as LightClientHeader<ForkName.capella>).executionBranch,
242
- EXECUTION_PAYLOAD_DEPTH,
243
- EXECUTION_PAYLOAD_INDEX,
344
+ getGindexDepth(BLOCK_BODY_EXECUTION_PAYLOAD_GINDEX),
345
+ getGindexIndex(BLOCK_BODY_EXECUTION_PAYLOAD_GINDEX),
244
346
  header.beacon.bodyRoot
245
347
  );
246
348
  }
@@ -254,12 +356,9 @@ export function upgradeLightClientUpdate(
254
356
  update.finalizedHeader = upgradeLightClientHeader(config, targetFork, update.finalizedHeader);
255
357
  update.nextSyncCommitteeBranch = normalizeMerkleBranch(
256
358
  update.nextSyncCommitteeBranch,
257
- isForkPostElectra(targetFork) ? NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA : NEXT_SYNC_COMMITTEE_DEPTH
258
- );
259
- update.finalityBranch = normalizeMerkleBranch(
260
- update.finalityBranch,
261
- isForkPostElectra(targetFork) ? FINALIZED_ROOT_DEPTH_ELECTRA : FINALIZED_ROOT_DEPTH
359
+ nextSyncCommitteeGindexAtFork(targetFork)
262
360
  );
361
+ update.finalityBranch = normalizeMerkleBranch(update.finalityBranch, finalizedRootGindexAtFork(targetFork));
263
362
 
264
363
  return update;
265
364
  }
@@ -273,7 +372,7 @@ export function upgradeLightClientFinalityUpdate(
273
372
  finalityUpdate.finalizedHeader = upgradeLightClientHeader(config, targetFork, finalityUpdate.finalizedHeader);
274
373
  finalityUpdate.finalityBranch = normalizeMerkleBranch(
275
374
  finalityUpdate.finalityBranch,
276
- isForkPostElectra(targetFork) ? FINALIZED_ROOT_DEPTH_ELECTRA : FINALIZED_ROOT_DEPTH
375
+ finalizedRootGindexAtFork(targetFork)
277
376
  );
278
377
 
279
378
  return finalityUpdate;
@@ -315,3 +414,72 @@ export function upgradeLightClientStore(
315
414
 
316
415
  return store;
317
416
  }
417
+
418
+ function isGloasLightClientHeader(header: LightClientHeader): header is LightClientHeader<ForkName.gloas> {
419
+ return (header as LightClientHeader<ForkName.gloas>).executionBlockHash !== undefined;
420
+ }
421
+
422
+ function upgradeLightClientHeaderToGloas(
423
+ config: ChainForkConfig,
424
+ pre: LightClientHeader<ForkName.electra>
425
+ ): LightClientHeader<ForkName.gloas> {
426
+ if (ssz.electra.LightClientHeader.equals(pre, ssz.electra.LightClientHeader.defaultValue())) {
427
+ return ssz.gloas.LightClientHeader.defaultValue();
428
+ }
429
+
430
+ const epoch = computeEpochAtSlot(pre.beacon.slot);
431
+
432
+ if (epoch >= config.DENEB_FORK_EPOCH) {
433
+ const blockHashGindex = ssz.deneb.ExecutionPayloadHeader.getPathInfo(["blockHash"]).gindex;
434
+ const executionBranch = new Tree(ssz.deneb.ExecutionPayloadHeader.toView(pre.execution).node).getSingleProof(
435
+ blockHashGindex
436
+ );
437
+
438
+ return {
439
+ beacon: pre.beacon,
440
+ executionBlockHash: pre.execution.blockHash,
441
+ executionBranch: normalizeMerkleBranch(
442
+ [...executionBranch, ...pre.executionBranch],
443
+ EXECUTION_BLOCK_HASH_GINDEX_GLOAS
444
+ ),
445
+ };
446
+ }
447
+
448
+ if (epoch >= config.CAPELLA_FORK_EPOCH) {
449
+ const executionHeader = {
450
+ parentHash: pre.execution.parentHash,
451
+ feeRecipient: pre.execution.feeRecipient,
452
+ stateRoot: pre.execution.stateRoot,
453
+ receiptsRoot: pre.execution.receiptsRoot,
454
+ logsBloom: pre.execution.logsBloom,
455
+ prevRandao: pre.execution.prevRandao,
456
+ blockNumber: pre.execution.blockNumber,
457
+ gasLimit: pre.execution.gasLimit,
458
+ gasUsed: pre.execution.gasUsed,
459
+ timestamp: pre.execution.timestamp,
460
+ extraData: pre.execution.extraData,
461
+ baseFeePerGas: pre.execution.baseFeePerGas,
462
+ blockHash: pre.execution.blockHash,
463
+ transactionsRoot: pre.execution.transactionsRoot,
464
+ withdrawalsRoot: pre.execution.withdrawalsRoot,
465
+ };
466
+ const blockHashGindex = ssz.capella.ExecutionPayloadHeader.getPathInfo(["blockHash"]).gindex;
467
+ const executionBranch = new Tree(ssz.capella.ExecutionPayloadHeader.toView(executionHeader).node).getSingleProof(
468
+ blockHashGindex
469
+ );
470
+
471
+ return {
472
+ beacon: pre.beacon,
473
+ executionBlockHash: executionHeader.blockHash,
474
+ executionBranch: normalizeMerkleBranch(
475
+ [...executionBranch, ...pre.executionBranch],
476
+ EXECUTION_BLOCK_HASH_GINDEX_GLOAS
477
+ ),
478
+ };
479
+ }
480
+
481
+ return {
482
+ ...ssz.gloas.LightClientHeader.defaultValue(),
483
+ beacon: pre.beacon,
484
+ };
485
+ }
@@ -1,13 +1,7 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
- import {isForkPostElectra} from "@lodestar/params";
3
2
  import {LightClientBootstrap, Root, ssz} from "@lodestar/types";
4
3
  import {byteArrayEquals, toHex} from "@lodestar/utils";
5
- import {isValidLightClientHeader, isValidMerkleBranch} from "./utils.js";
6
-
7
- const CURRENT_SYNC_COMMITTEE_INDEX = 22;
8
- const CURRENT_SYNC_COMMITTEE_DEPTH = 5;
9
- const CURRENT_SYNC_COMMITTEE_INDEX_ELECTRA = 22;
10
- const CURRENT_SYNC_COMMITTEE_DEPTH_ELECTRA = 6;
4
+ import {currentSyncCommitteeGindexAtFork, isValidLightClientHeader, isValidNormalizedMerkleBranch} from "./utils.js";
11
5
 
12
6
  export function validateLightClientBootstrap(
13
7
  config: ChainForkConfig,
@@ -26,11 +20,10 @@ export function validateLightClientBootstrap(
26
20
  }
27
21
 
28
22
  if (
29
- !isValidMerkleBranch(
23
+ !isValidNormalizedMerkleBranch(
30
24
  ssz.altair.SyncCommittee.hashTreeRoot(bootstrap.currentSyncCommittee),
31
25
  bootstrap.currentSyncCommitteeBranch,
32
- isForkPostElectra(fork) ? CURRENT_SYNC_COMMITTEE_DEPTH_ELECTRA : CURRENT_SYNC_COMMITTEE_DEPTH,
33
- isForkPostElectra(fork) ? CURRENT_SYNC_COMMITTEE_INDEX_ELECTRA : CURRENT_SYNC_COMMITTEE_INDEX,
26
+ currentSyncCommitteeGindexAtFork(fork),
34
27
  bootstrap.header.beacon.stateRoot
35
28
  )
36
29
  ) {
@@ -1,29 +1,19 @@
1
1
  import {PublicKey, Signature, fastAggregateVerify} from "@chainsafe/blst";
2
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";
3
+ import {DOMAIN_SYNC_COMMITTEE, GENESIS_SLOT, MIN_SYNC_COMMITTEE_PARTICIPANTS} from "@lodestar/params";
4
+ import {LightClientUpdate, Root, ssz} from "@lodestar/types";
17
5
  import type {ILightClientStore, SyncCommitteeFast} from "./store.js";
18
6
  import {
19
7
  ZERO_HASH,
8
+ finalizedRootGindexAtFork,
20
9
  getParticipantPubkeys,
21
10
  isFinalityUpdate,
22
11
  isSyncCommitteeUpdate,
23
12
  isValidLightClientHeader,
24
- isValidMerkleBranch,
13
+ isValidNormalizedMerkleBranch,
25
14
  isZeroedHeader,
26
15
  isZeroedSyncCommittee,
16
+ nextSyncCommitteeGindexAtFork,
27
17
  sumBits,
28
18
  } from "./utils.js";
29
19
 
@@ -33,6 +23,8 @@ export function validateLightClientUpdate(
33
23
  update: LightClientUpdate,
34
24
  syncCommittee: SyncCommitteeFast
35
25
  ): void {
26
+ const attestedFork = config.getForkName(update.attestedHeader.beacon.slot);
27
+
36
28
  // Verify sync committee has sufficient participants
37
29
  if (sumBits(update.syncAggregate.syncCommitteeBits) < MIN_SYNC_COMMITTEE_PARTICIPANTS) {
38
30
  throw Error("Sync committee has not sufficient participants");
@@ -78,11 +70,10 @@ export function validateLightClientUpdate(
78
70
  }
79
71
 
80
72
  if (
81
- !isValidMerkleBranch(
73
+ !isValidNormalizedMerkleBranch(
82
74
  finalizedRoot,
83
75
  update.finalityBranch,
84
- isElectraLightClientUpdate(update) ? FINALIZED_ROOT_DEPTH_ELECTRA : FINALIZED_ROOT_DEPTH,
85
- isElectraLightClientUpdate(update) ? FINALIZED_ROOT_INDEX_ELECTRA : FINALIZED_ROOT_INDEX,
76
+ finalizedRootGindexAtFork(attestedFork),
86
77
  update.attestedHeader.beacon.stateRoot
87
78
  )
88
79
  ) {
@@ -98,11 +89,10 @@ export function validateLightClientUpdate(
98
89
  }
99
90
  } else {
100
91
  if (
101
- !isValidMerkleBranch(
92
+ !isValidNormalizedMerkleBranch(
102
93
  ssz.altair.SyncCommittee.hashTreeRoot(update.nextSyncCommittee),
103
94
  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,
95
+ nextSyncCommitteeGindexAtFork(attestedFork),
106
96
  update.attestedHeader.beacon.stateRoot
107
97
  )
108
98
  ) {
@@ -26,8 +26,10 @@
26
26
  * ```
27
27
  */
28
28
  export type SyncCommitteeWitness = {
29
- /** Vector[Bytes32, 4] or Vector[Bytes32, 5] depending on the fork */
29
+ /** Shared witness for pre-Gloas forks where current and next sync committees are siblings. */
30
30
  witness: Uint8Array[];
31
31
  currentSyncCommitteeRoot: Uint8Array;
32
32
  nextSyncCommitteeRoot: Uint8Array;
33
+ currentSyncCommitteeBranch?: Uint8Array[];
34
+ nextSyncCommitteeBranch?: Uint8Array[];
33
35
  };
package/src/metrics.ts CHANGED
@@ -159,11 +159,9 @@ export function onPostStateMetrics(postState: CachedBeaconStateAllForks, metrics
159
159
  // This cache is populated during epoch transition, and should be preserved for performance.
160
160
  // If the cache is missing too often, means that our clone strategy is not working well.
161
161
  function isValidatorsNodesPopulated(state: CachedBeaconStateAllForks): boolean {
162
- // biome-ignore lint/complexity/useLiteralKeys: It is a private attribute
163
- return state.validators["nodesPopulated"] === true;
162
+ return (state.validators as unknown as {nodesPopulated?: boolean}).nodesPopulated === true;
164
163
  }
165
164
 
166
165
  function isBalancesNodesPopulated(state: CachedBeaconStateAllForks): boolean {
167
- // biome-ignore lint/complexity/useLiteralKeys: It is a private attribute
168
- return state.balances["nodesPopulated"] === true;
166
+ return (state.balances as unknown as {nodesPopulated?: boolean}).nodesPopulated === true;
169
167
  }
@@ -131,7 +131,7 @@ function translateParticipation(
131
131
 
132
132
  for (const attestation of pendingAttesations.getAllReadonly()) {
133
133
  const data = attestation.data;
134
- const attestationFlags = getAttestationParticipationStatus(
134
+ const {flags: attestationFlags} = getAttestationParticipationStatus(
135
135
  ForkSeq.altair,
136
136
  data,
137
137
  attestation.inclusionDelay,
@@ -1,3 +1,15 @@
1
+ import {getNodesAtDepth} from "@chainsafe/persistent-merkle-tree";
2
+ import {
3
+ BasicType,
4
+ CompositeType,
5
+ CompositeView,
6
+ CompositeViewDU,
7
+ ListBasicTreeViewDU,
8
+ ListCompositeTreeViewDU,
9
+ ProgressiveListBasicType,
10
+ ProgressiveListCompositeType,
11
+ ValueOf,
12
+ } from "@chainsafe/ssz";
1
13
  import {PAYLOAD_BUILDER_VERSION, SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
2
14
  import {ssz} from "@lodestar/types";
3
15
  import {toPubkeyHex} from "@lodestar/utils";
@@ -7,6 +19,7 @@ import {CachedBeaconStateFulu, CachedBeaconStateGloas} from "../types.js";
7
19
  import {addBuilderToRegistry, initializePtcWindow, isBuilderWithdrawalCredential} from "../util/gloas.js";
8
20
  import {isValidatorKnown} from "../util/index.js";
9
21
  import {PendingDepositsLookup} from "../util/pendingDepositsLookup.js";
22
+ import {progressiveListRootNode} from "../util/ssz.js";
10
23
 
11
24
  /**
12
25
  * Upgrade a state from Fulu to Gloas.
@@ -34,17 +47,26 @@ export function upgradeStateToGloas(stateFulu: CachedBeaconStateFulu): CachedBea
34
47
  stateGloasView.eth1Data = stateGloasCloned.eth1Data;
35
48
  stateGloasView.eth1DataVotes = stateGloasCloned.eth1DataVotes;
36
49
  stateGloasView.eth1DepositIndex = stateGloasCloned.eth1DepositIndex;
37
- stateGloasView.validators = stateGloasCloned.validators;
38
- stateGloasView.balances = stateGloasCloned.balances;
50
+ stateGloasView.validators = migrateCompositeListToGloas(stateGloasCloned.validators, ssz.gloas.Validators);
51
+ stateGloasView.balances = migrateBasicListToGloas(stateGloasCloned.balances, ssz.gloas.Balances);
39
52
  stateGloasView.randaoMixes = stateGloasCloned.randaoMixes;
40
53
  stateGloasView.slashings = stateGloasCloned.slashings;
41
- stateGloasView.previousEpochParticipation = stateGloasCloned.previousEpochParticipation;
42
- stateGloasView.currentEpochParticipation = stateGloasCloned.currentEpochParticipation;
54
+ stateGloasView.previousEpochParticipation = migrateBasicListToGloas(
55
+ stateGloasCloned.previousEpochParticipation,
56
+ ssz.gloas.EpochParticipation
57
+ );
58
+ stateGloasView.currentEpochParticipation = migrateBasicListToGloas(
59
+ stateGloasCloned.currentEpochParticipation,
60
+ ssz.gloas.EpochParticipation
61
+ );
43
62
  stateGloasView.justificationBits = stateGloasCloned.justificationBits;
44
63
  stateGloasView.previousJustifiedCheckpoint = stateGloasCloned.previousJustifiedCheckpoint;
45
64
  stateGloasView.currentJustifiedCheckpoint = stateGloasCloned.currentJustifiedCheckpoint;
46
65
  stateGloasView.finalizedCheckpoint = stateGloasCloned.finalizedCheckpoint;
47
- stateGloasView.inactivityScores = stateGloasCloned.inactivityScores;
66
+ stateGloasView.inactivityScores = migrateBasicListToGloas(
67
+ stateGloasCloned.inactivityScores,
68
+ ssz.gloas.InactivityScores
69
+ );
48
70
  stateGloasView.currentSyncCommittee = stateGloasCloned.currentSyncCommittee;
49
71
  stateGloasView.nextSyncCommittee = stateGloasCloned.nextSyncCommittee;
50
72
  stateGloasView.latestExecutionPayloadBid.blockHash = stateFulu.latestExecutionPayloadHeader.blockHash;
@@ -61,9 +83,18 @@ export function upgradeStateToGloas(stateFulu: CachedBeaconStateFulu): CachedBea
61
83
  stateGloasView.earliestExitEpoch = stateGloasCloned.earliestExitEpoch;
62
84
  stateGloasView.consolidationBalanceToConsume = stateGloasCloned.consolidationBalanceToConsume;
63
85
  stateGloasView.earliestConsolidationEpoch = stateGloasCloned.earliestConsolidationEpoch;
64
- stateGloasView.pendingDeposits = stateGloasCloned.pendingDeposits;
65
- stateGloasView.pendingPartialWithdrawals = stateGloasCloned.pendingPartialWithdrawals;
66
- stateGloasView.pendingConsolidations = stateGloasCloned.pendingConsolidations;
86
+ stateGloasView.pendingDeposits = migrateCompositeListToGloas(
87
+ stateGloasCloned.pendingDeposits,
88
+ ssz.gloas.PendingDeposits
89
+ );
90
+ stateGloasView.pendingPartialWithdrawals = migrateCompositeListToGloas(
91
+ stateGloasCloned.pendingPartialWithdrawals,
92
+ ssz.gloas.PendingPartialWithdrawals
93
+ );
94
+ stateGloasView.pendingConsolidations = migrateCompositeListToGloas(
95
+ stateGloasCloned.pendingConsolidations,
96
+ ssz.gloas.PendingConsolidations
97
+ );
67
98
  stateGloasView.proposerLookahead = stateGloasCloned.proposerLookahead;
68
99
  stateGloasView.ptcWindow = ssz.gloas.PtcWindow.toViewDU(initializePtcWindow(stateFulu));
69
100
 
@@ -85,6 +116,50 @@ export function upgradeStateToGloas(stateFulu: CachedBeaconStateFulu): CachedBea
85
116
  return stateGloas;
86
117
  }
87
118
 
119
+ /**
120
+ * Migrate a composite list from fulu to its gloas progressive-list equivalent by reusing the fulu
121
+ * list's element nodes.
122
+ *
123
+ * Works whenever the element type is identical across the fork (e.g. validators use ValidatorNodeStruct,
124
+ * the pending* queues use the same electra element types). Each element's cached subtree root is then
125
+ * valid under gloas, so only the progressive list superstructure is rebuilt and a subsequent
126
+ * hashTreeRoot() skips re-hashing every element — the dominant cost for large lists like validators.
127
+ * Much cheaper than `gloasType.toViewDU(fuluList.getAllReadonlyValues())`, which decodes every element
128
+ * to a value and forces a full re-hash.
129
+ *
130
+ * The chunk nodes of a composite list ARE the element root nodes, so they are extracted directly
131
+ * with getNodesAtDepth instead of allocating a temporary ViewDU wrapper per element (getAllReadonly).
132
+ * Requires the fulu view to be committed (done at the top of upgradeStateToGloas).
133
+ */
134
+ function migrateCompositeListToGloas<
135
+ ElementType extends CompositeType<ValueOf<ElementType>, CompositeView<ElementType>, CompositeViewDU<ElementType>>,
136
+ >(fuluList: ListCompositeTreeViewDU<ElementType>, gloasType: ProgressiveListCompositeType<ElementType>) {
137
+ const {length, type} = fuluList;
138
+ const elementNodes = getNodesAtDepth(fuluList.node.left, type.chunkDepth, 0, length);
139
+ return gloasType.getViewDU(progressiveListRootNode(elementNodes, length));
140
+ }
141
+
142
+ /**
143
+ * Migrate a basic list from fulu to its gloas progressive-list equivalent by reusing the fulu
144
+ * list's packed chunk leaf nodes.
145
+ *
146
+ * Packed leaf chunks are bit-identical between List[T, N] and ProgressiveList[T] (same 32-byte
147
+ * LE packing, zero-padded final chunk); only the superstructure above the leaves differs. Reusing
148
+ * the leaves avoids materializing the value array (getAll), re-serializing it, and allocating
149
+ * fresh LeafNodes — the gloas tree shares the leaf nodes with the fulu tree.
150
+ * Requires the fulu view to be committed (done at the top of upgradeStateToGloas).
151
+ */
152
+ function migrateBasicListToGloas<ElementType extends BasicType<unknown>>(
153
+ fuluList: ListBasicTreeViewDU<ElementType>,
154
+ gloasType: ProgressiveListBasicType<ElementType>
155
+ ) {
156
+ const {length, type} = fuluList;
157
+ const chunkCount = Math.ceil(length / type.itemsPerChunk);
158
+ // List root = BranchNode(chunksNode, lengthNode) → chunks tree is the left child
159
+ const chunkLeafNodes = getNodesAtDepth(fuluList.node.left, type.chunkDepth, 0, chunkCount);
160
+ return gloasType.getViewDU(progressiveListRootNode(chunkLeafNodes, length));
161
+ }
162
+
88
163
  /**
89
164
  * Applies any pending deposits for builders to onboard builders during the fork transition
90
165
  * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.8/specs/gloas/fork.md#new-onboard_builders_from_pending_deposits
@@ -94,7 +169,7 @@ function onboardBuildersFromPendingDeposits(state: CachedBeaconStateGloas): void
94
169
  // at the fork, so every builder pubkey here is one added in an earlier iteration.
95
170
  const builderPubkeys = new Set<string>();
96
171
 
97
- const pendingDeposits = ssz.electra.PendingDeposits.defaultViewDU();
172
+ const pendingDeposits = ssz.gloas.PendingDeposits.defaultViewDU();
98
173
  const pendingDepositsLookup = PendingDepositsLookup.buildEmpty();
99
174
 
100
175
  for (let i = 0; i < state.pendingDeposits.length; i++) {
@@ -515,14 +515,6 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
515
515
  return this.cachedState.epochCtx.getBeaconProposer(slot);
516
516
  }
517
517
 
518
- getBeaconProposerOrNull(slot: Slot): ValidatorIndex | null {
519
- try {
520
- return this.cachedState.epochCtx.getBeaconProposer(slot);
521
- } catch {
522
- return null;
523
- }
524
- }
525
-
526
518
  computeAnchorCheckpoint(): {checkpoint: phase0.Checkpoint; blockHeader: phase0.BeaconBlockHeader} {
527
519
  return computeAnchorCheckpoint(this.config, this.cachedState);
528
520
  }
@@ -92,7 +92,6 @@ export interface IBeaconStateView {
92
92
  currentProposers: ValidatorIndex[];
93
93
  nextProposers: ValidatorIndex[];
94
94
  getBeaconProposer(slot: Slot): ValidatorIndex;
95
- getBeaconProposerOrNull(slot: Slot): ValidatorIndex | null;
96
95
 
97
96
  // Validators and balances
98
97
  effectiveBalanceIncrements: EffectiveBalanceIncrements;
@@ -128,8 +128,6 @@ export class NativeBeaconStateView implements IBeaconStateViewLatestFork {
128
128
  private readonly _getBeaconCommitteeCountPerSlot = new Map<Epoch, number>();
129
129
  private readonly _getShufflingDecisionRoot = new Map<Epoch, RootHex>();
130
130
  private readonly _getBeaconProposer = new Map<Slot, ValidatorIndex>();
131
- // getBeaconProposerOrNull can return null, so use .has() to distinguish "not cached" from "cached null"
132
- private readonly _getBeaconProposerOrNull = new Map<Slot, ValidatorIndex | null>();
133
131
  private readonly _getValidator = new Map<ValidatorIndex, phase0.Validator>();
134
132
  private readonly _getBalance = new Map<number, number>();
135
133
  private readonly _getIndexedSyncCommitteeAtEpoch = new Map<Epoch, SyncCommitteeCache>();
@@ -411,14 +409,6 @@ export class NativeBeaconStateView implements IBeaconStateViewLatestFork {
411
409
  return cached;
412
410
  }
413
411
 
414
- getBeaconProposerOrNull(slot: Slot): ValidatorIndex | null {
415
- if (!this._getBeaconProposerOrNull.has(slot)) {
416
- this._getBeaconProposerOrNull.set(slot, this.binding.getBeaconProposerOrNull(slot));
417
- }
418
- // biome-ignore lint/style/noNonNullAssertion: has() check guarantees a value
419
- return this._getBeaconProposerOrNull.get(slot)!;
420
- }
421
-
422
412
  // Validators and balances
423
413
 
424
414
  get effectiveBalanceIncrements(): EffectiveBalanceIncrements {
@@ -9,6 +9,7 @@ import {
9
9
  bellatrix,
10
10
  capella,
11
11
  deneb,
12
+ gloas,
12
13
  isBlindedBeaconBlockBody,
13
14
  isExecutionPayload,
14
15
  ssz,
@@ -127,7 +128,10 @@ export function isCapellaPayloadHeader(
127
128
  }
128
129
 
129
130
  export function executionPayloadToPayloadHeader(fork: ForkSeq, payload: ExecutionPayload): ExecutionPayloadHeader {
130
- const transactionsRoot = ssz.bellatrix.Transactions.hashTreeRoot(payload.transactions);
131
+ const transactionsRoot =
132
+ fork >= ForkSeq.gloas
133
+ ? ssz.gloas.Transactions.hashTreeRoot((payload as gloas.ExecutionPayload).transactions)
134
+ : ssz.bellatrix.Transactions.hashTreeRoot(payload.transactions);
131
135
 
132
136
  const bellatrixPayloadFields: ExecutionPayloadHeader = {
133
137
  parentHash: payload.parentHash,
@@ -147,9 +151,10 @@ export function executionPayloadToPayloadHeader(fork: ForkSeq, payload: Executio
147
151
  };
148
152
 
149
153
  if (fork >= ForkSeq.capella) {
150
- (bellatrixPayloadFields as capella.ExecutionPayloadHeader).withdrawalsRoot = ssz.capella.Withdrawals.hashTreeRoot(
151
- (payload as capella.ExecutionPayload).withdrawals
152
- );
154
+ (bellatrixPayloadFields as capella.ExecutionPayloadHeader).withdrawalsRoot =
155
+ fork >= ForkSeq.gloas
156
+ ? ssz.gloas.Withdrawals.hashTreeRoot((payload as gloas.ExecutionPayload).withdrawals)
157
+ : ssz.capella.Withdrawals.hashTreeRoot((payload as capella.ExecutionPayload).withdrawals);
153
158
  }
154
159
 
155
160
  if (fork >= ForkSeq.deneb) {