@lodestar/state-transition 1.43.0 → 1.44.0-dev.055b83cb3d

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 (82) hide show
  1. package/lib/block/processDepositRequest.d.ts +3 -11
  2. package/lib/block/processDepositRequest.d.ts.map +1 -1
  3. package/lib/block/processDepositRequest.js +27 -35
  4. package/lib/block/processDepositRequest.js.map +1 -1
  5. package/lib/block/processParentExecutionPayload.d.ts.map +1 -1
  6. package/lib/block/processParentExecutionPayload.js +4 -3
  7. package/lib/block/processParentExecutionPayload.js.map +1 -1
  8. package/lib/cache/epochCache.d.ts.map +1 -1
  9. package/lib/cache/epochCache.js +10 -7
  10. package/lib/cache/epochCache.js.map +1 -1
  11. package/lib/lightClient/spec/index.d.ts +22 -0
  12. package/lib/lightClient/spec/index.d.ts.map +1 -0
  13. package/lib/lightClient/spec/index.js +58 -0
  14. package/lib/lightClient/spec/index.js.map +1 -0
  15. package/lib/lightClient/spec/isBetterUpdate.d.ts +23 -0
  16. package/lib/lightClient/spec/isBetterUpdate.d.ts.map +1 -0
  17. package/lib/lightClient/spec/isBetterUpdate.js +66 -0
  18. package/lib/lightClient/spec/isBetterUpdate.js.map +1 -0
  19. package/lib/lightClient/spec/processLightClientUpdate.d.ts +12 -0
  20. package/lib/lightClient/spec/processLightClientUpdate.d.ts.map +1 -0
  21. package/lib/lightClient/spec/processLightClientUpdate.js +80 -0
  22. package/lib/lightClient/spec/processLightClientUpdate.js.map +1 -0
  23. package/lib/lightClient/spec/store.d.ts +45 -0
  24. package/lib/lightClient/spec/store.d.ts.map +1 -0
  25. package/lib/lightClient/spec/store.js +56 -0
  26. package/lib/lightClient/spec/store.js.map +1 -0
  27. package/lib/lightClient/spec/utils.d.ts +47 -0
  28. package/lib/lightClient/spec/utils.d.ts.map +1 -0
  29. package/lib/lightClient/spec/utils.js +197 -0
  30. package/lib/lightClient/spec/utils.js.map +1 -0
  31. package/lib/lightClient/spec/validateLightClientBootstrap.d.ts +4 -0
  32. package/lib/lightClient/spec/validateLightClientBootstrap.d.ts.map +1 -0
  33. package/lib/lightClient/spec/validateLightClientBootstrap.js +22 -0
  34. package/lib/lightClient/spec/validateLightClientBootstrap.js.map +1 -0
  35. package/lib/lightClient/spec/validateLightClientUpdate.d.ts +5 -0
  36. package/lib/lightClient/spec/validateLightClientUpdate.d.ts.map +1 -0
  37. package/lib/lightClient/spec/validateLightClientUpdate.js +88 -0
  38. package/lib/lightClient/spec/validateLightClientUpdate.js.map +1 -0
  39. package/lib/slot/upgradeStateToGloas.d.ts.map +1 -1
  40. package/lib/slot/upgradeStateToGloas.js +35 -29
  41. package/lib/slot/upgradeStateToGloas.js.map +1 -1
  42. package/lib/stateView/beaconStateView.d.ts +9 -3
  43. package/lib/stateView/beaconStateView.d.ts.map +1 -1
  44. package/lib/stateView/beaconStateView.js +23 -4
  45. package/lib/stateView/beaconStateView.js.map +1 -1
  46. package/lib/stateView/interface.d.ts +2 -1
  47. package/lib/stateView/interface.d.ts.map +1 -1
  48. package/lib/stateView/interface.js.map +1 -1
  49. package/lib/util/gloas.d.ts +14 -0
  50. package/lib/util/gloas.d.ts.map +1 -1
  51. package/lib/util/gloas.js +24 -0
  52. package/lib/util/gloas.js.map +1 -1
  53. package/lib/util/index.d.ts +1 -0
  54. package/lib/util/index.d.ts.map +1 -1
  55. package/lib/util/index.js +1 -0
  56. package/lib/util/index.js.map +1 -1
  57. package/lib/util/pendingDepositsLookup.d.ts +40 -0
  58. package/lib/util/pendingDepositsLookup.d.ts.map +1 -0
  59. package/lib/util/pendingDepositsLookup.js +84 -0
  60. package/lib/util/pendingDepositsLookup.js.map +1 -0
  61. package/lib/util/shuffling.d.ts +6 -5
  62. package/lib/util/shuffling.d.ts.map +1 -1
  63. package/lib/util/shuffling.js +13 -15
  64. package/lib/util/shuffling.js.map +1 -1
  65. package/package.json +12 -7
  66. package/src/block/processDepositRequest.ts +29 -47
  67. package/src/block/processParentExecutionPayload.ts +4 -3
  68. package/src/cache/epochCache.ts +10 -7
  69. package/src/lightClient/spec/index.ts +101 -0
  70. package/src/lightClient/spec/isBetterUpdate.ts +94 -0
  71. package/src/lightClient/spec/processLightClientUpdate.ts +119 -0
  72. package/src/lightClient/spec/store.ts +106 -0
  73. package/src/lightClient/spec/utils.ts +317 -0
  74. package/src/lightClient/spec/validateLightClientBootstrap.ts +39 -0
  75. package/src/lightClient/spec/validateLightClientUpdate.ts +145 -0
  76. package/src/slot/upgradeStateToGloas.ts +43 -45
  77. package/src/stateView/beaconStateView.ts +23 -4
  78. package/src/stateView/interface.ts +2 -1
  79. package/src/util/gloas.ts +28 -0
  80. package/src/util/index.ts +1 -0
  81. package/src/util/pendingDepositsLookup.ts +105 -0
  82. package/src/util/shuffling.ts +17 -15
