UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

303 lines • 15.4 kB
import { EPOCHS_PER_HISTORICAL_VECTOR, SLOTS_PER_EPOCH, SYNC_COMMITTEE_SUBNET_SIZE } from "@lodestar/params"; import { computeEpochAtSlot, computeStartSlotAtEpoch, getCurrentEpoch, isStatePostAltair, isStatePostElectra, isStatePostFulu, } from "@lodestar/state-transition"; import { getValidatorStatus, ssz } from "@lodestar/types"; 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 ? chain.getHeadState().loadOtherState(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 = state.getRandaoMix(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 { pubkeyCache } = chain; const validatorResponses = []; if (validatorIds.length) { assertUniqueItems(validatorIds, "Duplicate validator IDs provided"); for (const id of validatorIds) { const resp = getStateValidatorIndex(id, state, pubkeyCache); if (resp.valid) { const validatorIndex = resp.validatorIndex; const validator = state.getValidator(validatorIndex); if (statuses.length && !statuses.includes(getValidatorStatus(validator, currentEpoch))) { continue; } const validatorResponse = toValidatorResponse(validatorIndex, validator, state.getBalance(validatorIndex), currentEpoch); validatorResponses.push(validatorResponse); } } return { data: validatorResponses, meta: { executionOptimistic, finalized }, }; } if (statuses.length) { assertUniqueItems(statuses, "Duplicate statuses provided"); const validatorsByStatus = filterStateValidatorsByStatus(statuses, state, pubkeyCache, currentEpoch); return { data: validatorsByStatus, meta: { executionOptimistic, finalized }, }; } // TODO: This loops over the entire state, it's a DOS vector const validatorsArr = state.getAllValidators(); const balancesArr = state.getAllBalances(); 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 { pubkeyCache } = chain; let validatorIdentities; if (validatorIds.length) { assertUniqueItems(validatorIds, "Duplicate validator IDs provided"); validatorIdentities = []; for (const id of validatorIds) { const resp = getStateValidatorIndex(id, state, pubkeyCache); if (resp.valid) { const index = resp.validatorIndex; const { pubkey, activationEpoch } = state.getValidator(index); validatorIdentities.push({ index, pubkey, activationEpoch }); } } } else { const validatorsArr = state.getAllValidators(); 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 { pubkeyCache } = chain; const resp = getStateValidatorIndex(validatorId, state, pubkeyCache); if (!resp.valid) { throw new ApiError(resp.code, resp.reason); } const validatorIndex = resp.validatorIndex; return { data: toValidatorResponse(validatorIndex, state.getValidator(validatorIndex), state.getBalance(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 balances = []; for (const id of validatorIds) { const resp = getStateValidatorIndex(id, state, chain.pubkeyCache); if (resp.valid) { balances.push({ index: resp.validatorIndex, balance: state.getBalance(resp.validatorIndex), }); } } return { data: balances, meta: { executionOptimistic, finalized }, }; } // TODO: This loops over the entire state, it's a DOS vector const balancesArr = state.getAllBalances(); 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 stateEpoch = computeEpochAtSlot(state.slot); const epoch = filters.epoch ?? stateEpoch; const startSlot = computeStartSlotAtEpoch(epoch); const endSlot = startSlot + SLOTS_PER_EPOCH - 1; if (Math.abs(epoch - stateEpoch) > 1) { throw new ApiError(400, `Epoch ${epoch} must be within one epoch of state epoch ${stateEpoch}`); } if (filters.slot !== undefined && (filters.slot < startSlot || filters.slot > endSlot)) { throw new ApiError(400, `Slot ${filters.slot} is not in epoch ${epoch}`); } const decisionRoot = state.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"); } if (!isStatePostAltair(state)) { throw new Error("Expected Altair state for sync committee lookup"); } const syncCommitteeCache = state.getIndexedSyncCommitteeAtEpoch(epoch ?? stateEpoch); const validatorIndices = new Array(...syncCommitteeCache.validatorIndices); // Subcommittee assignments of the current sync committee const validatorAggregates = []; for (let i = 0; i < validatorIndices.length; i += SYNC_COMMITTEE_SUBNET_SIZE) { validatorAggregates.push(validatorIndices.slice(i, i + SYNC_COMMITTEE_SUBNET_SIZE)); } return { data: { validators: validatorIndices, validatorAggregates, }, meta: { executionOptimistic, finalized }, }; }, async getPendingDeposits({ stateId }, context) { const { state, executionOptimistic, finalized } = await getState(stateId); const fork = state.forkName; if (!isStatePostElectra(state)) { throw new ApiError(400, `Cannot retrieve pending deposits for pre-electra state fork=${fork}`); } const pendingDeposits = state.pendingDeposits; return { data: context?.returnBytes ? ssz.electra.PendingDeposits.serialize(pendingDeposits) : pendingDeposits, meta: { executionOptimistic, finalized, version: fork }, }; }, async getPendingPartialWithdrawals({ stateId }, context) { const { state, executionOptimistic, finalized } = await getState(stateId); const fork = state.forkName; if (!isStatePostElectra(state)) { throw new ApiError(400, `Cannot retrieve pending partial withdrawals for pre-electra state fork=${fork}`); } const pendingPartialWithdrawals = state.pendingPartialWithdrawals; return { data: context?.returnBytes ? ssz.electra.PendingPartialWithdrawals.serialize(pendingPartialWithdrawals) : pendingPartialWithdrawals, meta: { executionOptimistic, finalized, version: fork }, }; }, async getPendingConsolidations({ stateId }, context) { const { state, executionOptimistic, finalized } = await getState(stateId); const fork = state.forkName; if (!isStatePostElectra(state)) { throw new ApiError(400, `Cannot retrieve pending consolidations for pre-electra state fork=${fork}`); } const pendingConsolidations = state.pendingConsolidations; return { data: context?.returnBytes ? ssz.electra.PendingConsolidations.serialize(pendingConsolidations) : pendingConsolidations, meta: { executionOptimistic, finalized, version: fork }, }; }, async getProposerLookahead({ stateId }, context) { const { state, executionOptimistic, finalized } = await getState(stateId); const fork = state.forkName; if (!isStatePostFulu(state)) { throw new ApiError(400, `Cannot retrieve proposer lookahead for pre-fulu state fork=${fork}`); } const proposerLookahead = state.proposerLookahead; return { data: context?.returnBytes ? ssz.fulu.ProposerLookahead.serialize(proposerLookahead) : proposerLookahead, meta: { executionOptimistic, finalized, version: fork }, }; }, }; } //# sourceMappingURL=index.js.map