UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

299 lines • 15.3 kB
import { EPOCHS_PER_HISTORICAL_VECTOR, isForkPostElectra, isForkPostFulu } from "@lodestar/params"; import { computeEpochAtSlot, computeStartSlotAtEpoch, getCurrentEpoch, getRandaoMix, loadState, } from "@lodestar/state-transition"; import { getValidatorStatus } from "@lodestar/types"; import { fromHex } from "@lodestar/utils"; import { ApiError } from "../../errors.js"; import { assertUniqueItems } from "../../utils.js"; import { filterStateValidatorsByStatus, getStateResponseWithRegen, getStateValidatorIndex, toValidatorResponse, } from "./utils.js"; export function getBeaconStateApi({ chain, config, }) { async function getState(stateId) { const { state, executionOptimistic, finalized } = await getStateResponseWithRegen(chain, stateId); return { state: state instanceof Uint8Array ? loadState(config, chain.getHeadState(), state).state : state, executionOptimistic, finalized, }; } return { async getStateRoot({ stateId }) { const { state, executionOptimistic, finalized } = await getState(stateId); return { data: { root: state.hashTreeRoot() }, meta: { executionOptimistic, finalized }, }; }, async getStateFork({ stateId }) { const { state, executionOptimistic, finalized } = await getState(stateId); return { data: state.fork, meta: { executionOptimistic, finalized }, }; }, async getStateRandao({ stateId, epoch }) { const { state, executionOptimistic, finalized } = await getState(stateId); const stateEpoch = computeEpochAtSlot(state.slot); const usedEpoch = epoch ?? stateEpoch; if (!(stateEpoch < usedEpoch + EPOCHS_PER_HISTORICAL_VECTOR && usedEpoch <= stateEpoch)) { throw new ApiError(400, "Requested epoch is out of range"); } const randao = getRandaoMix(state, usedEpoch); return { data: { randao }, meta: { executionOptimistic, finalized }, }; }, async getStateFinalityCheckpoints({ stateId }) { const { state, executionOptimistic, finalized } = await getState(stateId); return { data: { currentJustified: state.currentJustifiedCheckpoint, previousJustified: state.previousJustifiedCheckpoint, finalized: state.finalizedCheckpoint, }, meta: { executionOptimistic, finalized }, }; }, async getStateValidators({ stateId, validatorIds = [], statuses = [] }) { const { state, executionOptimistic, finalized } = await getState(stateId); const currentEpoch = getCurrentEpoch(state); const { validators, balances } = state; // Get the validators sub tree once for all the loop const { pubkey2index } = chain.getHeadState().epochCtx; const validatorResponses = []; if (validatorIds.length) { assertUniqueItems(validatorIds, "Duplicate validator IDs provided"); for (const id of validatorIds) { const resp = getStateValidatorIndex(id, state, pubkey2index); if (resp.valid) { const validatorIndex = resp.validatorIndex; const validator = validators.getReadonly(validatorIndex); if (statuses.length && !statuses.includes(getValidatorStatus(validator, currentEpoch))) { continue; } const validatorResponse = toValidatorResponse(validatorIndex, validator, balances.get(validatorIndex), currentEpoch); validatorResponses.push(validatorResponse); } } return { data: validatorResponses, meta: { executionOptimistic, finalized }, }; } if (statuses.length) { assertUniqueItems(statuses, "Duplicate statuses provided"); const validatorsByStatus = filterStateValidatorsByStatus(statuses, state, pubkey2index, currentEpoch); return { data: validatorsByStatus, meta: { executionOptimistic, finalized }, }; } // TODO: This loops over the entire state, it's a DOS vector const validatorsArr = state.validators.getAllReadonlyValues(); const balancesArr = state.balances.getAll(); const resp = []; for (let i = 0; i < validatorsArr.length; i++) { resp.push(toValidatorResponse(i, validatorsArr[i], balancesArr[i], currentEpoch)); } return { data: resp, meta: { executionOptimistic, finalized }, }; }, async postStateValidators(args, context) { return this.getStateValidators(args, context); }, async postStateValidatorIdentities({ stateId, validatorIds = [] }) { const { state, executionOptimistic, finalized } = await getState(stateId); const { pubkey2index } = chain.getHeadState().epochCtx; let validatorIdentities; if (validatorIds.length) { assertUniqueItems(validatorIds, "Duplicate validator IDs provided"); validatorIdentities = []; for (const id of validatorIds) { const resp = getStateValidatorIndex(id, state, pubkey2index); if (resp.valid) { const index = resp.validatorIndex; const { pubkey, activationEpoch } = state.validators.getReadonly(index); validatorIdentities.push({ index, pubkey, activationEpoch }); } } } else { const validatorsArr = state.validators.getAllReadonlyValues(); validatorIdentities = new Array(validatorsArr.length); for (let i = 0; i < validatorsArr.length; i++) { const { pubkey, activationEpoch } = validatorsArr[i]; validatorIdentities[i] = { index: i, pubkey, activationEpoch }; } } return { data: validatorIdentities, meta: { executionOptimistic, finalized }, }; }, async getStateValidator({ stateId, validatorId }) { const { state, executionOptimistic, finalized } = await getState(stateId); const { pubkey2index } = chain.getHeadState().epochCtx; const resp = getStateValidatorIndex(validatorId, state, pubkey2index); if (!resp.valid) { throw new ApiError(resp.code, resp.reason); } const validatorIndex = resp.validatorIndex; return { data: toValidatorResponse(validatorIndex, state.validators.getReadonly(validatorIndex), state.balances.get(validatorIndex), getCurrentEpoch(state)), meta: { executionOptimistic, finalized }, }; }, async getStateValidatorBalances({ stateId, validatorIds = [] }) { const { state, executionOptimistic, finalized } = await getState(stateId); if (validatorIds.length) { assertUniqueItems(validatorIds, "Duplicate validator IDs provided"); const headState = chain.getHeadState(); const balances = []; for (const id of validatorIds) { if (typeof id === "number") { if (state.validators.length <= id) { continue; } balances.push({ index: id, balance: state.balances.get(id) }); } else { const index = headState.epochCtx.pubkey2index.get(fromHex(id)); if (index != null && index <= state.validators.length) { balances.push({ index, balance: state.balances.get(index) }); } } } return { data: balances, meta: { executionOptimistic, finalized }, }; } // TODO: This loops over the entire state, it's a DOS vector const balancesArr = state.balances.getAll(); const resp = []; for (let i = 0; i < balancesArr.length; i++) { resp.push({ index: i, balance: balancesArr[i] }); } return { data: resp, meta: { executionOptimistic, finalized }, }; }, async postStateValidatorBalances(args, context) { return this.getStateValidatorBalances(args, context); }, async getEpochCommittees({ stateId, ...filters }) { const { state, executionOptimistic, finalized } = await getState(stateId); const stateCached = state; if (stateCached.epochCtx === undefined) { throw new ApiError(400, `No cached state available for stateId: ${stateId}`); } const epoch = filters.epoch ?? computeEpochAtSlot(state.slot); const startSlot = computeStartSlotAtEpoch(epoch); const decisionRoot = stateCached.epochCtx.getShufflingDecisionRoot(epoch); const shuffling = await chain.shufflingCache.get(epoch, decisionRoot); if (!shuffling) { throw new ApiError(500, `No shuffling found to calculate committees for epoch: ${epoch} and decisionRoot: ${decisionRoot}`); } const committees = shuffling.committees; const committeesFlat = committees.flatMap((slotCommittees, slotInEpoch) => { const slot = startSlot + slotInEpoch; if (filters.slot !== undefined && filters.slot !== slot) { return []; } return slotCommittees.flatMap((committee, committeeIndex) => { if (filters.index !== undefined && filters.index !== committeeIndex) { return []; } return [ { index: committeeIndex, slot, validators: Array.from(committee), }, ]; }); }); return { data: committeesFlat, meta: { executionOptimistic, finalized }, }; }, /** * Retrieves the sync committees for the given state. * @param epoch Fetch sync committees for the given epoch. If not present then the sync committees for the epoch of the state will be obtained. */ async getEpochSyncCommittees({ stateId, epoch }) { // TODO: Should pick a state with the provided epoch too const { state, executionOptimistic, finalized } = await getState(stateId); // TODO: If possible compute the syncCommittees in advance of the fork and expose them here. // So the validators can prepare and potentially attest the first block. Not critical tho, it's very unlikely const stateEpoch = computeEpochAtSlot(state.slot); if (stateEpoch < config.ALTAIR_FORK_EPOCH) { throw new ApiError(400, "Requested state before ALTAIR_FORK_EPOCH"); } const stateCached = state; if (stateCached.epochCtx === undefined) { throw new ApiError(400, `No cached state available for stateId: ${stateId}`); } const syncCommitteeCache = stateCached.epochCtx.getIndexedSyncCommitteeAtEpoch(epoch ?? stateEpoch); return { data: { validators: new Array(...syncCommitteeCache.validatorIndices), // TODO: This is not used by the validator and will be deprecated soon validatorAggregates: [], }, meta: { executionOptimistic, finalized }, }; }, async getPendingDeposits({ stateId }, context) { const { state, executionOptimistic, finalized } = await getState(stateId); const fork = config.getForkName(state.slot); if (!isForkPostElectra(fork)) { throw new ApiError(400, `Cannot retrieve pending deposits for pre-electra state fork=${fork}`); } const { pendingDeposits } = state; return { data: context?.returnBytes ? pendingDeposits.serialize() : pendingDeposits.toValue(), meta: { executionOptimistic, finalized, version: fork }, }; }, async getPendingPartialWithdrawals({ stateId }, context) { const { state, executionOptimistic, finalized } = await getState(stateId); const fork = config.getForkName(state.slot); if (!isForkPostElectra(fork)) { throw new ApiError(400, `Cannot retrieve pending partial withdrawals for pre-electra state fork=${fork}`); } const { pendingPartialWithdrawals } = state; return { data: context?.returnBytes ? pendingPartialWithdrawals.serialize() : pendingPartialWithdrawals.toValue(), meta: { executionOptimistic, finalized, version: fork }, }; }, async getPendingConsolidations({ stateId }, context) { const { state, executionOptimistic, finalized } = await getState(stateId); const fork = config.getForkName(state.slot); if (!isForkPostElectra(fork)) { throw new ApiError(400, `Cannot retrieve pending consolidations for pre-electra state fork=${fork}`); } const { pendingConsolidations } = state; return { data: context?.returnBytes ? pendingConsolidations.serialize() : pendingConsolidations.toValue(), meta: { executionOptimistic, finalized, version: fork }, }; }, async getProposerLookahead({ stateId }, context) { const { state, executionOptimistic, finalized } = await getState(stateId); const fork = config.getForkName(state.slot); if (!isForkPostFulu(fork)) { throw new ApiError(400, `Cannot retrieve proposer lookahead for pre-fulu state fork=${fork}`); } const { proposerLookahead } = state; return { data: context?.returnBytes ? proposerLookahead.serialize() : proposerLookahead.toValue(), meta: { executionOptimistic, finalized, version: fork }, }; }, }; } //# sourceMappingURL=index.js.map