@lodestar/state-transition 1.45.0 → 1.46.0-dev.cdb6b1a0d2
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/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 +8 -8
- 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/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,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
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.
|
|
102
|
+
stateGloas.payloadExpectedWithdrawals = ssz.gloas.Withdrawals.toViewDU(expectedWithdrawals);
|
|
103
103
|
|
|
104
104
|
// Update builder pending withdrawals queue
|
|
105
105
|
stateGloas.builderPendingWithdrawals = stateGloas.builderPendingWithdrawals.sliceFrom(
|
package/src/cache/stateCache.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 {
|
|
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:
|
|
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 =
|
|
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
|
|
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 {
|
|
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(
|
|
83
|
-
finalizedHeader:
|
|
84
|
-
|
|
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
|
});
|
|
@@ -5,7 +5,13 @@ import {pruneSetToMax} from "@lodestar/utils";
|
|
|
5
5
|
import {computeSyncPeriodAtSlot} from "../../util/epoch.js";
|
|
6
6
|
import {LightClientUpdateSummary, isBetterUpdate, toLightClientUpdateSummary} from "./isBetterUpdate.js";
|
|
7
7
|
import {type ILightClientStore, MAX_SYNC_PERIODS_CACHE, type SyncCommitteeFast} from "./store.js";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
deserializeSyncCommittee,
|
|
10
|
+
getSafetyThreshold,
|
|
11
|
+
isFinalityUpdate,
|
|
12
|
+
isSyncCommitteeUpdate,
|
|
13
|
+
sumBits,
|
|
14
|
+
} from "./utils.js";
|
|
9
15
|
import {validateLightClientUpdate} from "./validateLightClientUpdate.js";
|
|
10
16
|
|
|
11
17
|
export interface ProcessUpdateOpts {
|
|
@@ -44,7 +50,13 @@ export function processLightClientUpdate(
|
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
// Update finalized header
|
|
53
|
+
// Defense-in-depth: require isFinalityUpdate(update) so a non-finality update can never
|
|
54
|
+
// overwrite store.finalizedHeader, regardless of any future loosening of the zeroed-header
|
|
55
|
+
// checks in validateLightClientUpdate. Spec-faithful behavior also holds without this guard
|
|
56
|
+
// once update.finalizedHeader is guaranteed fully-zero in the non-finality case, but the
|
|
57
|
+
// guard makes the invariant local to this write site.
|
|
47
58
|
if (
|
|
59
|
+
isFinalityUpdate(update) &&
|
|
48
60
|
syncCommitteeTrueBits * 3 >= SYNC_COMMITTEE_SIZE * 2 &&
|
|
49
61
|
update.finalizedHeader.beacon.slot > store.finalizedHeader.beacon.slot
|
|
50
62
|
) {
|
|
@@ -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
|
) {
|