@@ -0,0 +1,119 @@
1
+ import {ChainForkConfig} from "@lodestar/config";
2
+ import {SYNC_COMMITTEE_SIZE} from "@lodestar/params";
3
+ import {LightClientUpdate, Slot, SyncPeriod} from "@lodestar/types";
4
+ import {pruneSetToMax} from "@lodestar/utils";
5
+ import {computeSyncPeriodAtSlot} from "../../util/epoch.js";
6
+ import {LightClientUpdateSummary, isBetterUpdate, toLightClientUpdateSummary} from "./isBetterUpdate.js";
7
+ import {type ILightClientStore, MAX_SYNC_PERIODS_CACHE, type SyncCommitteeFast} from "./store.js";
8
+ import {deserializeSyncCommittee, getSafetyThreshold, isSyncCommitteeUpdate, sumBits} from "./utils.js";
9
+ import {validateLightClientUpdate} from "./validateLightClientUpdate.js";
10
+
11
+ export interface ProcessUpdateOpts {
12
+ allowForcedUpdates?: boolean;
13
+ updateHeadersOnForcedUpdate?: boolean;
14
+ }
15
+
16
+ export function processLightClientUpdate(
17
+ config: ChainForkConfig,
18
+ store: ILightClientStore,
19
+ currentSlot: Slot,
20
+ opts: ProcessUpdateOpts,
21
+ update: LightClientUpdate
22
+ ): void {
23
+ if (update.signatureSlot > currentSlot) {
24
+ throw Error(`update slot ${update.signatureSlot} must not be in the future, current slot ${currentSlot}`);
25
+ }
26
+
27
+ const updateSignaturePeriod = computeSyncPeriodAtSlot(update.signatureSlot);
28
+ // TODO: Consider attempting to retrieve LightClientUpdate from transport if missing
29
+ // Note: store.getSyncCommitteeAtPeriod() may advance store
30
+ const syncCommittee = getSyncCommitteeAtPeriod(store, updateSignaturePeriod, opts);
31
+
32
+ validateLightClientUpdate(config, store, update, syncCommittee);
33
+
34
+ // Track the maximum number of active participants in the committee signatures
35
+ const syncCommitteeTrueBits = sumBits(update.syncAggregate.syncCommitteeBits);
36
+ store.setActiveParticipants(updateSignaturePeriod, syncCommitteeTrueBits);
37
+
38
+ // Update the optimistic header
39
+ if (
40
+ syncCommitteeTrueBits > getSafetyThreshold(store.getMaxActiveParticipants(updateSignaturePeriod)) &&
41
+ update.attestedHeader.beacon.slot > store.optimisticHeader.beacon.slot
42
+ ) {
43
+ store.optimisticHeader = update.attestedHeader;
44
+ }
45
+
46
+ // Update finalized header
47
+ if (
48
+ syncCommitteeTrueBits * 3 >= SYNC_COMMITTEE_SIZE * 2 &&
49
+ update.finalizedHeader.beacon.slot > store.finalizedHeader.beacon.slot
50
+ ) {
51
+ store.finalizedHeader = update.finalizedHeader;
52
+ if (store.finalizedHeader.beacon.slot > store.optimisticHeader.beacon.slot) {
53
+ store.optimisticHeader = store.finalizedHeader;
54
+ }
55
+ }
56
+
57
+ if (isSyncCommitteeUpdate(update)) {
58
+ // Update the best update in case we have to force-update to it if the timeout elapses
59
+ const bestValidUpdate = store.bestValidUpdates.get(updateSignaturePeriod);
60
+ const updateSummary = toLightClientUpdateSummary(update);
61
+ if (!bestValidUpdate || isBetterUpdate(updateSummary, bestValidUpdate.summary)) {
62
+ store.bestValidUpdates.set(updateSignaturePeriod, {update, summary: updateSummary});
63
+ pruneSetToMax(store.bestValidUpdates, MAX_SYNC_PERIODS_CACHE);
64
+ }
65
+
66
+ // Note: defer update next sync committee to a future getSyncCommitteeAtPeriod() call
67
+ }
68
+ }
69
+
70
+ export function getSyncCommitteeAtPeriod(
71
+ store: ILightClientStore,
72
+ period: SyncPeriod,
73
+ opts: ProcessUpdateOpts
74
+ ): SyncCommitteeFast {
75
+ const syncCommittee = store.syncCommittees.get(period);
76
+ if (syncCommittee) {
77
+ return syncCommittee;
78
+ }
79
+
80
+ const bestValidUpdate = store.bestValidUpdates.get(period - 1);
81
+ if (bestValidUpdate && (isSafeLightClientUpdate(bestValidUpdate.summary) || opts.allowForcedUpdates)) {
82
+ const {update} = bestValidUpdate;
83
+ const syncCommittee = deserializeSyncCommittee(update.nextSyncCommittee);
84
+ store.syncCommittees.set(period, syncCommittee);
85
+ pruneSetToMax(store.syncCommittees, MAX_SYNC_PERIODS_CACHE);
86
+ store.bestValidUpdates.delete(period - 1);
87
+
88
+ if (opts.updateHeadersOnForcedUpdate) {
89
+ // From https://github.com/ethereum/consensus-specs/blob/a57e15636013eeba3610ff3ade41781dba1bb0cd/specs/altair/light-client/sync-protocol.md?plain=1#L403
90
+ if (update.finalizedHeader.beacon.slot <= store.finalizedHeader.beacon.slot) {
91
+ update.finalizedHeader = update.attestedHeader;
92
+ }
93
+
94
+ // From https://github.com/ethereum/consensus-specs/blob/a57e15636013eeba3610ff3ade41781dba1bb0cd/specs/altair/light-client/sync-protocol.md?plain=1#L374
95
+ if (update.finalizedHeader.beacon.slot > store.finalizedHeader.beacon.slot) {
96
+ store.finalizedHeader = update.finalizedHeader;
97
+ }
98
+ if (store.finalizedHeader.beacon.slot > store.optimisticHeader.beacon.slot) {
99
+ store.optimisticHeader = store.finalizedHeader;
100
+ }
101
+ }
102
+
103
+ return syncCommittee;
104
+ }
105
+
106
+ const availableSyncCommittees = Array.from(store.syncCommittees.keys());
107
+ const availableBestValidUpdates = Array.from(store.bestValidUpdates.keys());
108
+ throw Error(
109
+ `No SyncCommittee for period ${period}` +
110
+ ` available syncCommittees ${JSON.stringify(availableSyncCommittees)}` +
111
+ ` available bestValidUpdates ${JSON.stringify(availableBestValidUpdates)}`
112
+ );
113
+ }
114
+
115
+ export function isSafeLightClientUpdate(update: LightClientUpdateSummary): boolean {
116
+ return (
117
+ update.activeParticipants * 3 >= SYNC_COMMITTEE_SIZE * 2 && update.isFinalityUpdate && update.isSyncCommitteeUpdate
118
+ );
119
+ }
@@ -0,0 +1,106 @@
1
+ import type {PublicKey} from "@chainsafe/blst";
2
+ import {BeaconConfig} from "@lodestar/config";
3
+ import {LightClientBootstrap, LightClientHeader, LightClientUpdate, SyncPeriod} from "@lodestar/types";
4
+ import {computeSyncPeriodAtSlot} from "../../util/epoch.js";
5
+ import type {LightClientUpdateSummary} from "./isBetterUpdate.js";
6
+ import {deserializeSyncCommittee} from "./utils.js";
7
+
8
+ export const MAX_SYNC_PERIODS_CACHE = 2;
9
+
10
+ export interface ILightClientStore {
11
+ readonly config: BeaconConfig;
12
+
13
+ /** Map of trusted SyncCommittee to be used for sig validation */
14
+ readonly syncCommittees: Map<SyncPeriod, SyncCommitteeFast>;
15
+ /** Map of best valid updates */
16
+ readonly bestValidUpdates: Map<SyncPeriod, LightClientUpdateWithSummary>;
17
+
18
+ getMaxActiveParticipants(period: SyncPeriod): number;
19
+ setActiveParticipants(period: SyncPeriod, activeParticipants: number): void;
20
+
21
+ // Header that is finalized
22
+ finalizedHeader: LightClientHeader;
23
+
24
+ // Most recent available reasonably-safe header
25
+ optimisticHeader: LightClientHeader;
26
+ }
27
+
28
+ export interface LightClientStoreEvents {
29
+ onSetFinalizedHeader?: (header: LightClientHeader) => void;
30
+ onSetOptimisticHeader?: (header: LightClientHeader) => void;
31
+ }
32
+
33
+ export class LightClientStore implements ILightClientStore {
34
+ readonly syncCommittees = new Map<SyncPeriod, SyncCommitteeFast>();
35
+ readonly bestValidUpdates = new Map<SyncPeriod, LightClientUpdateWithSummary>();
36
+
37
+ private finalizedHeaderValue: LightClientHeader;
38
+ private optimisticHeaderValue: LightClientHeader;
39
+
40
+ private readonly maxActiveParticipants = new Map<SyncPeriod, number>();
41
+
42
+ constructor(
43
+ readonly config: BeaconConfig,
44
+ bootstrap: LightClientBootstrap,
45
+ private readonly events: LightClientStoreEvents
46
+ ) {
47
+ const bootstrapPeriod = computeSyncPeriodAtSlot(bootstrap.header.beacon.slot);
48
+ this.syncCommittees.set(bootstrapPeriod, deserializeSyncCommittee(bootstrap.currentSyncCommittee));
49
+ this.finalizedHeaderValue = bootstrap.header;
50
+ this.optimisticHeaderValue = bootstrap.header;
51
+ }
52
+
53
+ get finalizedHeader(): LightClientHeader {
54
+ return this.finalizedHeaderValue;
55
+ }
56
+
57
+ set finalizedHeader(value: LightClientHeader) {
58
+ this.finalizedHeaderValue = value;
59
+ this.events.onSetFinalizedHeader?.(value);
60
+ }
61
+
62
+ get optimisticHeader(): LightClientHeader {
63
+ return this.optimisticHeaderValue;
64
+ }
65
+
66
+ set optimisticHeader(value: LightClientHeader) {
67
+ this.optimisticHeaderValue = value;
68
+ this.events.onSetOptimisticHeader?.(value);
69
+ }
70
+
71
+ getMaxActiveParticipants(period: SyncPeriod): number {
72
+ const currMaxParticipants = this.maxActiveParticipants.get(period) ?? 0;
73
+ const prevMaxParticipants = this.maxActiveParticipants.get(period - 1) ?? 0;
74
+
75
+ return Math.max(currMaxParticipants, prevMaxParticipants);
76
+ }
77
+
78
+ setActiveParticipants(period: SyncPeriod, activeParticipants: number): void {
79
+ const maxActiveParticipants = this.maxActiveParticipants.get(period) ?? 0;
80
+ if (activeParticipants > maxActiveParticipants) {
81
+ this.maxActiveParticipants.set(period, activeParticipants);
82
+ }
83
+
84
+ // Prune old entries
85
+ for (const key of this.maxActiveParticipants.keys()) {
86
+ if (key < period - MAX_SYNC_PERIODS_CACHE) {
87
+ this.maxActiveParticipants.delete(key);
88
+ }
89
+ }
90
+ }
91
+ }
92
+
93
+ export type SyncCommitteeFast = {
94
+ pubkeys: PublicKey[];
95
+ aggregatePubkey: PublicKey;
96
+ };
97
+
98
+ export type LightClientUpdateWithSummary = {
99
+ update: LightClientUpdate;
100
+ summary: LightClientUpdateSummary;
101
+ };
102
+
103
+ // === storePeriod ? store.currentSyncCommittee : store.nextSyncCommittee;
104
+ // if (!syncCommittee) {
105
+ // throw Error(`syncCommittee not available for signature period ${updateSignaturePeriod}`);
106
+ // }
@@ -0,0 +1,317 @@
1
+ import {PublicKey} from "@chainsafe/blst";
2
+ import {BitArray} from "@chainsafe/ssz";
3
+ import {ChainForkConfig} from "@lodestar/config";
4
+ 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,
9
+ ForkName,
10
+ ForkSeq,
11
+ NEXT_SYNC_COMMITTEE_DEPTH,
12
+ NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA,
13
+ isForkPostElectra,
14
+ } from "@lodestar/params";
15
+ import {
16
+ BeaconBlockHeader,
17
+ LightClientFinalityUpdate,
18
+ LightClientHeader,
19
+ LightClientOptimisticUpdate,
20
+ LightClientUpdate,
21
+ Slot,
22
+ SyncCommittee,
23
+ isElectraLightClientUpdate,
24
+ ssz,
25
+ } from "@lodestar/types";
26
+ import {byteArrayEquals, verifyMerkleBranch} from "@lodestar/utils";
27
+ import {computeEpochAtSlot, computeSyncPeriodAtSlot} from "../../util/epoch.js";
28
+ import type {LightClientStore, SyncCommitteeFast} from "./store.js";
29
+
30
+ export const GENESIS_SLOT = 0;
31
+ export const ZERO_HASH = new Uint8Array(32);
32
+ export const ZERO_PUBKEY = new Uint8Array(48);
33
+ export const ZERO_SYNC_COMMITTEE = ssz.altair.SyncCommittee.defaultValue();
34
+ export const ZERO_HEADER = ssz.phase0.BeaconBlockHeader.defaultValue();
35
+ /** From https://notes.ethereum.org/@vbuterin/extended_light_client_protocol#Optimistic-head-determining-function */
36
+ const SAFETY_THRESHOLD_FACTOR = 2;
37
+
38
+ export function sumBits(bits: BitArray): number {
39
+ return bits.getTrueBitIndexes().length;
40
+ }
41
+
42
+ /**
43
+ * Util to guarantee that all bits have a corresponding pubkey.
44
+ */
45
+ export function getParticipantPubkeys<T>(pubkeys: T[], bits: BitArray): T[] {
46
+ // BitArray.intersectValues() checks the length is correct.
47
+ return bits.intersectValues(pubkeys);
48
+ }
49
+
50
+ function deserializePubkeys(pubkeys: SyncCommittee["pubkeys"]): PublicKey[] {
51
+ return pubkeys.map((pk) => PublicKey.fromBytes(pk, true));
52
+ }
53
+
54
+ function serializePubkeys(pubkeys: PublicKey[]): SyncCommittee["pubkeys"] {
55
+ return pubkeys.map((pk) => pk.toBytes());
56
+ }
57
+
58
+ export function deserializeSyncCommittee(syncCommittee: SyncCommittee): SyncCommitteeFast {
59
+ return {
60
+ pubkeys: deserializePubkeys(syncCommittee.pubkeys),
61
+ aggregatePubkey: PublicKey.fromBytes(syncCommittee.aggregatePubkey, true),
62
+ };
63
+ }
64
+
65
+ export function serializeSyncCommittee(syncCommittee: SyncCommitteeFast): SyncCommittee {
66
+ return {
67
+ pubkeys: serializePubkeys(syncCommittee.pubkeys),
68
+ aggregatePubkey: syncCommittee.aggregatePubkey.toBytes(),
69
+ };
70
+ }
71
+
72
+ export function getSafetyThreshold(maxActiveParticipants: number): number {
73
+ return Math.floor(maxActiveParticipants / SAFETY_THRESHOLD_FACTOR);
74
+ }
75
+
76
+ 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);
82
+ }
83
+
84
+ 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);
88
+ }
89
+
90
+ export function isSyncCommitteeUpdate(update: LightClientUpdate): boolean {
91
+ return (
92
+ // Fast return for when constructing full LightClientUpdate from partial updates
93
+ update.nextSyncCommitteeBranch !==
94
+ getZeroSyncCommitteeBranch(isElectraLightClientUpdate(update) ? ForkName.electra : ForkName.altair) &&
95
+ update.nextSyncCommitteeBranch.some((branch) => !byteArrayEquals(branch, ZERO_HASH))
96
+ );
97
+ }
98
+
99
+ export function isFinalityUpdate(update: LightClientUpdate): boolean {
100
+ return (
101
+ // Fast return for when constructing full LightClientUpdate from partial updates
102
+ update.finalityBranch !==
103
+ getZeroFinalityBranch(isElectraLightClientUpdate(update) ? ForkName.electra : ForkName.altair) &&
104
+ update.finalityBranch.some((branch) => !byteArrayEquals(branch, ZERO_HASH))
105
+ );
106
+ }
107
+
108
+ export function isZeroedHeader(header: BeaconBlockHeader): boolean {
109
+ // Fast return for when constructing full LightClientUpdate from partial updates
110
+ return header === ZERO_HEADER || byteArrayEquals(header.bodyRoot, ZERO_HASH);
111
+ }
112
+
113
+ export function isZeroedSyncCommittee(syncCommittee: SyncCommittee): boolean {
114
+ // Fast return for when constructing full LightClientUpdate from partial updates
115
+ return syncCommittee === ZERO_SYNC_COMMITTEE || byteArrayEquals(syncCommittee.pubkeys[0], ZERO_PUBKEY);
116
+ }
117
+
118
+ export function isValidMerkleBranch(
119
+ leaf: Uint8Array,
120
+ branch: Uint8Array[],
121
+ depth: number,
122
+ index: number,
123
+ root: Uint8Array
124
+ ): boolean {
125
+ if (branch.length !== depth) {
126
+ return false;
127
+ }
128
+
129
+ return verifyMerkleBranch(leaf, branch, depth, index, root);
130
+ }
131
+
132
+ export function normalizeMerkleBranch(branch: Uint8Array[], depth: number): Uint8Array[] {
133
+ const numExtraDepth = depth - branch.length;
134
+
135
+ return [...Array.from({length: numExtraDepth}, () => ZERO_HASH), ...branch];
136
+ }
137
+
138
+ export function upgradeLightClientHeader(
139
+ config: ChainForkConfig,
140
+ targetFork: ForkName,
141
+ header: LightClientHeader
142
+ ): LightClientHeader {
143
+ const headerFork = config.getForkName(header.beacon.slot);
144
+ if (ForkSeq[headerFork] >= ForkSeq[targetFork]) {
145
+ throw Error(`Invalid upgrade request from headerFork=${headerFork} to targetFork=${targetFork}`);
146
+ }
147
+
148
+ // We are modifying the same header object, may be we could create a copy, but its
149
+ // not required as of now
150
+ const upgradedHeader = header;
151
+ const startUpgradeFromFork = Object.values(ForkName)[ForkSeq[headerFork] + 1];
152
+
153
+ switch (startUpgradeFromFork) {
154
+ // biome-ignore lint/suspicious/useDefaultSwitchClauseLast: We want default to evaluate at first to throw error early
155
+ default:
156
+ throw Error(
157
+ `Invalid startUpgradeFromFork=${startUpgradeFromFork} for headerFork=${headerFork} in upgradeLightClientHeader to targetFork=${targetFork}`
158
+ );
159
+
160
+ case ForkName.altair:
161
+ // biome-ignore lint/suspicious/noFallthroughSwitchClause: We need fall-through behavior here
162
+ case ForkName.bellatrix:
163
+ // Break if no further upgradation is required else fall through
164
+ if (ForkSeq[targetFork] <= ForkSeq.bellatrix) break;
165
+
166
+ // biome-ignore lint/suspicious/noFallthroughSwitchClause: We need fall-through behavior here
167
+ case ForkName.capella:
168
+ (upgradedHeader as LightClientHeader<ForkName.capella>).execution =
169
+ ssz.capella.LightClientHeader.fields.execution.defaultValue();
170
+ (upgradedHeader as LightClientHeader<ForkName.capella>).executionBranch =
171
+ ssz.capella.LightClientHeader.fields.executionBranch.defaultValue();
172
+
173
+ // Break if no further upgradation is required else fall through
174
+ if (ForkSeq[targetFork] <= ForkSeq.capella) break;
175
+
176
+ // biome-ignore lint/suspicious/noFallthroughSwitchClause: We need fall-through behavior here
177
+ case ForkName.deneb:
178
+ (upgradedHeader as LightClientHeader<ForkName.deneb>).execution.blobGasUsed =
179
+ ssz.deneb.LightClientHeader.fields.execution.fields.blobGasUsed.defaultValue();
180
+ (upgradedHeader as LightClientHeader<ForkName.deneb>).execution.excessBlobGas =
181
+ ssz.deneb.LightClientHeader.fields.execution.fields.excessBlobGas.defaultValue();
182
+
183
+ // Break if no further upgradation is required else fall through
184
+ if (ForkSeq[targetFork] <= ForkSeq.deneb) break;
185
+
186
+ // biome-ignore lint/suspicious/noFallthroughSwitchClause: We need fall-through behavior here
187
+ case ForkName.electra:
188
+ // No changes to LightClientHeader in Electra
189
+
190
+ // Break if no further upgrades is required else fall through
191
+ if (ForkSeq[targetFork] <= ForkSeq.electra) break;
192
+
193
+ // biome-ignore lint/suspicious/noFallthroughSwitchClause: We need fall-through behavior here
194
+ case ForkName.fulu:
195
+ // No changes to LightClientHeader in Fulu
196
+
197
+ // Break if no further upgrades is required else fall through
198
+ if (ForkSeq[targetFork] <= ForkSeq.fulu) break;
199
+
200
+ case ForkName.gloas:
201
+ // No changes to LightClientHeader in Gloas
202
+
203
+ // Break if no further upgrades is required else fall through
204
+ if (ForkSeq[targetFork] <= ForkSeq.gloas) break;
205
+ }
206
+ return upgradedHeader;
207
+ }
208
+
209
+ export function isValidLightClientHeader(config: ChainForkConfig, header: LightClientHeader): boolean {
210
+ const epoch = computeEpochAtSlot(header.beacon.slot);
211
+
212
+ if (epoch < config.CAPELLA_FORK_EPOCH) {
213
+ return (
214
+ ((header as LightClientHeader<ForkName.capella>).execution === undefined ||
215
+ ssz.capella.ExecutionPayloadHeader.equals(
216
+ (header as LightClientHeader<ForkName.capella>).execution,
217
+ ssz.capella.LightClientHeader.fields.execution.defaultValue()
218
+ )) &&
219
+ ((header as LightClientHeader<ForkName.capella>).executionBranch === undefined ||
220
+ ssz.capella.LightClientHeader.fields.executionBranch.equals(
221
+ ssz.capella.LightClientHeader.fields.executionBranch.defaultValue(),
222
+ (header as LightClientHeader<ForkName.capella>).executionBranch
223
+ ))
224
+ );
225
+ }
226
+
227
+ if (
228
+ epoch < config.DENEB_FORK_EPOCH &&
229
+ (((header as LightClientHeader<ForkName.deneb>).execution.blobGasUsed &&
230
+ (header as LightClientHeader<ForkName.deneb>).execution.blobGasUsed !== BigInt(0)) ||
231
+ ((header as LightClientHeader<ForkName.deneb>).execution.excessBlobGas &&
232
+ (header as LightClientHeader<ForkName.deneb>).execution.excessBlobGas !== BigInt(0)))
233
+ ) {
234
+ return false;
235
+ }
236
+
237
+ return isValidMerkleBranch(
238
+ config
239
+ .getPostBellatrixForkTypes(header.beacon.slot)
240
+ .ExecutionPayloadHeader.hashTreeRoot((header as LightClientHeader<ForkName.capella>).execution),
241
+ (header as LightClientHeader<ForkName.capella>).executionBranch,
242
+ EXECUTION_PAYLOAD_DEPTH,
243
+ EXECUTION_PAYLOAD_INDEX,
244
+ header.beacon.bodyRoot
245
+ );
246
+ }
247
+
248
+ export function upgradeLightClientUpdate(
249
+ config: ChainForkConfig,
250
+ targetFork: ForkName,
251
+ update: LightClientUpdate
252
+ ): LightClientUpdate {
253
+ update.attestedHeader = upgradeLightClientHeader(config, targetFork, update.attestedHeader);
254
+ update.finalizedHeader = upgradeLightClientHeader(config, targetFork, update.finalizedHeader);
255
+ update.nextSyncCommitteeBranch = normalizeMerkleBranch(
256
+ 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
262
+ );
263
+
264
+ return update;
265
+ }
266
+
267
+ export function upgradeLightClientFinalityUpdate(
268
+ config: ChainForkConfig,
269
+ targetFork: ForkName,
270
+ finalityUpdate: LightClientFinalityUpdate
271
+ ): LightClientFinalityUpdate {
272
+ finalityUpdate.attestedHeader = upgradeLightClientHeader(config, targetFork, finalityUpdate.attestedHeader);
273
+ finalityUpdate.finalizedHeader = upgradeLightClientHeader(config, targetFork, finalityUpdate.finalizedHeader);
274
+ finalityUpdate.finalityBranch = normalizeMerkleBranch(
275
+ finalityUpdate.finalityBranch,
276
+ isForkPostElectra(targetFork) ? FINALIZED_ROOT_DEPTH_ELECTRA : FINALIZED_ROOT_DEPTH
277
+ );
278
+
279
+ return finalityUpdate;
280
+ }
281
+
282
+ export function upgradeLightClientOptimisticUpdate(
283
+ config: ChainForkConfig,
284
+ targetFork: ForkName,
285
+ optimisticUpdate: LightClientOptimisticUpdate
286
+ ): LightClientOptimisticUpdate {
287
+ optimisticUpdate.attestedHeader = upgradeLightClientHeader(config, targetFork, optimisticUpdate.attestedHeader);
288
+
289
+ return optimisticUpdate;
290
+ }
291
+
292
+ /**
293
+ * Currently this upgradation is not required because all processing is done based on the
294
+ * summary that the store generates and maintains. In case store needs to be saved to disk,
295
+ * this could be required depending on the format the store is saved to the disk
296
+ */
297
+ export function upgradeLightClientStore(
298
+ config: ChainForkConfig,
299
+ targetFork: ForkName,
300
+ store: LightClientStore,
301
+ signatureSlot: Slot
302
+ ): LightClientStore {
303
+ const updateSignaturePeriod = computeSyncPeriodAtSlot(signatureSlot);
304
+ const bestValidUpdate = store.bestValidUpdates.get(updateSignaturePeriod);
305
+
306
+ if (bestValidUpdate) {
307
+ store.bestValidUpdates.set(updateSignaturePeriod, {
308
+ update: upgradeLightClientUpdate(config, targetFork, bestValidUpdate.update),
309
+ summary: bestValidUpdate.summary,
310
+ });
311
+ }
312
+
313
+ store.finalizedHeader = upgradeLightClientHeader(config, targetFork, store.finalizedHeader);
314
+ store.optimisticHeader = upgradeLightClientHeader(config, targetFork, store.optimisticHeader);
315
+
316
+ return store;
317
+ }
@@ -0,0 +1,39 @@
1
+ import {ChainForkConfig} from "@lodestar/config";
2
+ import {isForkPostElectra} from "@lodestar/params";
3
+ import {LightClientBootstrap, Root, ssz} from "@lodestar/types";
4
+ 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;
11
+
12
+ export function validateLightClientBootstrap(
13
+ config: ChainForkConfig,
14
+ trustedBlockRoot: Root,
15
+ bootstrap: LightClientBootstrap
16
+ ): void {
17
+ const headerRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(bootstrap.header.beacon);
18
+ const fork = config.getForkName(bootstrap.header.beacon.slot);
19
+
20
+ if (!isValidLightClientHeader(config, bootstrap.header)) {
21
+ throw Error("Bootstrap Header is not Valid Light Client Header");
22
+ }
23
+
24
+ if (!byteArrayEquals(headerRoot, trustedBlockRoot)) {
25
+ throw Error(`bootstrap header root ${toHex(headerRoot)} != trusted root ${toHex(trustedBlockRoot)}`);
26
+ }
27
+
28
+ if (
29
+ !isValidMerkleBranch(
30
+ ssz.altair.SyncCommittee.hashTreeRoot(bootstrap.currentSyncCommittee),
31
+ 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,
34
+ bootstrap.header.beacon.stateRoot
35
+ )
36
+ ) {
37
+ throw Error("Invalid currentSyncCommittee merkle branch");
38
+ }
39
+ }