libethsync/src/util.ts

134 lines
3.8 KiB
TypeScript

import {
createBeaconConfig,
BeaconConfig,
ChainForkConfig,
} from "@lodestar/config";
import { allForks } from "@lodestar/types";
import { BEACON_SYNC_SUPER_MAJORITY, mainnetConfig } from "./constants.js";
import { networksChainConfig } from "@lodestar/config/networks";
import { fromHexString } from "@chainsafe/ssz";
import { capella, OptimisticUpdate, phase0, VerifyWithReason } from "#types.js";
import { assertValidSignedHeader } from "@lodestar/light-client/validation";
import bls from "@chainsafe/bls/switchable";
import axiosRetry from "axios-retry";
import axios from "axios";
import { digest } from "@chainsafe/as-sha256";
import { concatBytes } from "@noble/hashes/utils";
import {
deserializeSyncCommittee,
isValidMerkleBranch,
} from "@lodestar/light-client/utils";
import {
BLOCK_BODY_EXECUTION_PAYLOAD_DEPTH as EXECUTION_PAYLOAD_DEPTH,
BLOCK_BODY_EXECUTION_PAYLOAD_INDEX as EXECUTION_PAYLOAD_INDEX,
} from "@lodestar/params";
export function getDefaultClientConfig() {
const chainConfig = createBeaconConfig(
networksChainConfig.mainnet,
fromHexString(mainnetConfig.genesis_validator_root),
);
return {
genesis: {
committee: mainnetConfig.committee_pk,
slot: parseInt(mainnetConfig.slot),
time: parseInt(mainnetConfig.genesis_time),
},
chainConfig,
n: 1,
};
}
export function optimisticUpdateFromJSON(update: any): OptimisticUpdate {
return capella.ssz.LightClientOptimisticUpdate.fromJson(update);
}
export async function optimisticUpdateVerify(
committee: Uint8Array[],
update: OptimisticUpdate,
): Promise<VerifyWithReason> {
try {
const { attestedHeader: header, syncAggregate } = update;
const headerBlockRoot = phase0.ssz.BeaconBlockHeader.hashTreeRoot(
header.beacon,
);
const chainConfig = getDefaultClientConfig().chainConfig;
const committeeFast = deserializeSyncCommittee({
pubkeys: committee,
aggregatePubkey: bls.PublicKey.aggregate(
deserializePubkeys(committee),
).toBytes(),
});
try {
assertValidSignedHeader(
chainConfig,
committeeFast,
syncAggregate,
headerBlockRoot,
header.beacon.slot,
);
} catch (e) {
return { correct: false, reason: "invalid signatures" };
}
const participation =
syncAggregate.syncCommitteeBits.getTrueBitIndexes().length;
if (participation < BEACON_SYNC_SUPER_MAJORITY) {
return { correct: false, reason: "insufficient signatures" };
}
if (!isValidLightClientHeader(chainConfig, header)) {
return { correct: false, reason: "invalid header" };
}
return { correct: true };
} catch (e) {
console.error(e);
return { correct: false, reason: (e as Error).message };
}
}
export function deserializePubkeys(pubkeys) {
return pubkeys.map((pk) => bls.PublicKey.fromBytes(pk));
}
export function getCommitteeHash(committee: Uint8Array[]): Uint8Array {
return digest(concatBytes(...committee));
}
export const consensusClient = axios.create();
axiosRetry(consensusClient, { retries: 3 });
export async function getConsensusOptimisticUpdate() {
const resp = await consensusClient.get(
`/eth/v1/beacon/light_client/optimistic_update`,
);
const update = resp.data;
if (!update) {
throw Error(`fetching optimistic update failed`);
}
return update.data;
}
function isValidLightClientHeader(
config: ChainForkConfig,
header: allForks.LightClientHeader,
): boolean {
return isValidMerkleBranch(
config
.getExecutionForkTypes(header.beacon.slot)
.ExecutionPayloadHeader.hashTreeRoot(
(header as capella.LightClientHeader).execution,
),
(header as capella.LightClientHeader).executionBranch,
EXECUTION_PAYLOAD_DEPTH,
EXECUTION_PAYLOAD_INDEX,
header.beacon.bodyRoot,
);
}