diff --git a/packages/api/src/beacon/routes/beacon/state.ts b/packages/api/src/beacon/routes/beacon/state.ts index 75d3549eb5a7..7047eaa42e77 100644 --- a/packages/api/src/beacon/routes/beacon/state.ts +++ b/packages/api/src/beacon/routes/beacon/state.ts @@ -109,6 +109,23 @@ export type Api = { > >; + /** + * Fetch the RANDAO mix for the requested epoch from the state identified by 'stateId'. + * + * @param stateId State identifier. + * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", "justified", \, \. + * @param epoch Fetch randao mix for the given epoch. If an epoch is not specified then the RANDAO mix for the state's current epoch will be returned. + */ + getStateRandao( + stateId: StateId, + epoch?: Epoch + ): Promise< + ApiClientResponse< + {[HttpStatusCode.OK]: {data: {randao: Root}; executionOptimistic: ExecutionOptimistic}}, + HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND + > + >; + /** * Get state finality checkpoints * Returns finality checkpoints for state with given 'stateId'. @@ -216,6 +233,7 @@ export const routesData: RoutesData = { getStateFinalityCheckpoints: {url: "/eth/v1/beacon/states/{state_id}/finality_checkpoints", method: "GET"}, getStateFork: {url: "/eth/v1/beacon/states/{state_id}/fork", method: "GET"}, getStateRoot: {url: "/eth/v1/beacon/states/{state_id}/root", method: "GET"}, + getStateRandao: {url: "/eth/v1/beacon/states/{state_id}/randao", method: "GET"}, getStateValidator: {url: "/eth/v1/beacon/states/{state_id}/validators/{validator_id}", method: "GET"}, getStateValidators: {url: "/eth/v1/beacon/states/{state_id}/validators", method: "GET"}, getStateValidatorBalances: {url: "/eth/v1/beacon/states/{state_id}/validator_balances", method: "GET"}, @@ -231,6 +249,7 @@ export type ReqTypes = { getStateFinalityCheckpoints: StateIdOnlyReq; getStateFork: StateIdOnlyReq; getStateRoot: StateIdOnlyReq; + getStateRandao: {params: {state_id: StateId}; query: {epoch?: number}}; getStateValidator: {params: {state_id: StateId; validator_id: ValidatorId}}; getStateValidators: {params: {state_id: StateId}; query: {id?: ValidatorId[]; status?: ValidatorStatus[]}}; getStateValidatorBalances: {params: {state_id: StateId}; query: {id?: ValidatorId[]}}; @@ -266,6 +285,15 @@ export function getReqSerializers(): ReqSerializers { getStateFork: stateIdOnlyReq, getStateRoot: stateIdOnlyReq, + getStateRandao: { + writeReq: (state_id, epoch) => ({params: {state_id}, query: {epoch}}), + parseReq: ({params, query}) => [params.state_id, query.epoch], + schema: { + params: {state_id: Schema.StringRequired}, + query: {epoch: Schema.Uint}, + }, + }, + getStateValidator: { writeReq: (state_id, validator_id) => ({params: {state_id, validator_id}}), parseReq: ({params}) => [params.state_id, params.validator_id], @@ -299,6 +327,10 @@ export function getReturnTypes(): ReturnTypes { root: ssz.Root, }); + const RandaoContainer = new ContainerType({ + randao: ssz.Root, + }); + const FinalityCheckpoints = new ContainerType( { previousJustified: ssz.phase0.Checkpoint, @@ -346,6 +378,7 @@ export function getReturnTypes(): ReturnTypes { return { getStateRoot: ContainerDataExecutionOptimistic(RootContainer), getStateFork: ContainerDataExecutionOptimistic(ssz.phase0.Fork), + getStateRandao: ContainerDataExecutionOptimistic(RandaoContainer), getStateFinalityCheckpoints: ContainerDataExecutionOptimistic(FinalityCheckpoints), getStateValidators: ContainerDataExecutionOptimistic(ArrayOf(ValidatorResponse)), getStateValidator: ContainerDataExecutionOptimistic(ValidatorResponse), diff --git a/packages/api/test/unit/beacon/testData/beacon.ts b/packages/api/test/unit/beacon/testData/beacon.ts index bb9697cf9587..5cb35540cf7a 100644 --- a/packages/api/test/unit/beacon/testData/beacon.ts +++ b/packages/api/test/unit/beacon/testData/beacon.ts @@ -10,6 +10,7 @@ import { import {GenericServerTestCases} from "../../../utils/genericServerTest.js"; const root = Buffer.alloc(32, 1); +const randao = Buffer.alloc(32, 1); const balance = 32e9; const pubkeyHex = toHexString(Buffer.alloc(48, 1)); @@ -131,6 +132,10 @@ export const testData: GenericServerTestCases = { args: ["head"], res: {executionOptimistic: true, data: ssz.phase0.Fork.defaultValue()}, }, + getStateRandao: { + args: ["head", 1], + res: {executionOptimistic: true, data: {randao}}, + }, getStateFinalityCheckpoints: { args: ["head"], res: { diff --git a/packages/beacon-node/src/api/impl/beacon/state/index.ts b/packages/beacon-node/src/api/impl/beacon/state/index.ts index 54d663234afe..c9f74b45a9f2 100644 --- a/packages/beacon-node/src/api/impl/beacon/state/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/state/index.ts @@ -5,7 +5,9 @@ import { computeEpochAtSlot, computeStartSlotAtEpoch, getCurrentEpoch, + getRandaoMix, } from "@lodestar/state-transition"; +import {EPOCHS_PER_HISTORICAL_VECTOR} from "@lodestar/params"; import {ApiError} from "../../errors.js"; import {ApiModules} from "../../types.js"; import { @@ -43,6 +45,25 @@ export function getBeaconStateApi({ }; }, + async getStateRandao(stateId, epoch) { + const {state, executionOptimistic} = 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 { + executionOptimistic, + data: { + randao, + }, + }; + }, + async getStateFinalityCheckpoints(stateId) { const {state, executionOptimistic} = await getState(stateId); return {