@lodestar/state-transition 1.45.0-dev.0cff4e6916 → 1.45.0-dev.17a48050e6

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 (83) hide show
  1. package/lib/block/processOperations.d.ts.map +1 -1
  2. package/lib/block/processOperations.js +17 -1
  3. package/lib/block/processOperations.js.map +1 -1
  4. package/lib/block/processParentExecutionPayload.d.ts.map +1 -1
  5. package/lib/block/processParentExecutionPayload.js +15 -1
  6. package/lib/block/processParentExecutionPayload.js.map +1 -1
  7. package/lib/block/processWithdrawals.js +1 -1
  8. package/lib/block/processWithdrawals.js.map +1 -1
  9. package/lib/cache/stateCache.d.ts.map +1 -1
  10. package/lib/cache/stateCache.js +2 -4
  11. package/lib/cache/stateCache.js.map +1 -1
  12. package/lib/epoch/processParticipationFlagUpdates.d.ts +2 -2
  13. package/lib/epoch/processParticipationFlagUpdates.d.ts.map +1 -1
  14. package/lib/epoch/processParticipationFlagUpdates.js +13 -0
  15. package/lib/epoch/processParticipationFlagUpdates.js.map +1 -1
  16. package/lib/epoch/processRewardsAndPenalties.d.ts.map +1 -1
  17. package/lib/epoch/processRewardsAndPenalties.js +1 -2
  18. package/lib/epoch/processRewardsAndPenalties.js.map +1 -1
  19. package/lib/lightClient/proofs.d.ts.map +1 -1
  20. package/lib/lightClient/proofs.js +28 -2
  21. package/lib/lightClient/proofs.js.map +1 -1
  22. package/lib/lightClient/spec/index.d.ts.map +1 -1
  23. package/lib/lightClient/spec/index.js +7 -4
  24. package/lib/lightClient/spec/index.js.map +1 -1
  25. package/lib/lightClient/spec/utils.d.ts +7 -1
  26. package/lib/lightClient/spec/utils.d.ts.map +1 -1
  27. package/lib/lightClient/spec/utils.js +123 -14
  28. package/lib/lightClient/spec/utils.js.map +1 -1
  29. package/lib/lightClient/spec/validateLightClientBootstrap.d.ts.map +1 -1
  30. package/lib/lightClient/spec/validateLightClientBootstrap.js +2 -7
  31. package/lib/lightClient/spec/validateLightClientBootstrap.js.map +1 -1
  32. package/lib/lightClient/spec/validateLightClientUpdate.d.ts.map +1 -1
  33. package/lib/lightClient/spec/validateLightClientUpdate.js +6 -5
  34. package/lib/lightClient/spec/validateLightClientUpdate.js.map +1 -1
  35. package/lib/lightClient/types.d.ts +3 -1
  36. package/lib/lightClient/types.d.ts.map +1 -1
  37. package/lib/metrics.js +2 -4
  38. package/lib/metrics.js.map +1 -1
  39. package/lib/slot/upgradeStateToGloas.d.ts.map +1 -1
  40. package/lib/slot/upgradeStateToGloas.js +48 -9
  41. package/lib/slot/upgradeStateToGloas.js.map +1 -1
  42. package/lib/stateView/beaconStateView.d.ts +1 -1
  43. package/lib/stateView/beaconStateView.d.ts.map +1 -1
  44. package/lib/stateView/beaconStateView.js +6 -8
  45. package/lib/stateView/beaconStateView.js.map +1 -1
  46. package/lib/stateView/interface.d.ts +1 -1
  47. package/lib/stateView/interface.d.ts.map +1 -1
  48. package/lib/stateView/nativeBeaconStateView.d.ts +2 -2
  49. package/lib/stateView/nativeBeaconStateView.d.ts.map +1 -1
  50. package/lib/stateView/nativeBeaconStateView.js +7 -9
  51. package/lib/stateView/nativeBeaconStateView.js.map +1 -1
  52. package/lib/util/execution.d.ts.map +1 -1
  53. package/lib/util/execution.js +7 -2
  54. package/lib/util/execution.js.map +1 -1
  55. package/lib/util/index.d.ts +1 -0
  56. package/lib/util/index.d.ts.map +1 -1
  57. package/lib/util/index.js +1 -0
  58. package/lib/util/index.js.map +1 -1
  59. package/lib/util/ssz.d.ts +9 -0
  60. package/lib/util/ssz.d.ts.map +1 -0
  61. package/lib/util/ssz.js +39 -0
  62. package/lib/util/ssz.js.map +1 -0
  63. package/package.json +10 -10
  64. package/src/block/processOperations.ts +28 -1
  65. package/src/block/processParentExecutionPayload.ts +26 -1
  66. package/src/block/processWithdrawals.ts +1 -1
  67. package/src/cache/stateCache.ts +2 -4
  68. package/src/epoch/processParticipationFlagUpdates.ts +20 -2
  69. package/src/epoch/processRewardsAndPenalties.ts +1 -2
  70. package/src/lightClient/proofs.ts +36 -1
  71. package/src/lightClient/spec/index.ts +8 -4
  72. package/src/lightClient/spec/utils.ts +193 -25
  73. package/src/lightClient/spec/validateLightClientBootstrap.ts +3 -10
  74. package/src/lightClient/spec/validateLightClientUpdate.ts +11 -21
  75. package/src/lightClient/types.ts +3 -1
  76. package/src/metrics.ts +2 -4
  77. package/src/slot/upgradeStateToGloas.ts +84 -9
  78. package/src/stateView/beaconStateView.ts +8 -8
  79. package/src/stateView/interface.ts +1 -1
  80. package/src/stateView/nativeBeaconStateView.ts +8 -10
  81. package/src/util/execution.ts +9 -4
  82. package/src/util/index.ts +1 -0
  83. package/src/util/ssz.ts +42 -0
