@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
144 lines (126 loc) • 4.51 kB
text/typescript
import {routes} from "@lodestar/api";
import {CheckpointWithHex, IForkChoice} from "@lodestar/fork-choice";
import {GENESIS_SLOT} from "@lodestar/params";
import {IBeaconStateView, PubkeyCache} from "@lodestar/state-transition";
import {BLSPubkey, Epoch, RootHex, Slot, ValidatorIndex, getValidatorStatus, phase0} from "@lodestar/types";
import {fromHex} from "@lodestar/utils";
import {IBeaconChain} from "../../../../chain/index.js";
import {ApiError, ValidationError} from "../../errors.js";
export function resolveStateId(
forkChoice: IForkChoice,
stateId: routes.beacon.StateId
): RootHex | Slot | CheckpointWithHex {
if (stateId === "head") {
return forkChoice.getHead().stateRoot;
}
if (stateId === "genesis") {
return GENESIS_SLOT;
}
if (stateId === "finalized") {
return forkChoice.getFinalizedCheckpoint();
}
if (stateId === "justified") {
return forkChoice.getJustifiedCheckpoint();
}
if (typeof stateId === "string" && stateId.startsWith("0x")) {
return stateId;
}
// id must be slot
const blockSlot = parseInt(String(stateId), 10);
if (Number.isNaN(blockSlot) && Number.isNaN(blockSlot - 0)) {
throw new ValidationError(`Invalid block id '${stateId}'`, "blockId");
}
return blockSlot;
}
export async function getStateResponseWithRegen(
chain: IBeaconChain,
inStateId: routes.beacon.StateId
): Promise<{state: IBeaconStateView | Uint8Array; executionOptimistic: boolean; finalized: boolean}> {
const stateId = resolveStateId(chain.forkChoice, inStateId);
const res =
typeof stateId === "string"
? await chain.getStateByStateRoot(stateId, {allowRegen: true})
: typeof stateId === "number"
? stateId > chain.clock.currentSlot
? null // Don't try to serve future slots
: stateId >= chain.forkChoice.getFinalizedBlock().slot
? await chain.getStateBySlot(stateId, {allowRegen: true})
: await chain.getHistoricalStateBySlot(stateId)
: await chain.getStateOrBytesByCheckpoint(stateId);
if (!res) {
throw new ApiError(404, `State not found for id '${inStateId}'`);
}
return res;
}
export function toValidatorResponse(
index: ValidatorIndex,
validator: phase0.Validator,
balance: number,
currentEpoch: Epoch
): routes.beacon.ValidatorResponse {
return {
index,
status: getValidatorStatus(validator, currentEpoch),
balance,
validator,
};
}
export function filterStateValidatorsByStatus(
statuses: string[],
state: IBeaconStateView,
pubkeyCache: PubkeyCache,
currentEpoch: Epoch
): routes.beacon.ValidatorResponse[] {
const responses: routes.beacon.ValidatorResponse[] = [];
const validators = state.getValidatorsByStatus(new Set(statuses), currentEpoch);
for (const validator of validators) {
const resp = getStateValidatorIndex(validator.pubkey, state, pubkeyCache);
if (resp.valid) {
responses.push(
toValidatorResponse(resp.validatorIndex, validator, state.getBalance(resp.validatorIndex), currentEpoch)
);
}
}
return responses;
}
type StateValidatorIndexResponse =
| {valid: true; validatorIndex: ValidatorIndex}
| {valid: false; code: number; reason: string};
export function getStateValidatorIndex(
id: routes.beacon.ValidatorId | BLSPubkey,
state: IBeaconStateView,
pubkeyCache: PubkeyCache
): StateValidatorIndexResponse {
if (typeof id === "string") {
// mutate `id` and fallthrough to below
if (id.startsWith("0x")) {
try {
id = fromHex(id);
} catch (_e) {
return {valid: false, code: 400, reason: "Invalid pubkey hex encoding"};
}
} else {
id = Number(id);
}
}
if (typeof id === "number") {
const validatorIndex = id;
// validator is invalid or added later than given stateId
if (!Number.isSafeInteger(validatorIndex)) {
return {valid: false, code: 400, reason: "Invalid validator index"};
}
if (validatorIndex >= state.validatorCount) {
return {valid: false, code: 404, reason: "Validator index from future state"};
}
return {valid: true, validatorIndex};
}
// typeof id === Uint8Array
const validatorIndex = pubkeyCache.getIndex(id);
if (validatorIndex === null) {
return {valid: false, code: 404, reason: "Validator pubkey not found in state"};
}
if (validatorIndex >= state.validatorCount) {
return {valid: false, code: 404, reason: "Validator pubkey from future state"};
}
return {valid: true, validatorIndex};
}