@lodestar/light-client 1.35.0-dev.fcf8d024ea → 1.35.0-dev.feed916580
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/package.json +18 -16
- package/lib/events.d.ts.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/spec/index.d.ts.map +0 -1
- package/lib/spec/isBetterUpdate.d.ts.map +0 -1
- package/lib/spec/processLightClientUpdate.d.ts.map +0 -1
- package/lib/spec/store.d.ts.map +0 -1
- package/lib/spec/utils.d.ts.map +0 -1
- package/lib/spec/validateLightClientBootstrap.d.ts.map +0 -1
- package/lib/spec/validateLightClientUpdate.d.ts.map +0 -1
- package/lib/transport/index.d.ts.map +0 -1
- package/lib/transport/interface.d.ts.map +0 -1
- package/lib/transport/rest.d.ts.map +0 -1
- package/lib/transport.d.ts.map +0 -1
- package/lib/types.d.ts.map +0 -1
- package/lib/utils/api.d.ts.map +0 -1
- package/lib/utils/chunkify.d.ts.map +0 -1
- package/lib/utils/clock.d.ts.map +0 -1
- package/lib/utils/domain.d.ts.map +0 -1
- package/lib/utils/index.d.ts.map +0 -1
- package/lib/utils/logger.d.ts.map +0 -1
- package/lib/utils/map.d.ts.map +0 -1
- package/lib/utils/normalizeMerkleBranch.d.ts.map +0 -1
- package/lib/utils/update.d.ts.map +0 -1
- package/lib/utils/utils.d.ts.map +0 -1
- package/lib/utils/verifyMerkleBranch.d.ts.map +0 -1
- package/lib/utils.d.ts.map +0 -1
- package/lib/validation.d.ts.map +0 -1
- package/src/events.ts +0 -17
- package/src/index.ts +0 -340
- package/src/spec/index.ts +0 -71
- package/src/spec/isBetterUpdate.ts +0 -94
- package/src/spec/processLightClientUpdate.ts +0 -119
- package/src/spec/store.ts +0 -105
- package/src/spec/utils.ts +0 -266
- package/src/spec/validateLightClientBootstrap.ts +0 -41
- package/src/spec/validateLightClientUpdate.ts +0 -154
- package/src/transport/index.ts +0 -2
- package/src/transport/interface.ts +0 -37
- package/src/transport/rest.ts +0 -89
- package/src/transport.ts +0 -2
- package/src/types.ts +0 -20
- package/src/utils/api.ts +0 -19
- package/src/utils/chunkify.ts +0 -26
- package/src/utils/clock.ts +0 -45
- package/src/utils/domain.ts +0 -44
- package/src/utils/index.ts +0 -8
- package/src/utils/logger.ts +0 -29
- package/src/utils/map.ts +0 -20
- package/src/utils/normalizeMerkleBranch.ts +0 -15
- package/src/utils/update.ts +0 -30
- package/src/utils/utils.ts +0 -95
- package/src/utils/verifyMerkleBranch.ts +0 -29
- package/src/utils.ts +0 -2
- package/src/validation.ts +0 -201
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import bls from "@chainsafe/bls";
|
|
2
|
-
import type {PublicKey, Signature} from "@chainsafe/bls/types";
|
|
3
|
-
import {ChainForkConfig} from "@lodestar/config";
|
|
4
|
-
import {
|
|
5
|
-
DOMAIN_SYNC_COMMITTEE,
|
|
6
|
-
FINALIZED_ROOT_DEPTH,
|
|
7
|
-
FINALIZED_ROOT_DEPTH_ELECTRA,
|
|
8
|
-
FINALIZED_ROOT_INDEX,
|
|
9
|
-
FINALIZED_ROOT_INDEX_ELECTRA,
|
|
10
|
-
GENESIS_SLOT,
|
|
11
|
-
MIN_SYNC_COMMITTEE_PARTICIPANTS,
|
|
12
|
-
NEXT_SYNC_COMMITTEE_DEPTH,
|
|
13
|
-
NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA,
|
|
14
|
-
NEXT_SYNC_COMMITTEE_INDEX,
|
|
15
|
-
NEXT_SYNC_COMMITTEE_INDEX_ELECTRA,
|
|
16
|
-
} from "@lodestar/params";
|
|
17
|
-
import {LightClientUpdate, Root, isElectraLightClientUpdate, ssz} from "@lodestar/types";
|
|
18
|
-
import {SyncCommitteeFast} from "../types.js";
|
|
19
|
-
import {isValidMerkleBranch} from "../utils/index.js";
|
|
20
|
-
import {getParticipantPubkeys, sumBits} from "../utils/utils.js";
|
|
21
|
-
import {ILightClientStore} from "./store.js";
|
|
22
|
-
import {
|
|
23
|
-
ZERO_HASH,
|
|
24
|
-
isFinalityUpdate,
|
|
25
|
-
isSyncCommitteeUpdate,
|
|
26
|
-
isValidLightClientHeader,
|
|
27
|
-
isZeroedHeader,
|
|
28
|
-
isZeroedSyncCommittee,
|
|
29
|
-
} from "./utils.js";
|
|
30
|
-
|
|
31
|
-
export function validateLightClientUpdate(
|
|
32
|
-
config: ChainForkConfig,
|
|
33
|
-
store: ILightClientStore,
|
|
34
|
-
update: LightClientUpdate,
|
|
35
|
-
syncCommittee: SyncCommitteeFast
|
|
36
|
-
): void {
|
|
37
|
-
// Verify sync committee has sufficient participants
|
|
38
|
-
if (sumBits(update.syncAggregate.syncCommitteeBits) < MIN_SYNC_COMMITTEE_PARTICIPANTS) {
|
|
39
|
-
throw Error("Sync committee has not sufficient participants");
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (!isValidLightClientHeader(config, update.attestedHeader)) {
|
|
43
|
-
throw Error("Attested Header is not Valid Light Client Header");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Sanity check that slots are in correct order
|
|
47
|
-
if (update.signatureSlot <= update.attestedHeader.beacon.slot) {
|
|
48
|
-
throw Error(
|
|
49
|
-
`signature slot ${update.signatureSlot} must be after attested header slot ${update.attestedHeader.beacon.slot}`
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
if (update.attestedHeader.beacon.slot < update.finalizedHeader.beacon.slot) {
|
|
53
|
-
throw Error(
|
|
54
|
-
`attested header slot ${update.signatureSlot} must be after finalized header slot ${update.finalizedHeader.beacon.slot}`
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Verify that the `finality_branch`, if present, confirms `finalized_header`
|
|
59
|
-
// to match the finalized checkpoint root saved in the state of `attested_header`.
|
|
60
|
-
// Note that the genesis finalized checkpoint root is represented as a zero hash.
|
|
61
|
-
if (!isFinalityUpdate(update)) {
|
|
62
|
-
if (!isZeroedHeader(update.finalizedHeader.beacon)) {
|
|
63
|
-
throw Error("finalizedHeader must be zero for non-finality update");
|
|
64
|
-
}
|
|
65
|
-
} else {
|
|
66
|
-
let finalizedRoot: Root;
|
|
67
|
-
|
|
68
|
-
if (update.finalizedHeader.beacon.slot === GENESIS_SLOT) {
|
|
69
|
-
if (!isZeroedHeader(update.finalizedHeader.beacon)) {
|
|
70
|
-
throw Error("finalizedHeader must be zero for not finality update");
|
|
71
|
-
}
|
|
72
|
-
finalizedRoot = ZERO_HASH;
|
|
73
|
-
} else {
|
|
74
|
-
if (!isValidLightClientHeader(config, update.finalizedHeader)) {
|
|
75
|
-
throw Error("Finalized Header is not valid Light Client Header");
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
finalizedRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(update.finalizedHeader.beacon);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (
|
|
82
|
-
!isValidMerkleBranch(
|
|
83
|
-
finalizedRoot,
|
|
84
|
-
update.finalityBranch,
|
|
85
|
-
isElectraLightClientUpdate(update) ? FINALIZED_ROOT_DEPTH_ELECTRA : FINALIZED_ROOT_DEPTH,
|
|
86
|
-
isElectraLightClientUpdate(update) ? FINALIZED_ROOT_INDEX_ELECTRA : FINALIZED_ROOT_INDEX,
|
|
87
|
-
update.attestedHeader.beacon.stateRoot
|
|
88
|
-
)
|
|
89
|
-
) {
|
|
90
|
-
throw Error("Invalid finality header merkle branch");
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Verify that the `next_sync_committee`, if present, actually is the next sync committee saved in the
|
|
95
|
-
// state of the `attested_header`
|
|
96
|
-
if (!isSyncCommitteeUpdate(update)) {
|
|
97
|
-
if (!isZeroedSyncCommittee(update.nextSyncCommittee)) {
|
|
98
|
-
throw Error("nextSyncCommittee must be zero for non sync committee update");
|
|
99
|
-
}
|
|
100
|
-
} else {
|
|
101
|
-
if (
|
|
102
|
-
!isValidMerkleBranch(
|
|
103
|
-
ssz.altair.SyncCommittee.hashTreeRoot(update.nextSyncCommittee),
|
|
104
|
-
update.nextSyncCommitteeBranch,
|
|
105
|
-
isElectraLightClientUpdate(update) ? NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA : NEXT_SYNC_COMMITTEE_DEPTH,
|
|
106
|
-
isElectraLightClientUpdate(update) ? NEXT_SYNC_COMMITTEE_INDEX_ELECTRA : NEXT_SYNC_COMMITTEE_INDEX,
|
|
107
|
-
update.attestedHeader.beacon.stateRoot
|
|
108
|
-
)
|
|
109
|
-
) {
|
|
110
|
-
throw Error("Invalid next sync committee merkle branch");
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Verify sync committee aggregate signature
|
|
115
|
-
|
|
116
|
-
const participantPubkeys = getParticipantPubkeys(syncCommittee.pubkeys, update.syncAggregate.syncCommitteeBits);
|
|
117
|
-
|
|
118
|
-
const signingRoot = ssz.phase0.SigningData.hashTreeRoot({
|
|
119
|
-
objectRoot: ssz.phase0.BeaconBlockHeader.hashTreeRoot(update.attestedHeader.beacon),
|
|
120
|
-
domain: store.config.getDomain(update.signatureSlot - 1, DOMAIN_SYNC_COMMITTEE),
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
if (!isValidBlsAggregate(participantPubkeys, signingRoot, update.syncAggregate.syncCommitteeSignature)) {
|
|
124
|
-
throw Error("Invalid aggregate signature");
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Same as BLS.verifyAggregate but with detailed error messages
|
|
130
|
-
*/
|
|
131
|
-
function isValidBlsAggregate(publicKeys: PublicKey[], message: Uint8Array, signature: Uint8Array): boolean {
|
|
132
|
-
let aggPubkey: PublicKey;
|
|
133
|
-
try {
|
|
134
|
-
aggPubkey = bls.PublicKey.aggregate(publicKeys);
|
|
135
|
-
} catch (e) {
|
|
136
|
-
(e as Error).message = `Error aggregating pubkeys: ${(e as Error).message}`;
|
|
137
|
-
throw e;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
let sig: Signature;
|
|
141
|
-
try {
|
|
142
|
-
sig = bls.Signature.fromBytes(signature, undefined, true);
|
|
143
|
-
} catch (e) {
|
|
144
|
-
(e as Error).message = `Error deserializing signature: ${(e as Error).message}`;
|
|
145
|
-
throw e;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
try {
|
|
149
|
-
return sig.verify(aggPubkey, message);
|
|
150
|
-
} catch (e) {
|
|
151
|
-
(e as Error).message = `Error verifying signature: ${(e as Error).message}`;
|
|
152
|
-
throw e;
|
|
153
|
-
}
|
|
154
|
-
}
|
package/src/transport/index.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import {ForkName} from "@lodestar/params";
|
|
2
|
-
import {
|
|
3
|
-
LightClientBootstrap,
|
|
4
|
-
LightClientFinalityUpdate,
|
|
5
|
-
LightClientOptimisticUpdate,
|
|
6
|
-
LightClientUpdate,
|
|
7
|
-
SyncPeriod,
|
|
8
|
-
} from "@lodestar/types";
|
|
9
|
-
|
|
10
|
-
export interface LightClientTransport {
|
|
11
|
-
getUpdates(
|
|
12
|
-
startPeriod: SyncPeriod,
|
|
13
|
-
count: number
|
|
14
|
-
): Promise<
|
|
15
|
-
{
|
|
16
|
-
version: ForkName;
|
|
17
|
-
data: LightClientUpdate;
|
|
18
|
-
}[]
|
|
19
|
-
>;
|
|
20
|
-
/**
|
|
21
|
-
* Returns the latest optimistic head update available. Clients should use the SSE type `light_client_optimistic_update`
|
|
22
|
-
* unless to get the very first head update after syncing, or if SSE are not supported by the server.
|
|
23
|
-
*/
|
|
24
|
-
getOptimisticUpdate(): Promise<{version: ForkName; data: LightClientOptimisticUpdate}>;
|
|
25
|
-
getFinalityUpdate(): Promise<{version: ForkName; data: LightClientFinalityUpdate}>;
|
|
26
|
-
/**
|
|
27
|
-
* Fetch a bootstrapping state with a proof to a trusted block root.
|
|
28
|
-
* The trusted block root should be fetched with similar means to a weak subjectivity checkpoint.
|
|
29
|
-
* Only block roots for checkpoints are guaranteed to be available.
|
|
30
|
-
*/
|
|
31
|
-
getBootstrap(blockRoot: string): Promise<{version: ForkName; data: LightClientBootstrap}>;
|
|
32
|
-
|
|
33
|
-
// registers handler for LightClientOptimisticUpdate. This can come either via sse or p2p
|
|
34
|
-
onOptimisticUpdate(handler: (optimisticUpdate: LightClientOptimisticUpdate) => void): void;
|
|
35
|
-
// registers handler for LightClientFinalityUpdate. This can come either via sse or p2p
|
|
36
|
-
onFinalityUpdate(handler: (finalityUpdate: LightClientFinalityUpdate) => void): void;
|
|
37
|
-
}
|
package/src/transport/rest.ts
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import mitt, {Emitter as MittEmitter} from "mitt";
|
|
2
|
-
import {type ApiClient, routes} from "@lodestar/api";
|
|
3
|
-
import {type ForkName} from "@lodestar/params";
|
|
4
|
-
import {
|
|
5
|
-
LightClientBootstrap,
|
|
6
|
-
LightClientFinalityUpdate,
|
|
7
|
-
LightClientOptimisticUpdate,
|
|
8
|
-
LightClientUpdate,
|
|
9
|
-
type SyncPeriod,
|
|
10
|
-
} from "@lodestar/types";
|
|
11
|
-
import {type LightClientTransport} from "./interface.js";
|
|
12
|
-
|
|
13
|
-
export type LightClientRestEvents = {
|
|
14
|
-
[routes.events.EventType.lightClientFinalityUpdate]: (update: LightClientFinalityUpdate) => void;
|
|
15
|
-
[routes.events.EventType.lightClientOptimisticUpdate]: (update: LightClientOptimisticUpdate) => void;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export type LightClientRestEmitter = MittEmitter<LightClientRestEvents>;
|
|
19
|
-
|
|
20
|
-
export class LightClientRestTransport implements LightClientTransport {
|
|
21
|
-
private controller = new AbortController();
|
|
22
|
-
private readonly eventEmitter: LightClientRestEmitter = mitt();
|
|
23
|
-
private subscribedEventstream = false;
|
|
24
|
-
|
|
25
|
-
constructor(private readonly api: ApiClient) {}
|
|
26
|
-
|
|
27
|
-
async getUpdates(
|
|
28
|
-
startPeriod: SyncPeriod,
|
|
29
|
-
count: number
|
|
30
|
-
): Promise<
|
|
31
|
-
{
|
|
32
|
-
version: ForkName;
|
|
33
|
-
data: LightClientUpdate;
|
|
34
|
-
}[]
|
|
35
|
-
> {
|
|
36
|
-
const res = await this.api.lightclient.getLightClientUpdatesByRange({startPeriod, count});
|
|
37
|
-
const updates = res.value();
|
|
38
|
-
const {versions} = res.meta();
|
|
39
|
-
return updates.map((data, i) => ({data, version: versions[i]}));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async getOptimisticUpdate(): Promise<{version: ForkName; data: LightClientOptimisticUpdate}> {
|
|
43
|
-
const res = await this.api.lightclient.getLightClientOptimisticUpdate();
|
|
44
|
-
return {version: res.meta().version, data: res.value()};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async getFinalityUpdate(): Promise<{version: ForkName; data: LightClientFinalityUpdate}> {
|
|
48
|
-
const res = await this.api.lightclient.getLightClientFinalityUpdate();
|
|
49
|
-
return {version: res.meta().version, data: res.value()};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async getBootstrap(blockRoot: string): Promise<{version: ForkName; data: LightClientBootstrap}> {
|
|
53
|
-
const res = await this.api.lightclient.getLightClientBootstrap({blockRoot});
|
|
54
|
-
return {version: res.meta().version, data: res.value()};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
onOptimisticUpdate(handler: (optimisticUpdate: LightClientOptimisticUpdate) => void): void {
|
|
58
|
-
this.subscribeEventstream();
|
|
59
|
-
this.eventEmitter.on(routes.events.EventType.lightClientOptimisticUpdate, handler);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
onFinalityUpdate(handler: (finalityUpdate: LightClientFinalityUpdate) => void): void {
|
|
63
|
-
this.subscribeEventstream();
|
|
64
|
-
this.eventEmitter.on(routes.events.EventType.lightClientFinalityUpdate, handler);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
private subscribeEventstream(): void {
|
|
68
|
-
if (this.subscribedEventstream) {
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
void this.api.events.eventstream({
|
|
73
|
-
topics: [routes.events.EventType.lightClientOptimisticUpdate, routes.events.EventType.lightClientFinalityUpdate],
|
|
74
|
-
signal: this.controller.signal,
|
|
75
|
-
onEvent: (event) => {
|
|
76
|
-
switch (event.type) {
|
|
77
|
-
case routes.events.EventType.lightClientOptimisticUpdate:
|
|
78
|
-
this.eventEmitter.emit(routes.events.EventType.lightClientOptimisticUpdate, event.message.data);
|
|
79
|
-
break;
|
|
80
|
-
|
|
81
|
-
case routes.events.EventType.lightClientFinalityUpdate:
|
|
82
|
-
this.eventEmitter.emit(routes.events.EventType.lightClientFinalityUpdate, event.message.data);
|
|
83
|
-
break;
|
|
84
|
-
}
|
|
85
|
-
},
|
|
86
|
-
});
|
|
87
|
-
this.subscribedEventstream = true;
|
|
88
|
-
}
|
|
89
|
-
}
|
package/src/transport.ts
DELETED
package/src/types.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type {PublicKey} from "@chainsafe/bls/types";
|
|
2
|
-
import {LightClientHeader, LightClientUpdate, SyncPeriod} from "@lodestar/types";
|
|
3
|
-
|
|
4
|
-
export type LightClientStoreFast = {
|
|
5
|
-
snapshot: LightClientSnapshotFast;
|
|
6
|
-
bestUpdates: Map<SyncPeriod, LightClientUpdate>;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
export type LightClientSnapshotFast = {
|
|
10
|
-
/** Beacon block header */
|
|
11
|
-
header: LightClientHeader;
|
|
12
|
-
/** Sync committees corresponding to the header */
|
|
13
|
-
currentSyncCommittee: SyncCommitteeFast;
|
|
14
|
-
nextSyncCommittee: SyncCommitteeFast;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export type SyncCommitteeFast = {
|
|
18
|
-
pubkeys: PublicKey[];
|
|
19
|
-
aggregatePubkey: PublicKey;
|
|
20
|
-
};
|
package/src/utils/api.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import {ApiClient, ApiRequestInit, getClient} from "@lodestar/api";
|
|
2
|
-
import {ChainForkConfig, createChainForkConfig} from "@lodestar/config";
|
|
3
|
-
import {NetworkName, networksChainConfig} from "@lodestar/config/networks";
|
|
4
|
-
|
|
5
|
-
export function getApiFromUrl(url: string, network: NetworkName, init?: ApiRequestInit): ApiClient {
|
|
6
|
-
if (!(network in networksChainConfig)) {
|
|
7
|
-
throw Error(`Invalid network name "${network}". Valid options are: ${Object.keys(networksChainConfig).join()}`);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
return getClient({urls: [url], globalInit: init}, {config: createChainForkConfig(networksChainConfig[network])});
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function getChainForkConfigFromNetwork(network: NetworkName): ChainForkConfig {
|
|
14
|
-
if (!(network in networksChainConfig)) {
|
|
15
|
-
throw Error(`Invalid network name "${network}". Valid options are: ${Object.keys(networksChainConfig).join()}`);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return createChainForkConfig(networksChainConfig[network]);
|
|
19
|
-
}
|
package/src/utils/chunkify.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Split an inclusive range into a sequence of contiguous inclusive ranges
|
|
3
|
-
* ```
|
|
4
|
-
* [[a,b], [c,d] ... Sn] = chunkifyInclusiveRange([a,z], n)
|
|
5
|
-
* // where
|
|
6
|
-
* [a,z] = [a,b] U [c,d] U ... U Sn
|
|
7
|
-
* ```
|
|
8
|
-
* @param from range start inclusive
|
|
9
|
-
* @param to range end inclusive
|
|
10
|
-
* @param chunks Maximum number of chunks, if range is big enough
|
|
11
|
-
*/
|
|
12
|
-
export function chunkifyInclusiveRange(from: number, to: number, itemsPerChunk: number): number[][] {
|
|
13
|
-
if (itemsPerChunk < 1) itemsPerChunk = 1;
|
|
14
|
-
const totalItems = to - from + 1;
|
|
15
|
-
// Enforce chunkCount >= 1
|
|
16
|
-
const chunkCount = Math.max(Math.ceil(totalItems / itemsPerChunk), 1);
|
|
17
|
-
|
|
18
|
-
const chunks: number[][] = [];
|
|
19
|
-
for (let i = 0; i < chunkCount; i++) {
|
|
20
|
-
const _from = from + i * itemsPerChunk;
|
|
21
|
-
const _to = Math.min(from + (i + 1) * itemsPerChunk - 1, to);
|
|
22
|
-
chunks.push([_from, _to]);
|
|
23
|
-
if (_to >= to) break;
|
|
24
|
-
}
|
|
25
|
-
return chunks;
|
|
26
|
-
}
|
package/src/utils/clock.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import {ChainConfig} from "@lodestar/config";
|
|
2
|
-
import {EPOCHS_PER_SYNC_COMMITTEE_PERIOD, SLOTS_PER_EPOCH} from "@lodestar/params";
|
|
3
|
-
import {Epoch, Slot, SyncPeriod} from "@lodestar/types";
|
|
4
|
-
|
|
5
|
-
export function getCurrentSlot(config: ChainConfig, genesisTime: number): Slot {
|
|
6
|
-
const diffInSeconds = Date.now() / 1000 - genesisTime;
|
|
7
|
-
return Math.floor(diffInSeconds / config.SECONDS_PER_SLOT);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/** Returns the slot if the internal clock were advanced by `toleranceSec`. */
|
|
11
|
-
export function slotWithFutureTolerance(config: ChainConfig, genesisTime: number, toleranceSec: number): Slot {
|
|
12
|
-
// this is the same to getting slot at now + toleranceSec
|
|
13
|
-
return getCurrentSlot(config, genesisTime - toleranceSec);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Return the epoch number at the given slot.
|
|
18
|
-
*/
|
|
19
|
-
export function computeEpochAtSlot(slot: Slot): Epoch {
|
|
20
|
-
return Math.floor(slot / SLOTS_PER_EPOCH);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Return the sync committee period at slot
|
|
25
|
-
*/
|
|
26
|
-
export function computeSyncPeriodAtSlot(slot: Slot): SyncPeriod {
|
|
27
|
-
return computeSyncPeriodAtEpoch(computeEpochAtSlot(slot));
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Return the sync committee period at epoch
|
|
32
|
-
*/
|
|
33
|
-
export function computeSyncPeriodAtEpoch(epoch: Epoch): SyncPeriod {
|
|
34
|
-
return Math.floor(epoch / EPOCHS_PER_SYNC_COMMITTEE_PERIOD);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function timeUntilNextEpoch(config: Pick<ChainConfig, "SECONDS_PER_SLOT">, genesisTime: number): number {
|
|
38
|
-
const milliSecondsPerEpoch = SLOTS_PER_EPOCH * config.SECONDS_PER_SLOT * 1000;
|
|
39
|
-
const msFromGenesis = Date.now() - genesisTime * 1000;
|
|
40
|
-
if (msFromGenesis >= 0) {
|
|
41
|
-
return milliSecondsPerEpoch - (msFromGenesis % milliSecondsPerEpoch);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return Math.abs(msFromGenesis % milliSecondsPerEpoch);
|
|
45
|
-
}
|
package/src/utils/domain.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
// Only used by processDeposit + lightclient
|
|
2
|
-
|
|
3
|
-
import {Type} from "@chainsafe/ssz";
|
|
4
|
-
import {Domain, DomainType, Epoch, Root, Version, phase0, ssz} from "@lodestar/types";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Return the domain for the [[domainType]] and [[forkVersion]].
|
|
8
|
-
*/
|
|
9
|
-
export function computeDomain(domainType: DomainType, forkVersion: Version, genesisValidatorRoot: Root): Uint8Array {
|
|
10
|
-
const forkDataRoot = computeForkDataRoot(forkVersion, genesisValidatorRoot);
|
|
11
|
-
const domain = new Uint8Array(32);
|
|
12
|
-
domain.set(domainType, 0);
|
|
13
|
-
domain.set(forkDataRoot.slice(0, 28), 4);
|
|
14
|
-
return domain;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Return the ForkVersion at an epoch from a Fork type
|
|
19
|
-
*/
|
|
20
|
-
export function getForkVersion(fork: phase0.Fork, epoch: Epoch): Version {
|
|
21
|
-
return epoch < fork.epoch ? fork.previousVersion : fork.currentVersion;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Used primarily in signature domains to avoid collisions across forks/chains.
|
|
26
|
-
*/
|
|
27
|
-
export function computeForkDataRoot(currentVersion: Version, genesisValidatorsRoot: Root): Uint8Array {
|
|
28
|
-
const forkData: phase0.ForkData = {
|
|
29
|
-
currentVersion,
|
|
30
|
-
genesisValidatorsRoot,
|
|
31
|
-
};
|
|
32
|
-
return ssz.phase0.ForkData.hashTreeRoot(forkData);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Return the signing root of an object by calculating the root of the object-domain tree.
|
|
37
|
-
*/
|
|
38
|
-
export function computeSigningRoot<T>(type: Type<T>, sszObject: T, domain: Domain): Uint8Array {
|
|
39
|
-
const domainWrappedObject: phase0.SigningData = {
|
|
40
|
-
objectRoot: type.hashTreeRoot(sszObject),
|
|
41
|
-
domain,
|
|
42
|
-
};
|
|
43
|
-
return ssz.phase0.SigningData.hashTreeRoot(domainWrappedObject);
|
|
44
|
-
}
|
package/src/utils/index.ts
DELETED
package/src/utils/logger.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* biome-ignore lint/suspicious/noExplicitAny: We need to use `any` type here
|
|
3
|
-
* biome-ignore-all lint/suspicious/noConsole: The logger need to use the console
|
|
4
|
-
* */
|
|
5
|
-
export type LogHandler = (message: string, context?: any, error?: Error) => void;
|
|
6
|
-
|
|
7
|
-
export type ILcLogger = {
|
|
8
|
-
error: LogHandler;
|
|
9
|
-
warn: LogHandler;
|
|
10
|
-
info: LogHandler;
|
|
11
|
-
debug: LogHandler;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* With `console` module and ignoring debug logs
|
|
16
|
-
*/
|
|
17
|
-
export function getConsoleLogger(opts?: {logDebug?: boolean}): ILcLogger {
|
|
18
|
-
return {
|
|
19
|
-
error: console.error,
|
|
20
|
-
warn: console.warn,
|
|
21
|
-
info: console.log,
|
|
22
|
-
debug: opts?.logDebug ? console.log : () => {},
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* @deprecated - Use `getConsoleLogger` instead.
|
|
28
|
-
*/
|
|
29
|
-
export const getLcLoggerConsole = getConsoleLogger;
|
package/src/utils/map.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Prune an arbitrary set removing the first keys to have a set.size === maxItems.
|
|
3
|
-
* Returns the count of deleted items.
|
|
4
|
-
*/
|
|
5
|
-
export function pruneSetToMax<T>(set: Set<T> | Map<T, unknown>, maxItems: number): number {
|
|
6
|
-
let itemsToDelete = set.size - maxItems;
|
|
7
|
-
const deletedItems = Math.max(0, itemsToDelete);
|
|
8
|
-
|
|
9
|
-
if (itemsToDelete > 0) {
|
|
10
|
-
for (const key of set.keys()) {
|
|
11
|
-
set.delete(key);
|
|
12
|
-
itemsToDelete--;
|
|
13
|
-
if (itemsToDelete <= 0) {
|
|
14
|
-
break;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return deletedItems;
|
|
20
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import {ZERO_HASH} from "../spec/utils.js";
|
|
2
|
-
|
|
3
|
-
export const SYNC_COMMITTEES_DEPTH = 4;
|
|
4
|
-
export const SYNC_COMMITTEES_INDEX = 11;
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Given merkle branch ``branch``, extend its depth according to ``depth``
|
|
8
|
-
* If given ``depth`` is less than the depth of ``branch``, it will return
|
|
9
|
-
* unmodified ``branch``
|
|
10
|
-
*/
|
|
11
|
-
export function normalizeMerkleBranch(branch: Uint8Array[], depth: number): Uint8Array[] {
|
|
12
|
-
const numExtraDepth = depth - branch.length;
|
|
13
|
-
|
|
14
|
-
return [...Array.from({length: numExtraDepth}, () => ZERO_HASH), ...branch];
|
|
15
|
-
}
|
package/src/utils/update.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import {SYNC_COMMITTEE_SIZE} from "@lodestar/params";
|
|
2
|
-
import {Slot} from "@lodestar/types";
|
|
3
|
-
|
|
4
|
-
export type LightclientUpdateStats = {
|
|
5
|
-
isFinalized: boolean;
|
|
6
|
-
participation: number;
|
|
7
|
-
slot: Slot;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Returns the update with more bits. On ties, newUpdate is the better
|
|
12
|
-
*
|
|
13
|
-
* Spec v1.0.1
|
|
14
|
-
* ```python
|
|
15
|
-
* max(store.valid_updates, key=lambda update: sum(update.sync_committee_bits)))
|
|
16
|
-
* ```
|
|
17
|
-
*/
|
|
18
|
-
export function isBetterUpdate(prev: LightclientUpdateStats, next: LightclientUpdateStats): boolean {
|
|
19
|
-
// Finalized if participation is over 66%
|
|
20
|
-
if (!prev.isFinalized && next.isFinalized && next.participation * 3 > SYNC_COMMITTEE_SIZE * 2) {
|
|
21
|
-
return true;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Higher bit count
|
|
25
|
-
if (prev.participation > next.participation) return false;
|
|
26
|
-
if (prev.participation < next.participation) return true;
|
|
27
|
-
|
|
28
|
-
// else keep the oldest, lowest chance or re-org and requires less updating
|
|
29
|
-
return prev.slot > next.slot;
|
|
30
|
-
}
|
package/src/utils/utils.ts
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import bls from "@chainsafe/bls";
|
|
2
|
-
import type {PublicKey} from "@chainsafe/bls/types";
|
|
3
|
-
import {BitArray} from "@chainsafe/ssz";
|
|
4
|
-
import {ApiClient} from "@lodestar/api";
|
|
5
|
-
import {Bytes32, Root, altair, ssz} from "@lodestar/types";
|
|
6
|
-
import {BeaconBlockHeader} from "@lodestar/types/phase0";
|
|
7
|
-
import {GenesisData} from "../index.js";
|
|
8
|
-
import {SyncCommitteeFast} from "../types.js";
|
|
9
|
-
|
|
10
|
-
export function sumBits(bits: BitArray): number {
|
|
11
|
-
return bits.getTrueBitIndexes().length;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function isZeroHash(root: Root): boolean {
|
|
15
|
-
for (let i = 0; i < root.length; i++) {
|
|
16
|
-
if (root[i] !== 0) {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return true;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function assertZeroHashes(rootArray: Root[], expectedLength: number, errorMessage: string): void {
|
|
24
|
-
if (rootArray.length !== expectedLength) {
|
|
25
|
-
throw Error(`Wrong length ${errorMessage}`);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
for (const root of rootArray) {
|
|
29
|
-
if (!isZeroHash(root)) {
|
|
30
|
-
throw Error(`Not zeroed ${errorMessage}`);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Util to guarantee that all bits have a corresponding pubkey
|
|
37
|
-
*/
|
|
38
|
-
export function getParticipantPubkeys<T>(pubkeys: T[], bits: BitArray): T[] {
|
|
39
|
-
// BitArray.intersectValues() checks the length is correct
|
|
40
|
-
return bits.intersectValues(pubkeys);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function toBlockHeader(block: altair.BeaconBlock): BeaconBlockHeader {
|
|
44
|
-
return {
|
|
45
|
-
slot: block.slot,
|
|
46
|
-
proposerIndex: block.proposerIndex,
|
|
47
|
-
parentRoot: block.parentRoot,
|
|
48
|
-
stateRoot: block.stateRoot,
|
|
49
|
-
bodyRoot: ssz.altair.BeaconBlockBody.hashTreeRoot(block.body),
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function deserializePubkeys(pubkeys: altair.LightClientUpdate["nextSyncCommittee"]["pubkeys"]): PublicKey[] {
|
|
54
|
-
return pubkeys.map((pk) => bls.PublicKey.fromBytes(pk));
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function serializePubkeys(pubkeys: PublicKey[]): altair.LightClientUpdate["nextSyncCommittee"]["pubkeys"] {
|
|
58
|
-
return pubkeys.map((pk) => pk.toBytes());
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function deserializeSyncCommittee(syncCommittee: altair.SyncCommittee): SyncCommitteeFast {
|
|
62
|
-
return {
|
|
63
|
-
pubkeys: deserializePubkeys(syncCommittee.pubkeys),
|
|
64
|
-
aggregatePubkey: bls.PublicKey.fromBytes(syncCommittee.aggregatePubkey),
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function serializeSyncCommittee(syncCommittee: SyncCommitteeFast): altair.SyncCommittee {
|
|
69
|
-
return {
|
|
70
|
-
pubkeys: serializePubkeys(syncCommittee.pubkeys),
|
|
71
|
-
aggregatePubkey: syncCommittee.aggregatePubkey.toBytes(),
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function isEmptyHeader(header: BeaconBlockHeader): boolean {
|
|
76
|
-
const emptyValue = ssz.phase0.BeaconBlockHeader.defaultValue();
|
|
77
|
-
return ssz.phase0.BeaconBlockHeader.equals(emptyValue, header);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Thanks https://github.com/iliakan/detect-node/blob/master/index.esm.js
|
|
81
|
-
export const isNode =
|
|
82
|
-
Object.prototype.toString.call(typeof process !== "undefined" ? process : 0) === "[object process]";
|
|
83
|
-
|
|
84
|
-
export async function getGenesisData(api: Pick<ApiClient, "beacon">): Promise<GenesisData> {
|
|
85
|
-
const {genesisTime, genesisValidatorsRoot} = (await api.beacon.getGenesis()).value();
|
|
86
|
-
|
|
87
|
-
return {
|
|
88
|
-
genesisTime,
|
|
89
|
-
genesisValidatorsRoot,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export async function getFinalizedSyncCheckpoint(api: Pick<ApiClient, "beacon">): Promise<Bytes32> {
|
|
94
|
-
return (await api.beacon.getStateFinalityCheckpoints({stateId: "head"})).value().finalized.root;
|
|
95
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import {hasher} from "@chainsafe/persistent-merkle-tree";
|
|
2
|
-
import {byteArrayEquals} from "@chainsafe/ssz";
|
|
3
|
-
|
|
4
|
-
export const SYNC_COMMITTEES_DEPTH = 4;
|
|
5
|
-
export const SYNC_COMMITTEES_INDEX = 11;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Verify that the given ``leaf`` is on the merkle branch ``proof``
|
|
9
|
-
* starting with the given ``root``.
|
|
10
|
-
*
|
|
11
|
-
* Browser friendly version of verifyMerkleBranch
|
|
12
|
-
*/
|
|
13
|
-
export function isValidMerkleBranch(
|
|
14
|
-
leaf: Uint8Array,
|
|
15
|
-
proof: Uint8Array[],
|
|
16
|
-
depth: number,
|
|
17
|
-
index: number,
|
|
18
|
-
root: Uint8Array
|
|
19
|
-
): boolean {
|
|
20
|
-
let value = leaf;
|
|
21
|
-
for (let i = 0; i < depth; i++) {
|
|
22
|
-
if (Math.floor(index / 2 ** i) % 2) {
|
|
23
|
-
value = hasher.digest64(proof[i], value);
|
|
24
|
-
} else {
|
|
25
|
-
value = hasher.digest64(value, proof[i]);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return byteArrayEquals(value, root);
|
|
29
|
-
}
|