@@ -1,4 +1,12 @@
1
- import {ForkSeq} from "@lodestar/params";
1
+ import {
2
+ ForkSeq,
3
+ MAX_ATTESTATIONS_ELECTRA,
4
+ MAX_ATTESTER_SLASHINGS_ELECTRA,
5
+ MAX_BLS_TO_EXECUTION_CHANGES,
6
+ MAX_PAYLOAD_ATTESTATIONS,
7
+ MAX_PROPOSER_SLASHINGS,
8
+ MAX_VOLUNTARY_EXITS,
9
+ } from "@lodestar/params";
2
10
  import {BeaconBlockBody, capella, electra, gloas} from "@lodestar/types";
3
11
  import {BeaconStateTransitionMetrics} from "../metrics.js";
4
12
  import {
@@ -39,6 +47,10 @@ export function processOperations(
39
47
  opts: ProcessBlockOpts = {verifySignatures: true},
40
48
  metrics?: BeaconStateTransitionMetrics | null
41
49
  ): void {
50
+ if (fork >= ForkSeq.gloas) {
51
+ assertGloasOperationLimits(body as gloas.BeaconBlockBody);
52
+ }
53
+
42
54
  // verify that outstanding deposits are processed up to the maximum number of deposits.
43
55
  // From Fulu the eth1 bridge deposit mechanism was removed, so blocks must not contain any deposits.
44
56
  const maxDeposits = fork >= ForkSeq.fulu ? 0 : getEth1DepositCount(state);
@@ -94,3 +106,18 @@ export function processOperations(
94
106
  }
95
107
  }
96
108
  }
