@lodestar/state-transition 1.45.0 → 1.46.0-dev.4191d8e353
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.
- package/lib/block/processAttestationsAltair.d.ts +4 -1
- package/lib/block/processAttestationsAltair.d.ts.map +1 -1
- package/lib/block/processAttestationsAltair.js +7 -5
- package/lib/block/processAttestationsAltair.js.map +1 -1
- package/lib/block/processBuilderDepositRequest.d.ts.map +1 -1
- package/lib/block/processBuilderDepositRequest.js +13 -7
- package/lib/block/processBuilderDepositRequest.js.map +1 -1
- package/lib/block/processOperations.d.ts.map +1 -1
- package/lib/block/processOperations.js +17 -1
- package/lib/block/processOperations.js.map +1 -1
- package/lib/block/processParentExecutionPayload.d.ts.map +1 -1
- package/lib/block/processParentExecutionPayload.js +15 -1
- package/lib/block/processParentExecutionPayload.js.map +1 -1
- package/lib/block/processWithdrawals.js +1 -1
- package/lib/block/processWithdrawals.js.map +1 -1
- package/lib/cache/epochCache.d.ts.map +1 -1
- package/lib/cache/epochCache.js +18 -19
- package/lib/cache/epochCache.js.map +1 -1
- package/lib/cache/stateCache.d.ts.map +1 -1
- package/lib/cache/stateCache.js +2 -4
- package/lib/cache/stateCache.js.map +1 -1
- package/lib/epoch/processParticipationFlagUpdates.d.ts +2 -2
- package/lib/epoch/processParticipationFlagUpdates.d.ts.map +1 -1
- package/lib/epoch/processParticipationFlagUpdates.js +13 -0
- package/lib/epoch/processParticipationFlagUpdates.js.map +1 -1
- package/lib/epoch/processRewardsAndPenalties.d.ts.map +1 -1
- package/lib/epoch/processRewardsAndPenalties.js +1 -2
- package/lib/epoch/processRewardsAndPenalties.js.map +1 -1
- package/lib/lightClient/proofs.d.ts.map +1 -1
- package/lib/lightClient/proofs.js +28 -2
- package/lib/lightClient/proofs.js.map +1 -1
- package/lib/lightClient/spec/index.d.ts.map +1 -1
- package/lib/lightClient/spec/index.js +7 -4
- package/lib/lightClient/spec/index.js.map +1 -1
- package/lib/lightClient/spec/processLightClientUpdate.d.ts.map +1 -1
- package/lib/lightClient/spec/processLightClientUpdate.js +8 -2
- package/lib/lightClient/spec/processLightClientUpdate.js.map +1 -1
- package/lib/lightClient/spec/utils.d.ts +7 -2
- package/lib/lightClient/spec/utils.d.ts.map +1 -1
- package/lib/lightClient/spec/utils.js +130 -19
- package/lib/lightClient/spec/utils.js.map +1 -1
- package/lib/lightClient/spec/validateLightClientBootstrap.d.ts.map +1 -1
- package/lib/lightClient/spec/validateLightClientBootstrap.js +2 -7
- package/lib/lightClient/spec/validateLightClientBootstrap.js.map +1 -1
- package/lib/lightClient/spec/validateLightClientUpdate.d.ts.map +1 -1
- package/lib/lightClient/spec/validateLightClientUpdate.js +6 -5
- package/lib/lightClient/spec/validateLightClientUpdate.js.map +1 -1
- package/lib/lightClient/types.d.ts +3 -1
- package/lib/lightClient/types.d.ts.map +1 -1
- package/lib/metrics.js +2 -4
- package/lib/metrics.js.map +1 -1
- package/lib/slot/upgradeStateToAltair.js +1 -1
- package/lib/slot/upgradeStateToAltair.js.map +1 -1
- package/lib/slot/upgradeStateToGloas.d.ts.map +1 -1
- package/lib/slot/upgradeStateToGloas.js +48 -9
- package/lib/slot/upgradeStateToGloas.js.map +1 -1
- package/lib/util/execution.d.ts.map +1 -1
- package/lib/util/execution.js +7 -2
- package/lib/util/execution.js.map +1 -1
- package/lib/util/gloas.d.ts +3 -1
- package/lib/util/gloas.d.ts.map +1 -1
- package/lib/util/gloas.js +8 -11
- package/lib/util/gloas.js.map +1 -1
- package/lib/util/index.d.ts +1 -0
- package/lib/util/index.d.ts.map +1 -1
- package/lib/util/index.js +1 -0
- package/lib/util/index.js.map +1 -1
- package/lib/util/ssz.d.ts +9 -0
- package/lib/util/ssz.d.ts.map +1 -0
- package/lib/util/ssz.js +39 -0
- package/lib/util/ssz.js.map +1 -0
- package/package.json +14 -14
- package/src/block/processAttestationsAltair.ts +9 -6
- package/src/block/processBuilderDepositRequest.ts +21 -8
- package/src/block/processOperations.ts +28 -1
- package/src/block/processParentExecutionPayload.ts +26 -1
- package/src/block/processWithdrawals.ts +1 -1
- package/src/cache/epochCache.ts +19 -22
- package/src/cache/stateCache.ts +2 -4
- package/src/epoch/processParticipationFlagUpdates.ts +20 -2
- package/src/epoch/processRewardsAndPenalties.ts +1 -2
- package/src/lightClient/proofs.ts +36 -1
- package/src/lightClient/spec/index.ts +8 -4
- package/src/lightClient/spec/processLightClientUpdate.ts +13 -1
- package/src/lightClient/spec/utils.ts +200 -30
- package/src/lightClient/spec/validateLightClientBootstrap.ts +3 -10
- package/src/lightClient/spec/validateLightClientUpdate.ts +11 -21
- package/src/lightClient/types.ts +3 -1
- package/src/metrics.ts +2 -4
- package/src/slot/upgradeStateToAltair.ts +1 -1
- package/src/slot/upgradeStateToGloas.ts +84 -9
- package/src/util/execution.ts +9 -4
- package/src/util/gloas.ts +9 -14
- package/src/util/index.ts +1 -0
- 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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
12
|
-
|
|
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,
|
|
@@ -29,7 +38,6 @@ import type {LightClientStore, SyncCommitteeFast} from "./store.js";
|
|
|
29
38
|
|
|
30
39
|
export const GENESIS_SLOT = 0;
|
|
31
40
|
export const ZERO_HASH = new Uint8Array(32);
|
|
32
|
-
export const ZERO_PUBKEY = new Uint8Array(48);
|
|
33
41
|
export const ZERO_SYNC_COMMITTEE = ssz.altair.SyncCommittee.defaultValue();
|
|
34
42
|
export const ZERO_HEADER = ssz.phase0.BeaconBlockHeader.defaultValue();
|
|
35
43
|
/** From https://notes.ethereum.org/@vbuterin/extended_light_client_protocol#Optimistic-head-determining-function */
|
|
@@ -74,17 +82,11 @@ export function getSafetyThreshold(maxActiveParticipants: number): number {
|
|
|
74
82
|
}
|
|
75
83
|
|
|
76
84
|
export function getZeroSyncCommitteeBranch(fork: ForkName): Uint8Array[] {
|
|
77
|
-
|
|
78
|
-
? NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA
|
|
79
|
-
: NEXT_SYNC_COMMITTEE_DEPTH;
|
|
80
|
-
|
|
81
|
-
return Array.from({length: nextSyncCommitteeDepth}, () => ZERO_HASH);
|
|
85
|
+
return Array.from({length: getGindexDepth(nextSyncCommitteeGindexAtFork(fork))}, () => ZERO_HASH);
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
export function getZeroFinalityBranch(fork: ForkName): Uint8Array[] {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return Array.from({length: finalizedRootDepth}, () => ZERO_HASH);
|
|
89
|
+
return Array.from({length: getGindexDepth(finalizedRootGindexAtFork(fork))}, () => ZERO_HASH);
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
export function isSyncCommitteeUpdate(update: LightClientUpdate): boolean {
|
|
@@ -106,13 +108,16 @@ export function isFinalityUpdate(update: LightClientUpdate): boolean {
|
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
export function isZeroedHeader(header: BeaconBlockHeader): boolean {
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
+
// Spec requires the whole header to equal LightClientHeader() in the non-finality case
|
|
112
|
+
// (see altair/light-client/sync-protocol.md `process_light_client_update`). Checking only
|
|
113
|
+
// bodyRoot would let an attacker smuggle arbitrary slot/proposerIndex/parentRoot/stateRoot
|
|
114
|
+
// through the non-finality branch and overwrite store.finalizedHeader.
|
|
115
|
+
return header === ZERO_HEADER || ssz.phase0.BeaconBlockHeader.equals(header, ZERO_HEADER);
|
|
111
116
|
}
|
|
112
117
|
|
|
113
118
|
export function isZeroedSyncCommittee(syncCommittee: SyncCommittee): boolean {
|
|
114
|
-
//
|
|
115
|
-
return syncCommittee === ZERO_SYNC_COMMITTEE ||
|
|
119
|
+
// Spec requires the whole SyncCommittee to equal SyncCommittee() in the non-sync-committee-update case.
|
|
120
|
+
return syncCommittee === ZERO_SYNC_COMMITTEE || ssz.altair.SyncCommittee.equals(syncCommittee, ZERO_SYNC_COMMITTEE);
|
|
116
121
|
}
|
|
117
122
|
|
|
118
123
|
export function isValidMerkleBranch(
|
|
@@ -129,12 +134,73 @@ export function isValidMerkleBranch(
|
|
|
129
134
|
return verifyMerkleBranch(leaf, branch, depth, index, root);
|
|
130
135
|
}
|
|
131
136
|
|
|
132
|
-
export function
|
|
137
|
+
export function isValidNormalizedMerkleBranch(
|
|
138
|
+
leaf: Uint8Array,
|
|
139
|
+
branch: Uint8Array[],
|
|
140
|
+
gindex: number,
|
|
141
|
+
root: Uint8Array
|
|
142
|
+
): boolean {
|
|
143
|
+
const depth = getGindexDepth(gindex);
|
|
144
|
+
const index = getGindexIndex(gindex);
|
|
145
|
+
const numExtraDepth = branch.length - depth;
|
|
146
|
+
if (numExtraDepth < 0) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
for (let i = 0; i < numExtraDepth; i++) {
|
|
151
|
+
if (!byteArrayEquals(branch[i], ZERO_HASH)) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return isValidMerkleBranch(leaf, branch.slice(numExtraDepth), depth, index, root);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function normalizeMerkleBranch(branch: Uint8Array[], gindex: number): Uint8Array[] {
|
|
160
|
+
const depth = getGindexDepth(gindex);
|
|
133
161
|
const numExtraDepth = depth - branch.length;
|
|
134
162
|
|
|
135
163
|
return [...Array.from({length: numExtraDepth}, () => ZERO_HASH), ...branch];
|
|
136
164
|
}
|
|
137
165
|
|
|
166
|
+
export function currentSyncCommitteeGindexAtFork(fork: ForkName): number {
|
|
167
|
+
if (isForkPostGloas(fork)) {
|
|
168
|
+
return CURRENT_SYNC_COMMITTEE_GINDEX_GLOAS;
|
|
169
|
+
}
|
|
170
|
+
if (isForkPostElectra(fork)) {
|
|
171
|
+
return CURRENT_SYNC_COMMITTEE_GINDEX_ELECTRA;
|
|
172
|
+
}
|
|
173
|
+
return CURRENT_SYNC_COMMITTEE_GINDEX;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function finalizedRootGindexAtFork(fork: ForkName): number {
|
|
177
|
+
if (isForkPostGloas(fork)) {
|
|
178
|
+
return FINALIZED_ROOT_GINDEX_GLOAS;
|
|
179
|
+
}
|
|
180
|
+
if (isForkPostElectra(fork)) {
|
|
181
|
+
return FINALIZED_ROOT_GINDEX_ELECTRA;
|
|
182
|
+
}
|
|
183
|
+
return FINALIZED_ROOT_GINDEX;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function nextSyncCommitteeGindexAtFork(fork: ForkName): number {
|
|
187
|
+
if (isForkPostGloas(fork)) {
|
|
188
|
+
return NEXT_SYNC_COMMITTEE_GINDEX_GLOAS;
|
|
189
|
+
}
|
|
190
|
+
if (isForkPostElectra(fork)) {
|
|
191
|
+
return NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA;
|
|
192
|
+
}
|
|
193
|
+
return NEXT_SYNC_COMMITTEE_GINDEX;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function getGindexDepth(gindex: number): number {
|
|
197
|
+
return Math.floor(Math.log2(gindex));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function getGindexIndex(gindex: number): number {
|
|
201
|
+
return gindex - 2 ** getGindexDepth(gindex);
|
|
202
|
+
}
|
|
203
|
+
|
|
138
204
|
export function upgradeLightClientHeader(
|
|
139
205
|
config: ChainForkConfig,
|
|
140
206
|
targetFork: ForkName,
|
|
@@ -147,7 +213,7 @@ export function upgradeLightClientHeader(
|
|
|
147
213
|
|
|
148
214
|
// We are modifying the same header object, may be we could create a copy, but its
|
|
149
215
|
// not required as of now
|
|
150
|
-
|
|
216
|
+
let upgradedHeader = header;
|
|
151
217
|
const startUpgradeFromFork = Object.values(ForkName)[ForkSeq[headerFork] + 1];
|
|
152
218
|
|
|
153
219
|
switch (startUpgradeFromFork) {
|
|
@@ -198,7 +264,11 @@ export function upgradeLightClientHeader(
|
|
|
198
264
|
if (ForkSeq[targetFork] <= ForkSeq.fulu) break;
|
|
199
265
|
|
|
200
266
|
case ForkName.gloas:
|
|
201
|
-
|
|
267
|
+
if (isGloasLightClientHeader(upgradedHeader)) {
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
upgradedHeader = upgradeLightClientHeaderToGloas(config, upgradedHeader as LightClientHeader<ForkName.electra>);
|
|
202
272
|
|
|
203
273
|
// Break if no further upgrades is required else fall through
|
|
204
274
|
if (ForkSeq[targetFork] <= ForkSeq.gloas) break;
|
|
@@ -209,6 +279,40 @@ export function upgradeLightClientHeader(
|
|
|
209
279
|
export function isValidLightClientHeader(config: ChainForkConfig, header: LightClientHeader): boolean {
|
|
210
280
|
const epoch = computeEpochAtSlot(header.beacon.slot);
|
|
211
281
|
|
|
282
|
+
if (isGloasLightClientHeader(header)) {
|
|
283
|
+
if (epoch >= config.GLOAS_FORK_EPOCH) {
|
|
284
|
+
return isValidNormalizedMerkleBranch(
|
|
285
|
+
header.executionBlockHash,
|
|
286
|
+
header.executionBranch,
|
|
287
|
+
EXECUTION_BLOCK_HASH_GINDEX_GLOAS,
|
|
288
|
+
header.beacon.bodyRoot
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (epoch >= config.DENEB_FORK_EPOCH) {
|
|
293
|
+
return isValidNormalizedMerkleBranch(
|
|
294
|
+
header.executionBlockHash,
|
|
295
|
+
header.executionBranch,
|
|
296
|
+
EXECUTION_BLOCK_HASH_GINDEX_DENEB,
|
|
297
|
+
header.beacon.bodyRoot
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (epoch >= config.CAPELLA_FORK_EPOCH) {
|
|
302
|
+
return isValidNormalizedMerkleBranch(
|
|
303
|
+
header.executionBlockHash,
|
|
304
|
+
header.executionBranch,
|
|
305
|
+
EXECUTION_BLOCK_HASH_GINDEX,
|
|
306
|
+
header.beacon.bodyRoot
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return (
|
|
311
|
+
byteArrayEquals(header.executionBlockHash, ZERO_HASH) &&
|
|
312
|
+
header.executionBranch.every((node) => byteArrayEquals(node, ZERO_HASH))
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
212
316
|
if (epoch < config.CAPELLA_FORK_EPOCH) {
|
|
213
317
|
return (
|
|
214
318
|
((header as LightClientHeader<ForkName.capella>).execution === undefined ||
|
|
@@ -239,8 +343,8 @@ export function isValidLightClientHeader(config: ChainForkConfig, header: LightC
|
|
|
239
343
|
.getPostBellatrixForkTypes(header.beacon.slot)
|
|
240
344
|
.ExecutionPayloadHeader.hashTreeRoot((header as LightClientHeader<ForkName.capella>).execution),
|
|
241
345
|
(header as LightClientHeader<ForkName.capella>).executionBranch,
|
|
242
|
-
|
|
243
|
-
|
|
346
|
+
getGindexDepth(BLOCK_BODY_EXECUTION_PAYLOAD_GINDEX),
|
|
347
|
+
getGindexIndex(BLOCK_BODY_EXECUTION_PAYLOAD_GINDEX),
|
|
244
348
|
header.beacon.bodyRoot
|
|
245
349
|
);
|
|
246
350
|
}
|
|
@@ -254,12 +358,9 @@ export function upgradeLightClientUpdate(
|
|
|
254
358
|
update.finalizedHeader = upgradeLightClientHeader(config, targetFork, update.finalizedHeader);
|
|
255
359
|
update.nextSyncCommitteeBranch = normalizeMerkleBranch(
|
|
256
360
|
update.nextSyncCommitteeBranch,
|
|
257
|
-
|
|
258
|
-
);
|
|
259
|
-
update.finalityBranch = normalizeMerkleBranch(
|
|
260
|
-
update.finalityBranch,
|
|
261
|
-
isForkPostElectra(targetFork) ? FINALIZED_ROOT_DEPTH_ELECTRA : FINALIZED_ROOT_DEPTH
|
|
361
|
+
nextSyncCommitteeGindexAtFork(targetFork)
|
|
262
362
|
);
|
|
363
|
+
update.finalityBranch = normalizeMerkleBranch(update.finalityBranch, finalizedRootGindexAtFork(targetFork));
|
|
263
364
|
|
|
264
365
|
return update;
|
|
265
366
|
}
|
|
@@ -273,7 +374,7 @@ export function upgradeLightClientFinalityUpdate(
|
|
|
273
374
|
finalityUpdate.finalizedHeader = upgradeLightClientHeader(config, targetFork, finalityUpdate.finalizedHeader);
|
|
274
375
|
finalityUpdate.finalityBranch = normalizeMerkleBranch(
|
|
275
376
|
finalityUpdate.finalityBranch,
|
|
276
|
-
|
|
377
|
+
finalizedRootGindexAtFork(targetFork)
|
|
277
378
|
);
|
|
278
379
|
|
|
279
380
|
return finalityUpdate;
|
|
@@ -315,3 +416,72 @@ export function upgradeLightClientStore(
|
|
|
315
416
|
|
|
316
417
|
return store;
|
|
317
418
|
}
|
|
419
|
+
|
|
420
|
+
function isGloasLightClientHeader(header: LightClientHeader): header is LightClientHeader<ForkName.gloas> {
|
|
421
|
+
return (header as LightClientHeader<ForkName.gloas>).executionBlockHash !== undefined;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function upgradeLightClientHeaderToGloas(
|
|
425
|
+
config: ChainForkConfig,
|
|
426
|
+
pre: LightClientHeader<ForkName.electra>
|
|
427
|
+
): LightClientHeader<ForkName.gloas> {
|
|
428
|
+
if (ssz.electra.LightClientHeader.equals(pre, ssz.electra.LightClientHeader.defaultValue())) {
|
|
429
|
+
return ssz.gloas.LightClientHeader.defaultValue();
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const epoch = computeEpochAtSlot(pre.beacon.slot);
|
|
433
|
+
|
|
434
|
+
if (epoch >= config.DENEB_FORK_EPOCH) {
|
|
435
|
+
const blockHashGindex = ssz.deneb.ExecutionPayloadHeader.getPathInfo(["blockHash"]).gindex;
|
|
436
|
+
const executionBranch = new Tree(ssz.deneb.ExecutionPayloadHeader.toView(pre.execution).node).getSingleProof(
|
|
437
|
+
blockHashGindex
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
return {
|
|
441
|
+
beacon: pre.beacon,
|
|
442
|
+
executionBlockHash: pre.execution.blockHash,
|
|
443
|
+
executionBranch: normalizeMerkleBranch(
|
|
444
|
+
[...executionBranch, ...pre.executionBranch],
|
|
445
|
+
EXECUTION_BLOCK_HASH_GINDEX_GLOAS
|
|
446
|
+
),
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (epoch >= config.CAPELLA_FORK_EPOCH) {
|
|
451
|
+
const executionHeader = {
|
|
452
|
+
parentHash: pre.execution.parentHash,
|
|
453
|
+
feeRecipient: pre.execution.feeRecipient,
|
|
454
|
+
stateRoot: pre.execution.stateRoot,
|
|
455
|
+
receiptsRoot: pre.execution.receiptsRoot,
|
|
456
|
+
logsBloom: pre.execution.logsBloom,
|
|
457
|
+
prevRandao: pre.execution.prevRandao,
|
|
458
|
+
blockNumber: pre.execution.blockNumber,
|
|
459
|
+
gasLimit: pre.execution.gasLimit,
|
|
460
|
+
gasUsed: pre.execution.gasUsed,
|
|
461
|
+
timestamp: pre.execution.timestamp,
|
|
462
|
+
extraData: pre.execution.extraData,
|
|
463
|
+
baseFeePerGas: pre.execution.baseFeePerGas,
|
|
464
|
+
blockHash: pre.execution.blockHash,
|
|
465
|
+
transactionsRoot: pre.execution.transactionsRoot,
|
|
466
|
+
withdrawalsRoot: pre.execution.withdrawalsRoot,
|
|
467
|
+
};
|
|
468
|
+
const blockHashGindex = ssz.capella.ExecutionPayloadHeader.getPathInfo(["blockHash"]).gindex;
|
|
469
|
+
const executionBranch = new Tree(ssz.capella.ExecutionPayloadHeader.toView(executionHeader).node).getSingleProof(
|
|
470
|
+
blockHashGindex
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
return {
|
|
474
|
+
beacon: pre.beacon,
|
|
475
|
+
executionBlockHash: executionHeader.blockHash,
|
|
476
|
+
executionBranch: normalizeMerkleBranch(
|
|
477
|
+
[...executionBranch, ...pre.executionBranch],
|
|
478
|
+
EXECUTION_BLOCK_HASH_GINDEX_GLOAS
|
|
479
|
+
),
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return {
|
|
484
|
+
...ssz.gloas.LightClientHeader.defaultValue(),
|
|
485
|
+
beacon: pre.beacon,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
@@ -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,
|
|
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
|
-
!
|
|
23
|
+
!isValidNormalizedMerkleBranch(
|
|
30
24
|
ssz.altair.SyncCommittee.hashTreeRoot(bootstrap.currentSyncCommittee),
|
|
31
25
|
bootstrap.currentSyncCommitteeBranch,
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
!
|
|
73
|
+
!isValidNormalizedMerkleBranch(
|
|
82
74
|
finalizedRoot,
|
|
83
75
|
update.finalityBranch,
|
|
84
|
-
|
|
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
|
-
!
|
|
92
|
+
!isValidNormalizedMerkleBranch(
|
|
102
93
|
ssz.altair.SyncCommittee.hashTreeRoot(update.nextSyncCommittee),
|
|
103
94
|
update.nextSyncCommitteeBranch,
|
|
104
|
-
|
|
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
|
) {
|
package/src/lightClient/types.ts
CHANGED
|
@@ -26,8 +26,10 @@
|
|
|
26
26
|
* ```
|
|
27
27
|
*/
|
|
28
28
|
export type SyncCommitteeWitness = {
|
|
29
|
-
/**
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
42
|
-
|
|
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 =
|
|
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 =
|
|
65
|
-
|
|
66
|
-
|
|
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.
|
|
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++) {
|
package/src/util/execution.ts
CHANGED
|
@@ -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 =
|
|
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 =
|
|
151
|
-
|
|
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) {
|