@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
299 lines • 15.3 kB
JavaScript
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