109
+
110
+ function assertGloasOperationLimits(body: gloas.BeaconBlockBody): void {
111
+ assertMaxLength("proposerSlashings", body.proposerSlashings.length, MAX_PROPOSER_SLASHINGS);
112
+ assertMaxLength("attesterSlashings", body.attesterSlashings.length, MAX_ATTESTER_SLASHINGS_ELECTRA);
113
+ assertMaxLength("attestations", body.attestations.length, MAX_ATTESTATIONS_ELECTRA);
114
+ assertMaxLength("voluntaryExits", body.voluntaryExits.length, MAX_VOLUNTARY_EXITS);
115
+ assertMaxLength("blsToExecutionChanges", body.blsToExecutionChanges.length, MAX_BLS_TO_EXECUTION_CHANGES);
116
+ assertMaxLength("payloadAttestations", body.payloadAttestations.length, MAX_PAYLOAD_ATTESTATIONS);
117
+ }
118
+
119
+ function assertMaxLength(name: string, length: number, limit: number): void {
120
+ if (length > limit) {
121
+ throw new Error(`Block contains too many ${name}: count=${length} limit=${limit}`);
122
+ }
123
+ }
@@ -1,4 +1,12 @@
1
- import {ForkPostGloas, SLOTS_PER_EPOCH, SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
1
+ import {
2
+ ForkPostGloas,
3
+ MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD,
4
+ MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD,
5
+ MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD,
6
+ MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD,
7
+ SLOTS_PER_EPOCH,
8
+ SLOTS_PER_HISTORICAL_ROOT,
9
+ } from "@lodestar/params";
2
10
  import {BeaconBlock, gloas, ssz} from "@lodestar/types";
3
11
  import {byteArrayEquals, toRootHex} from "@lodestar/utils";
4
12
  import {CachedBeaconStateGloas} from "../types.js";
@@ -47,6 +55,8 @@ export function processParentExecutionPayload(state: CachedBeaconStateGloas, blo
47
55
  * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.6/specs/gloas/beacon-chain.md#new-apply_parent_execution_payload
48
56
  */
49
57
  export function applyParentExecutionPayload(state: CachedBeaconStateGloas, requests: gloas.ExecutionRequests): void {
58
+ assertExecutionRequestsWithinLimits(requests);
59
+
50
60
  const fork = state.config.getForkSeq(state.slot);
51
61
  const parentBid = state.latestExecutionPayloadBid;
52
62
  const parentSlot = parentBid.slot;
@@ -116,6 +126,21 @@ function settleBuilderPayment(state: CachedBeaconStateGloas, paymentIndex: numbe
116
126
  state.builderPendingPayments.set(paymentIndex, ssz.gloas.BuilderPendingPayment.defaultViewDU());
117
127
  }
118
128
 
129
+ function assertExecutionRequestsWithinLimits(requests: gloas.ExecutionRequests): void {
130
+ assertMaxLength("withdrawals", requests.withdrawals.length, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD);
131
+ assertMaxLength("consolidations", requests.consolidations.length, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD);
132
+ // New in GLOAS:EIP8282
133
+ assertMaxLength("builderDeposits", requests.builderDeposits.length, MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD);
134
+ // New in GLOAS:EIP8282
135
+ assertMaxLength("builderExits", requests.builderExits.length, MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD);
136
+ }
137
+
138
+ function assertMaxLength(name: string, length: number, limit: number): void {
139
+ if (length > limit) {
140
+ throw new Error(`Too many parent execution request ${name} count=${length} limit=${limit}`);
141
+ }
142
+ }
143
+
119
144
  function assertEmptyExecutionRequests(requests: gloas.ExecutionRequests): void {
120
145
  if (
121
146
  requests.deposits.length !== 0 ||
@@ -99,7 +99,7 @@ export function processWithdrawals(
99
99
  const stateGloas = state as CachedBeaconStateGloas;
100
100
 
101
101
  // Store expected withdrawals for verification
102
- stateGloas.payloadExpectedWithdrawals = ssz.capella.Withdrawals.toViewDU(expectedWithdrawals);
102
+ stateGloas.payloadExpectedWithdrawals = ssz.gloas.Withdrawals.toViewDU(expectedWithdrawals);
103
103
 
104
104
  // Update builder pending withdrawals queue
105
105
  stateGloas.builderPendingWithdrawals = stateGloas.builderPendingWithdrawals.sliceFrom(
@@ -254,11 +254,9 @@ export function isCachedBeaconState<T extends BeaconStateAllForks>(
254
254
  // This cache is populated during epoch transition, and should be preserved for performance.
255
255
  // If the cache is missing too often, means that our clone strategy is not working well.
256
256
  export function isStateValidatorsNodesPopulated(state: CachedBeaconStateAllForks): boolean {
257
- // biome-ignore lint/complexity/useLiteralKeys: It is a private attribute
258
- return state.validators["nodesPopulated"] === true;
257
+ return (state.validators as unknown as {nodesPopulated?: boolean}).nodesPopulated === true;
259
258
  }
260
259
 
261
260
  export function isStateBalancesNodesPopulated(state: CachedBeaconStateAllForks): boolean {
262
- // biome-ignore lint/complexity/useLiteralKeys: It is a private attribute
263
- return state.balances["nodesPopulated"] === true;
261
+ return (state.balances as unknown as {nodesPopulated?: boolean}).nodesPopulated === true;
264
262
  }
@@ -1,6 +1,8 @@
1
1
  import {zeroNode} from "@chainsafe/persistent-merkle-tree";
2
2
  import {ssz} from "@lodestar/types";
3
- import {CachedBeaconStateAltair} from "../types.js";
3
+ import type {BeaconStateAltair, BeaconStateGloas} from "../types.js";
4
+ import {isGloasStateType} from "../util/execution.js";
5
+ import {zeroProgressiveListBasicRootNode} from "../util/ssz.js";
4
6
 
5
7
  /**
6
8
  * Updates `state.previousEpochParticipation` with precalculated epoch participation. Creates a new empty tree for
@@ -9,7 +11,12 @@ import {CachedBeaconStateAltair} from "../types.js";
9
11
  * PERF: Cost = 'proportional' $VALIDATOR_COUNT. Since it updates all of them at once, it will always recreate both
10
12
  * trees completely.
11
13
  */
12
- export function processParticipationFlagUpdates(state: CachedBeaconStateAltair): void {
14
+ export function processParticipationFlagUpdates(state: BeaconStateAltair | BeaconStateGloas): void {
15
+ if (isGloasStateType(state)) {
16
+ processParticipationFlagUpdatesGloas(state);
17
+ return;
18
+ }
19
+
13
20
  // Set view and tree from currentEpochParticipation to previousEpochParticipation
14
21
  state.previousEpochParticipation = state.currentEpochParticipation;
15
22
 
@@ -25,3 +32,14 @@ export function processParticipationFlagUpdates(state: CachedBeaconStateAltair):
25
32
 
26
33
  state.currentEpochParticipation = ssz.altair.EpochParticipation.getViewDU(currentEpochParticipationNode);
27
34
  }
35
+
36
+ function processParticipationFlagUpdatesGloas(state: BeaconStateGloas): void {
37
+ state.previousEpochParticipation = state.currentEpochParticipation;
38
+
39
+ // Same trick as the altair path above, adapted to the progressive-list tree shape: all chunks
40
+ // are zero so the chunks tree is a chain of pre-computed zeroNodes, built in O(log n) instead
41
+ // of re-merkleizing a validator-count-sized array every epoch.
42
+ state.currentEpochParticipation = ssz.gloas.EpochParticipation.getViewDU(
43
+ zeroProgressiveListBasicRootNode(ssz.gloas.EpochParticipation.itemsPerChunk, state.currentEpochParticipation.length)
44
+ );
45
+ }
@@ -1,5 +1,4 @@
1
1
  import {ForkSeq, GENESIS_EPOCH} from "@lodestar/params";
2
- import {ssz} from "@lodestar/types";
3
2
  import {
4
3
  CachedBeaconStateAllForks,
5
4
  CachedBeaconStateAltair,
@@ -39,7 +38,7 @@ export function processRewardsAndPenalties(
39
38
 
40
39
  // important: do not change state one balance at a time. Set them all at once, constructing the tree in one go
41
40
  // cache the balances array, too
42
- state.balances = ssz.phase0.Balances.toViewDU(balances);
41
+ state.balances = state.type.fields.balances.toViewDU(balances);
43
42
 
44
43
  // For processEffectiveBalanceUpdates() to prevent having to re-compute the balances array.
45
44
  // For validator metrics
@@ -1,11 +1,15 @@
1
1
  import {Tree} from "@chainsafe/persistent-merkle-tree";
2
2
  import {
3
3
  BLOCK_BODY_EXECUTION_PAYLOAD_GINDEX,
4
+ CURRENT_SYNC_COMMITTEE_GINDEX_GLOAS,
4
5
  FINALIZED_ROOT_GINDEX,
5
6
  FINALIZED_ROOT_GINDEX_ELECTRA,
7
+ FINALIZED_ROOT_GINDEX_GLOAS,
6
8
  ForkName,
7
9
  ForkPostBellatrix,
10
+ NEXT_SYNC_COMMITTEE_GINDEX_GLOAS,
8
11
  isForkPostElectra,
12
+ isForkPostGloas,
9
13
  } from "@lodestar/params";
10
14
  import {BeaconBlockBody, SSZTypesFor, ssz} from "@lodestar/types";
11
15
  import {BeaconStateAllForks, CachedBeaconStateAllForks} from "../types.js";
@@ -17,6 +21,24 @@ export function getSyncCommitteesWitness(fork: ForkName, state: BeaconStateAllFo
17
21
  let currentSyncCommitteeRoot: Uint8Array;
18
22
  let nextSyncCommitteeRoot: Uint8Array;
19
23
 
24
+ if (isForkPostGloas(fork)) {
25
+ const tree = new Tree(state.node);
26
+ const currentSyncCommitteeGindex = BigInt(CURRENT_SYNC_COMMITTEE_GINDEX_GLOAS);
27
+ const nextSyncCommitteeGindex = BigInt(NEXT_SYNC_COMMITTEE_GINDEX_GLOAS);
28
+
29
+ currentSyncCommitteeRoot = tree.getRoot(currentSyncCommitteeGindex);
30
+ nextSyncCommitteeRoot = tree.getRoot(nextSyncCommitteeGindex);
31
+ witness = [];
32
+
33
+ return {
34
+ witness,
35
+ currentSyncCommitteeRoot,
36
+ nextSyncCommitteeRoot,
37
+ currentSyncCommitteeBranch: tree.getSingleProof(currentSyncCommitteeGindex),
38
+ nextSyncCommitteeBranch: tree.getSingleProof(nextSyncCommitteeGindex),
39
+ };
40
+ }
41
+
20
42
  if (isForkPostElectra(fork)) {
21
43
  const n2 = n1.left;
22
44
  const n5 = n2.right;
@@ -60,17 +82,30 @@ export function getSyncCommitteesWitness(fork: ForkName, state: BeaconStateAllFo
60
82
  }
61
83
 
62
84
  export function getNextSyncCommitteeBranch(syncCommitteesWitness: SyncCommitteeWitness): Uint8Array[] {
85
+ if (syncCommitteesWitness.nextSyncCommitteeBranch) {
86
+ return syncCommitteesWitness.nextSyncCommitteeBranch;
87
+ }
88
+
63
89
  // Witness branch is sorted by descending gindex
64
90
  return [syncCommitteesWitness.currentSyncCommitteeRoot, ...syncCommitteesWitness.witness];
65
91
  }
66
92
 
67
93
  export function getCurrentSyncCommitteeBranch(syncCommitteesWitness: SyncCommitteeWitness): Uint8Array[] {
94
+ if (syncCommitteesWitness.currentSyncCommitteeBranch) {
95
+ return syncCommitteesWitness.currentSyncCommitteeBranch;
96
+ }
97
+
68
98
  // Witness branch is sorted by descending gindex
69
99
  return [syncCommitteesWitness.nextSyncCommitteeRoot, ...syncCommitteesWitness.witness];
70
100
  }
71
101
 
72
102
  export function getFinalizedRootProof(state: CachedBeaconStateAllForks): Uint8Array[] {
73
- const finalizedRootGindex = state.epochCtx.isPostElectra() ? FINALIZED_ROOT_GINDEX_ELECTRA : FINALIZED_ROOT_GINDEX;
103
+ const fork = state.config.getForkName(state.slot);
104
+ const finalizedRootGindex = isForkPostGloas(fork)
105
+ ? FINALIZED_ROOT_GINDEX_GLOAS
106
+ : state.epochCtx.isPostElectra()
107
+ ? FINALIZED_ROOT_GINDEX_ELECTRA
108
+ : FINALIZED_ROOT_GINDEX;
74
109
  return new Tree(state.node).getSingleProof(BigInt(finalizedRootGindex));
75
110
  }
76
111
 
@@ -14,7 +14,7 @@ import {
14
14
  processLightClientUpdate,
15
15
  } from "./processLightClientUpdate.js";
16
16
  import {type ILightClientStore, LightClientStore, type LightClientStoreEvents} from "./store.js";
17
- import {ZERO_HEADER, ZERO_SYNC_COMMITTEE, getZeroFinalityBranch, getZeroSyncCommitteeBranch} from "./utils.js";
17
+ import {ZERO_SYNC_COMMITTEE, getZeroFinalityBranch, getZeroSyncCommitteeBranch} from "./utils.js";
18
18
 
19
19
  export type {LightClientUpdateSummary} from "./isBetterUpdate.js";
20
20
  export {isBetterUpdate, toLightClientUpdateSummary} from "./isBetterUpdate.js";
@@ -76,12 +76,16 @@ export class LightclientSpec {
76
76
  }
77
77
 
78
78
  onOptimisticUpdate(currentSlot: Slot, optimisticUpdate: LightClientOptimisticUpdate): void {
79
+ const fork = this.config.getForkName(optimisticUpdate.signatureSlot);
80
+
79
81
  this.onUpdate(currentSlot, {
80
82
  attestedHeader: optimisticUpdate.attestedHeader,
81
83
  nextSyncCommittee: ZERO_SYNC_COMMITTEE,
82
- nextSyncCommitteeBranch: getZeroSyncCommitteeBranch(this.config.getForkName(optimisticUpdate.signatureSlot)),
83
- finalizedHeader: {beacon: ZERO_HEADER},
84
- finalityBranch: getZeroFinalityBranch(this.config.getForkName(optimisticUpdate.signatureSlot)),
84
+ nextSyncCommitteeBranch: getZeroSyncCommitteeBranch(fork),
85
+ finalizedHeader: this.config
86
+ .getPostAltairForkTypes(optimisticUpdate.signatureSlot)
87
+ .LightClientHeader.defaultValue(),
88
+ finalityBranch: getZeroFinalityBranch(fork),
85
89
  syncAggregate: optimisticUpdate.syncAggregate,
86
90
  signatureSlot: optimisticUpdate.signatureSlot,
87
91
  });
@@ -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
  };